🔗

URL の結合を完全に理解する

2024/07/20に公開

URL の結合をよく理解していなかったので、それに対する備忘録です。

例えば

<a href="/top">Top</a>

みたいな HTML の anchor タグを使う時、現在の URL と/top を結合した URL に遷移するわけです。

これは、JavaScript の API にもあり、

const merged = new URL('/top', 'https://example.com/profile') // 'https://example.com/top'

のようにして結合することもできます。

この結合は Deno などの JavaScript ランタイムでファイルを参照する時にも使うのですが、 path.join などとも挙動が違うのでまとめたいと思います。

URL の構造

JavaScript の URL API によって、URL の各部分は以下のように分割できます。
Untitled 16
これが URL の構造です。
この前提条件を踏まえて説明していきます。

また、new URL(next, base) のようにして、next結合先base結合元 と定義していきます。

結合先の最初が /

  • 変化なし: protocol, username, password, hostname, port
  • 変化あり: pathname, search

結合先が/pageのような最初が/で始まる場合です。
そのとき、結合元のpathname以降が結合先で置き換わります。

例:

  • https://example.com + /page = https://example.com/page
  • http://example.com + /page = http://example.com/page
  • https://example.com/xxx + /page = https://example.com/page
  • https://example.com/xxx?x=y + /page = https://example.com/page
  • https://example.com + /page?x=y = https://example.com/page?x=y

結合先を/で分割したものに..が含まれる場合

  • 変化なし: protocol, username, password, hostname, port
  • 変化あり: pathname, search

これは、パスを一層上に上がるイメージです。pathname 以降のみを変更します。

例えば、https://example.com/aaa/bbb/ccc?x=y + ../../../../../xxxを考えてみます。

  1. ccc.. が打ち消しあう。このとき、?x=yは消える、
    (https://example.com/aaa/bbb + ../../../../xxx)
  2. bbb.. が打ち消しあう。
    (https://example.com/aaa + ../../../xxx)
  3. aaa.. が打ち消しあう。
    (https://example.com/ + ../../xxx)
  4. .. は 空の pathname について意味がないので、先頭の..が消える
    (https://example.com/ + xxx)
  5. https://example.com/xxx になる

みたいなイメージです。

結合先を/で分割したものに.が含まれる場合

  • 変化なし: protocol, username, password, hostname, port
  • 変化あり: pathname, search

これは無視されます。
例えば、結合先が./a/b/cの場合はa/b/cと等価になります。

結合先が通常の文字列

  • 変化なし: protocol, username, password, hostname, port
  • 変化あり: pathname, search

これの挙動を完全に理解していませんでした。

結合元を/で分割した最後の部分以降を、結合先で置き換えます。

Untitled 17

POSIX システムとは少し違うので注意です。

また、pathnameが何もないか、/のみの場合、https://example.com/ + aaa/bbb = https://example.com/aaa/bbb のようになります。

結合先が//で始まる

  • 変化なし: protocol
  • 変化あり: username, password, hostname, port, pathname, search

これは、プロトコルはそのままでそれ以外を結合先で置き換えます。

例:

  • https://example.com/path + //example.net/ = https://example.net/
  • http://example.com/path + //example.net/ = http://example.net/

同じプロトコルで <script> を使いたい時などに使えます。

結合先が完全な URL である

  • 変化なし: なし
  • 変化あり: protocol, username, password, hostname, port, pathname, search

この場合、結合元の全てを結合先で置き換えます。

例:

  • https://example.com/path + http://example.net/ = http://example.net/

まとめ

ファイルパスの操作などで URL API を使ったりする時は、path.join と挙動が違うので、気をつけたほうがいいです。

GitHubで編集を提案

Discussion