【大阪 / 横浜】インフラ / サーバーサイドエンジニア募集中!

【大阪 / 横浜】インフラ / サーバーサイドエンジニア募集中!

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始!

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始!

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【WordPress 専用】クラウドサーバー『ウェブスピード』

【WordPress 専用】クラウドサーバー『ウェブスピード』

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【低コスト】Wasabi オブジェクトストレージ 構築・運用サービス

【低コスト】Wasabi オブジェクトストレージ 構築・運用サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【中国現地企業に対応】中国クラウド / サーバー構築・運用保守

【中国現地企業に対応】中国クラウド / サーバー構築・運用保守

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

Node.js 8.0で正式実装されたWHATWG URL APIを紹介!

こんにちは。
開発チームのワイルド担当、まんだいです。

少し前の話になりますが、2017年5月30日にNode.jsの8.0.0が公開されました。
このバージョンから、npmのバージョン5.0.0がバンドルされるようになり、キャッシュ周りのコードが書き直され、高速化されているようです。

過去のバージョンとの速度比較を行ったツイートも公開され、この例では、従来の1/5の速度でインストールが完了しているようです。

現在のバージョンのV8エンジンのバージョンは5.8になりますが、V8 5.9やV8 6.0との互換性があるようで、今後のバージョンではV8エンジンのバージョンアップで更なる高速化が期待できそうとの事。 → Node.js 8.0が公開。npm 5.0バンドル、Node.js API搭載、WHATWG URLパーサーを正式サポートなど - Publickey

今回は、Node.js 8.0で正式実装された、WHATWG URL APIを見ていきたいと思います。


 

WHATWG URL API ってなんだ!?

実はNode.js 7系から存在したWHATWG URL APIですが、8.0.0で正式版となりました。
既に利用された事がある方も多くおられるでしょうが、「Experimental」(実験的)な立ち位置でしたので、本番環境で利用するのには、若干の躊躇があったかも知れません。

このAPIは、URLのパースを標準化するためのもので、従来のurlモジュールを拡張する形で提供されています。

const URL = require('url').URL;
const beyondUrl = new URL('http://www.beyondjapan.com/?abc=123&xyz=999#first');

console.log(beyondUrl);

// 結果
URL {
  href: 'http://www.beyondjapan.com/?abc=123&xyz=999#first',
  origin: 'http://www.beyondjapan.com',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'www.beyondjapan.com',
  hostname: 'www.beyondjapan.com',
  port: '',
  pathname: '/',
  search: '?abc=123&xyz=999',
  searchParams: URLSearchParams { 'abc' => '123', 'xyz' => '999' },
  hash: '#first' }

 

urlモジュールを使う事ももちろんできて、今まで通りの使用感です。

const url = require('url');
const beyondUrl = 'http://www.beyondjapan.com/?abc=123&xyz=999#first';

console.log(url.parse(beyondUrl));

// 結果
Url {
  protocol: 'http:',
  slashes: true,
  auth: null,
  host: 'www.beyondjapan.com',
  port: null,
  hostname: 'www.beyondjapan.com',
  hash: '#first',
  search: '?abc=123&xyz=999',
  query: 'abc=123&xyz=999',
  pathname: '/',
  path: '/?abc=123&xyz=999',
  href: 'http://www.beyondjapan.com/?abc=123&xyz=999#first' }

 

オブジェクト名が若干違うだけというのが、紛らわしいですが、出力されるオブジェクトの内容も少し違いますね。
WHATWG URL APIからのレスポンスでは、searchParamsというキーでクエリストリングがパースされて返ってくるのは便利です。

これだけでも使いたくなりますね。

 

URLオブジェクトの挙動

WHATWG APIから返ってきたURLオブジェクトは、各データにアクセスする事もできます。

const u = 'http://www.beyondjapan.com/?abc=123&xyz=999#first';
const URL = require('url').URL;
const beyondUrl = new URL(u);

console.log(beyondUrl.hostname);

// 結果
www.beyondjapan.com

 

ホスト名を別のものにしてみます。

const u = 'http://www.beyondjapan.com/?abc=123&xyz=999#first';
const URL = require('url').URL;
const beyondUrl = new URL(u);

beyondUrl.hostname = 'example.com';

console.log(beyondUrl);

// 結果
URL {
  href: 'http://example.com/?abc=123&xyz=999#first',
  origin: 'http://example.com',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'example.com',
  hostname: 'example.com',
  port: '',
  pathname: '/',
  search: '?abc=123&xyz=999',
  searchParams: URLSearchParams { 'abc' => '123', 'xyz' => '999' },
  hash: '#first' }

 

ホスト名は、ホスト名だけを認識して書き換えるので、以下のようにした場合もホスト名だけ変更されます。

const u = 'http://www.beyondjapan.com/?abc=123&xyz=999#first';
const URL = require('url').URL;
const beyondUrl = new URL(u);

beyondUrl.hostname = 'example.com:443';  // ポート番号も付けてみる

console.log(beyondUrl);

// 結果
URL {
  href: 'http://example.com/?abc=123&xyz=999#first',
  origin: 'http://example.com',
  protocol: 'http:',                                       // 変わらない
  username: '',
  password: '',
  host: 'example.com',
  hostname: 'example.com',
  port: '',                                                // 変わらない
  pathname: '/',
  search: '?abc=123&xyz=999',
  searchParams: URLSearchParams { 'abc' => '123', 'xyz' => '999' },
  hash: '#first' }

 

ポート番号を変えたいなら、ポート番号をちゃんと変更する必要があります。

const u = 'http://www.beyondjapan.com/?abc=123&xyz=999#first';
const URL = require('url').URL;
const beyondUrl = new URL(u);

beyondUrl.port = 443;

console.log(beyondUrl);

// 結果
URL {
  href: 'http://www.beyondjapan.com:443/?abc=123&xyz=999#first',
  origin: 'http://www.beyondjapan.com:443',
  protocol: 'http:',                                       // 変わらない
  username: '',
  password: '',
  host: 'www.beyondjapan.com:443',
  hostname: 'www.beyondjapan.com',
  port: '443',
  pathname: '/',
  search: '?abc=123&xyz=999',
  searchParams: URLSearchParams { 'abc' => '123', 'xyz' => '999' },
  hash: '#first' }

 

しかしながら、hostを変更した場合はそうではないようです。

const u = 'http://www.beyondjapan.com/?abc=123&xyz=999#first';
const URL = require('url').URL;
const beyondUrl = new URL(u);

beyondUrl.host = 'example.com:443';

console.log(beyondUrl);

// 結果
URL {
  href: 'http://example.com:443/?abc=123&xyz=999#first',
  origin: 'http://example.com:443',
  protocol: 'http:',                                       // 変わらない
  username: '',
  password: '',
  host: 'example.com:443',                                 // 変わった
  hostname: 'example.com',                                 // 変わった
  port: '443',                                             // 変わった
  pathname: '/',
  search: '?abc=123&xyz=999',
  searchParams: URLSearchParams { 'abc' => '123', 'xyz' => '999' },
  hash: '#first' }

 

URLSearchParamsクラス

先ほどはURLオブジェクトの挙動を見てみましたが、今度はURL.searchParamsから得られるURLSearchParamsクラスを見てみます。
このオブジェクトはNode.js 7系で実装されたクラスで、クエリストリングをパースし、getter/setterを提供するクラスになります。

公式ドキュメントにもquerystringモジュールと比較されているところがありますが、URLSearchParamsクラスはquerystringモジュールほど小回りが効くものではないとして、querystringモジュールが不要になるという訳ではないようです。

URLSearchParamsクラスは、urlモジュールの1クラスとして提供されているので、単独で使う事も可能です。
よって、解析だけでなく、生成にも利用できるパワフルなクラスです。

const {URLSearchParams} = require('url');

const qs = 'abc=123&xyz=456&aaa=789';
const qsObject = {
    abc:123,
    xyz:456,
    aaa:789
};
const qsIterable = [
    ['abc', 123],
    ['xyz', 456],
    ['aaa', 789],
];

const qsMap = new Map();
qsMap.set('abc', 123);
qsMap.set('xyz', 456);
qsMap.set('aaa', 789);

function* qsGenerator(){
    yield ['abc', 123];
    yield ['xyz', 456];
    yield ['aaa', 789];
}

const params1 = new URLSearchParams(qs);             // 普通のクエリストリング形式の文字列でも
const params2 = new URLSearchParams(qsObject);       // 普通のオブジェクトでも
const params3 = new URLSearchParams(qsIterable);     // イテレータでも
const params4 = new URLSearchParams(qsMap);          // Mapオブジェクトでも
const params5 = new URLSearchParams(qsGenerator());  // ジェネレータでも

console.log(params1);
console.log(params2);
console.log(params3);
console.log(params4);
console.log(params5);

// 結果
URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }

 

Object、配列、Mapオブジェクト、ジェネレータなど何でも食べる好き嫌いのない子という感じです。

作成されたURLSearchParamsオブジェクトは、色々なメソッドを持っています。

 

追加するappend

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

params.append('bbb', 963);
console.log(params.toString())

// 結果
// abc=123&xyz=456&aaa=789&bbb=963

 

 

削除するdelete

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

params.delete('bbb');
console.log(params.toString());

// 結果
// abc=123&xyz=456&aaa=789

 

 

イテレータを返すentries

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

for (let v of params.entries()) console.log(v);

// 結果
/*
[ 'abc', '123' ]
[ 'xyz', '456' ]
[ 'aaa', '789' ]
*/

 

 

全件ループのforEach

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

params.forEach((value, key, p) => {
    console.log(value, key, p);
})

// 結果
/*
123 abc URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
456 xyz URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
789 aaa URLSearchParams { 'abc' => '123', 'xyz' => '456', 'aaa' => '789' }
*/

 

 

引数のkeyのvalueを返すget

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

console.log(params.get('abc'));

// 結果
// 123

 

 

引数のkeyのvalueを全て返すgetAll

getと何が違うのかという話なので、サンプルを2つ作ってみました。

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

console.log(params.getAll('abc'))

// 結果
// [ '123' ]

 

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789&abc=777';
const params = new URLSearchParams(qs);

console.log(params2.getAll('abc'))

// 結果
// [ '123', '777' ]

 

URLSearchParamsオブジェクトは、キーの重複を許容するので、getAllメソッドが用意されています。

ちなみに、getメソッドの場合、重複したキーのうち、最初に登録されたものを返す仕様なので、getAllやループ内でしか値にアクセスできない事がありえます。

 

存在確認するhas

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

console.log(params.has('abc'));

// 結果
// true

 

 

キーのイテレータを返すkeys

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

for (let k of params.keys()) console.log(k);

// 結果
/*
abc
xyz
aaa
*/

 

 

上書きするset

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

params.set('vvv', 247);
console.log(params.toString());

// 結果
// abc=123&xyz=456&aaa=789&vvv=247

params.set('vvv', 247);
console.log(params.toString());

// 結果
// abc=123&xyz=456&aaa=789&vvv=247

 

対応するキーが存在しない場合は、append相当の動きをし、キーが存在する場合は、キーの内容を上書きします。
複数キーが存在している場合は、全て削除した後、appendするようです。

const qs = 'a=1&a=2&a=3';
const params = new URLSearchParams(qs);
console.log(params.toString());
// この時点では
// a=1&a=2&a=3
params.set('a', 4);
console.log(params.toString());
// 結果
// a=4

 

 

破壊的ソートのsort

オブジェクトの中身を名前順に正順ソートします。
逆順ソートはできないようです。

オブジェクトの順番を入れ替えたURLSearchParamsオブジェクトを返してくれるのではなく、実行したオブジェクトの順序を入れ替える点にご注意(順序が気になる事はあまりないですが)。

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

console.log(params.toString());
params.sort();
console.log(params.toString());

// 結果
// 実行前
// abc=123&xyz=456&aaa=789&vvv=247
// 実行後
// aaa=789&abc=123&vvv=247&xyz=456

 

 

値のイテレータを返すvalues

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

for (let v of params.values()) console.log(v);

// 結果
/*
789
123
247
456
*/

 

 

URLSearchParamsオブジェクト自体がイテラブル

const {URLSearchParams} = require('url');
const qs = 'abc=123&xyz=456&aaa=789';
const params = new URLSearchParams(qs);

for (const [k, v] of params) console.log(k, v);

// 結果
/*
aaa 789
abc 123
vvv 247
xyz 456
*/

 

 

まとめ

これからのURLパースで重要な位置を占めるであろう、WHATWG URL APIとURLSearchParamsについて、まとめてみましたが、おわかりいただけたでしょうか。
意外に面倒だったクエリストリング周りの実装が捗りそうですね。

 
以上です。

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
0
読み込み中...
0 票, 平均: 0.00 / 10
4,112
X facebook はてなブックマーク pocket
【2024.6.30 CentOS サポート終了】CentOS サーバー移行ソリューション

【2024.6.30 CentOS サポート終了】CentOS サーバー移行ソリューション

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始いたします!

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始いたします!

【大阪 / 横浜】インフラエンジニア・サーバーサイドエンジニア 積極採用中!

【大阪 / 横浜】インフラエンジニア・サーバーサイドエンジニア 積極採用中!

この記事をかいた人

About the author

萬代陽一

ソーシャルゲームのウェブ API などの開発がメイン業務ですが、ありがたいことにマーケティングなどいろんな仕事をさせてもらえています。
なおビヨンド内での私の肖像権は CC0 扱いになっています。