[Node.js] 新旧APIでのURLパースの違い

[Node.js] 新旧APIでのURLパースの違い:

千 Advent Calendar 2018の19日目の記事です。

Node.jsでurlのパースをするには2通りの方法があると知り、それぞれ何が違うのか調べてみた内容を書きたいと思います。


背景

Electronアプリでaws-sdkをプロキシに対応させるために、以下のようなことをしていました。

そこでurlのパースを行なったときに直面したお話です。

const AWS = require('aws-sdk') 
const url = require('url') 
const HttpsProxyAgent = require('https-proxy-agent') 
 
const proxyUrl = url.parse('http://proxyurl:8080') 
proxyUrl.rejectUnauthorized = false // 証明書によるエラーを無視 
 
AWS.config.update({ 
  httpOptions: { 
    agent: HttpsProxyAgent(proxyUrl)  
  } 
}) 
プロキシ対応の内容はこちらとだいたい同じです。

Using the AWS SDK for JavaScript from Behind a Proxy


url.parse()new URL()

URLのパースのために、

url.parse('http://proxyurl:8080') 
を使っていた所、これはLegacy APIなので↓が良いのでは?とご指摘を受けました。

new URL('http://proxyurl:8080') 
やってることは同じパースなら書き換えても変わらないだろうと思ったのですが、やってみると何やら挙動が変わりました。


2つのパースはどう違うのか

まず、url.parse()の結果はこうなります。

> url.parse('http://proxyurl:8080') 
Url { 
  protocol: 'http:', 
  slashes: true, 
  auth: null, 
  host: 'proxyurl:8080', 
  port: '8080', 
  hostname: 'proxyurl', 
  hash: null, 
  search: null, 
  query: null, 
  pathname: '/', 
  path: '/', 
  href: 'http://proxyurl:8080/' } 
次に、new URL()はこうです。

> new URL('http://proxyurl:8080') 
URL { 
  href: 'http://proxyurl:8080/', 
  origin: 'http://proxyurl:8080', 
  protocol: 'http:', 
  username: '', 
  password: '', 
  host: 'proxyurl:8080', 
  hostname: 'proxyurl', 
  port: '8080', 
  pathname: '/', 
  search: '', 
  searchParams: URLSearchParams {}, 
  hash: '' } 
大体は同じですが、Legacy APIの方ではauthだったのが、WHATWG APIではusernamepasswordに別れたりと若干違います。

どちらもhogeという変数に代入するとhoge.protocol'http:'の結果が得られます。


プロパティの変更

それぞれでポート番号を変更してみます。

> hoge = url.parse('http://proxyurl:8080') 
> hoge.port = '9090' 
> hoge 
Url { 
  protocol: 'http:', 
  slashes: true, 
  auth: null, 
  host: 'proxyurl:8080', 
  port: '9090', 
  hostname: 'proxyurl', 
  hash: null, 
  search: null, 
  query: null, 
  pathname: '/', 
  path: '/', 
  href: 'http://proxyurl:8080/' } 
url.parse()は変更すると、portが指定通り変わりました。
new URL()の方は、変更すると他のプロパティに表示されるポート番号も合わせて変わりました。

> hoge = new URL('http://proxyurl:8080') 
> hoge.port = '9090' 
> hoge 
URL { 
  href: 'http://proxyurl:9090/', 
  origin: 'http://proxyurl:9090', 
  protocol: 'http:', 
  username: '', 
  password: '', 
  host: 'proxyurl:9090', 
  hostname: 'proxyurl', 
  port: '9090', 
  pathname: '/', 
  search: '', 
  searchParams: URLSearchParams {}, 
  hash: '' } 


プロパティの削除

url.parse()の場合

> hoge = url.parse('http://proxyurl:8080') 
> delete hoge.port // true 
> hoge // portは削除されている 
Url { 
  protocol: 'http:', 
  slashes: true, 
  auth: null, 
  host: 'proxyurl:8080', 
  hostname: 'proxyurl', 
  hash: null, 
  search: null, 
  query: null, 
  pathname: '/', 
  path: '/', 
  href: 'http://proxyurl:8080/' } 
new URL()の場合

> hoge = new URL('http://proxyurl:8080') 
> delete hoge.port // true 
> hoge // portは残ったまま 
URL { 
  href: 'http://proxyurl:8080/', 
  origin: 'http://proxyurl:8080', 
  protocol: 'http:', 
  username: '', 
  password: '', 
  host: 'proxyurl:8080', 
  hostname: 'proxyurl', 
  port: '8080', 
  pathname: '/', 
  search: '', 
  searchParams: URLSearchParams {}, 
  hash: '' } 
delete hoge.xxxの結果はいずれもtrueが返ります。

ですが、new URL()の方は実際には削除されません。


Object.assign()の結果

まずはurl.parse()

> huga = Object.assign({}, url.parse('http://proxyurl:8080')) 
{ protocol: 'http:', 
  slashes: true, 
  auth: null, 
  host: 'proxyurl:8080', 
  port: '8080', 
  hostname: 'proxyurl', 
  hash: null, 
  search: null, 
  query: null, 
  pathname: '/', 
  path: '/', 
  href: 'http://proxyurl:8080/' } 
次にnew URL()

> huga = Object.assign({}, new URL('http://proxyurl:8080')) 
{ [Symbol(context)]:  
   URLContext { 
     flags: 400, 
     scheme: 'http:', 
     username: '', 
     password: '', 
     host: 'proxyurl', 
     port: 8080, 
     path: [ '' ], 
     query: null, 
     fragment: null }, 
  [Symbol(query)]: URLSearchParams {} } 
結構違いがあります。

Symbol()ってなに…?と思い調べて、Symbol型というものの存在を知りました。

Symbolについてはこちらの記事を読みました。→ ECMAScript6にシンボルができた理由

上の記事で

シンボルをキーとしてオブジェクトに保存された値は、一般的な for ... in ループで列挙されない。作成したシンボル(つまり上記のfoo)をスコープの外に出るなどして忘れてしまったら、既存の方法で値を参照することは二度と不可能になる。
と書かれている通り、キーとなるシンボルSymbol(context)を参照できないと、このオブジェクトの値を参照できなくなってしまうようです。


なぜ挙動が変わったのか

https-proxy-agentというモジュールを使っていたのですが、このモジュールでは引数で渡したurlオブジェクトに対してObject.assign()していました。

https://github.com/TooTallNate/node-https-proxy-agent/blob/11bc3478fdff1eec108cde4607bf10298be65ca3/index.js#L35

url.parse()であれば問題は無いのですが、new URL()だとObject.assign()後に値を参照できなくなり、得られる結果が変わってしまったのが原因でした。


Legacy API と WHATWG API

Node.jsのドキュメントを読んで、urlモジュールにはLegacy API と Node.js 8.0で正式採用されたWHATWG API の2つのAPIがあるとわかりました。
url.parse()はLegacy API、new URL()はWHATWG APIの書き方だそうです。

The url module provides two APIs for working with URLs: a legacy API that is Node.js specific, and a newer API that implements the same WHATWG URL Standard used by web browsers.
Legacy APIの方は、廃止されてはいませんが既存アプリケーションの下位互換性のために残っているようです。

新規のアプリケーションはWHATWG APIの使用を推奨していました。

While the Legacy API has not been deprecated, it is maintained solely for backwards compatibility with existing applications. New application code should use the WHATWG API.
なので今回は、https-proxy-agentとの互換性のためにurl.parse()を使うことにしました。


おわり

初のQiita投稿でしどろもどろな感じですが、おかしなところは随時修正していきます…(;´Д`)

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)