NodeJS 速習チュートリアル

Node.js URL モジュール

1. ビルトイン URL モジュール

URL モジュールは、URL の解決(Resolution)やパース(Parsing)を行うためのユーティリティを提供します。
これを使用することで、Web アドレスを読み取り可能な各パーツに分割したり、URL を新しく構築したり、さまざまな URL コンポーネントを操作したりすることが可能になります。

2. はじめに

URL モジュールを組み込むには、require() メソッドを使用します。
モダンな Node.js(v10.0.0+)では、レガシー API またはより新しい URL クラス(WHATWG URL API) のいずれかを使用できます。

2.1 モジュールのインポート

// レガシー API を使用する場合
const url = require('url');

// モダンな URL クラス(WHATWG API)を使用する場合
const { URL } = require('url');

url.parse() メソッドを使用してアドレスをパースすると、アドレスの各パーツがプロパティとして格納された URL オブジェクトが返されます。

例:Web アドレスを読み取り可能なパーツに分割する

let url = require('url');
let adr = 'http://localhost:8080/default.htm?year=2017&month=february';
let q = url.parse(adr, true);

console.log(q.host);     // 'localhost:8080'
console.log(q.pathname); // '/default.htm'
console.log(q.search);   // '?year=2017&month=february'

let qdata = q.query;     // パースされたクエリ情報
console.log(qdata.month); // 'february'

3. URL のパースとフォーマット

3.1 URL オブジェクトのプロパティ

URL をパースすると、以下のプロパティを持つ URL オブジェクトが得られます:

  • href: パースされたフル URL
  • protocol: プロトコルスキーム(例: 'http:')
  • host: ポート番号を含むフルホスト部分(例: 'example.com:8080')
  • hostname: ホスト名部分(例: 'example.com')
  • port: 指定されている場合のポート番号
  • pathname: URL のパスセクション
  • search: 先頭の ? を含むクエリ文字列
  • query: ? なしのクエリ文字列、またはパース済みのクエリオブジェクト
  • hash: 先頭の # を含むフラグメント識別子

4. レガシー API vs WHATWG URL API

Node.js では現在、新しいコードには WHATWG URL API の使用が推奨されています。

例:両 API の比較

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

// WHATWG URL API を使用(新規コードに推奨)
const myURL = new URL('https://example.org:8080/p/a/t/h?query=string#hash');
console.log(myURL.hostname); // 'example.org'
console.log(myURL.pathname); // '/p/a/t/h'
console.log(myURL.searchParams.get('query')); // 'string'

// レガシー API を使用
const parsedUrl = require('url').parse('https://example.org:8080/p/a/t/h?query=string#hash');
console.log(parsedUrl.host); // 'example.org:8080'
console.log(parsedUrl.query); // 'query=string'

5. URLSearchParams API

URLSearchParams API は、URL のクエリ文字列を操作するための便利なメソッドを提供します。

例:クエリパラメータの操作

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

const myURL = new URL('https://example.com/?name=Kai&age=30');
const params = new URLSearchParams(myURL.search);

// パラメータを取得
console.log(params.get('name')); // 'Kai'

// パラメータを追加
params.append('city', 'Stavanger');

// パラメータを削除
params.delete('age');

// 文字列に変換
console.log(params.toString()); // 'name=Kai&city=Stavanger'

6. Node.js ファイルサーバーの実装

クエリ文字列のパース方法がわかったところで、以前学んだ HTTP モジュールFile System モジュールを組み合わせて、クライアントからのリクエストに応じてファイルを返すファイルサーバーを構築してみましょう。

まず、Node.js ファイルと同じフォルダに 2 つの HTML ファイルを作成します。

summer.html

<!DOCTYPE html>
<html>
<body>
<h1>夏 (Summer)</h1>
<p>太陽が大好きです!</p>
</body>
</html>

winter.html

<!DOCTYPE html>
<html>
<body>
<h1>冬 (Winter)</h1>
<p>雪が大好きです!</p>
</body>
</html>

次に、リクエストされたファイルを開き、その内容をクライアントに返す Node.js ファイルを作成します。エラーが発生した場合は 404 エラーを返します。

demo_fileserver.js:

let http = require('http');
let url = require('url');
let fs = require('fs');

http.createServer(function (req, res) {
  // リクエスト URL をパース
  let q = url.parse(req.url, true);
  // パス名からファイルパスを構築
  let filename = "." + q.pathname;

  // ファイルを読み込む
  fs.readFile(filename, function(err, data) {
    if (err) {
      // ファイルが見つからない場合などは 404 を返す
      res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
      return res.end("404 Not Found (ファイルが見つかりません)");
    }
 
    // 成功した場合は 200 とデータを出力
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
    res.write(data);
    return res.end();
  });
}).listen(8080);

ファイルを起動します:

C:\Users\Your Name>node demo_fileserver.js

ブラウザで以下のアドレスを開くと、それぞれ異なる結果が表示されます:

  1. http://localhost:8080/summer.html -> Summer のページを表示
  2. http://localhost:8080/winter.html -> Winter のページを表示

7. ベストプラクティス

7.1 URL のバリデーションとサニタイズ

入力された文字列が有効な URL かどうかを常に確認してください。

function isValidHttpUrl(string) {
  try {
    const url = new URL(string);
    return url.protocol === 'http:' || url.protocol === 'https:';
  } catch (err) {
    return false;
  }
}

console.log(isValidHttpUrl('https://example.com')); // true
console.log(isValidHttpUrl('ftp://example.com'));   // false

7.2 安全な URL の構築

文字列連結ではなく、URL クラスを使用して安全にパスを構築しましょう。

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

// URL を安全に構築するメソッド
function createProfileUrl(domain, username) {
  // encodeURIComponent を使用して不正な文字をエスケープ
  return new URL(`/users/${encodeURIComponent(username)}`, domain).href;
}

console.log(createProfileUrl('https://example.com', 'johndoe'));
// 'https://example.com/users/johndoe'

7.3 クエリパラメータの高度な処理

searchParams を使用すると、既存の URL に対して簡単にパラメータを追加・変更できます。

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

const url = new URL('https://example.com/search?q=node.js&lang=en');

// すべてのパラメータを文字列として取得
console.log(url.searchParams.toString()); // 'q=node.js&lang=en'

// 特定のパラメータを取得
console.log(url.searchParams.get('q')); // 'node.js'

// パラメータが存在するか確認
console.log(url.searchParams.has('lang')); // true

// 新しいパラメータを追加
url.searchParams.append('page', '2');
console.log(url.href); 
// 'https://example.com/search?q=node.js&lang=en&page=2'