URL の結合を完全に理解する
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 の各部分は以下のように分割できます。
これが 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を考えてみます。
-
cccと..が打ち消しあう。このとき、?x=yは消える、
(https://example.com/aaa/bbb+../../../../xxx) -
bbbと..が打ち消しあう。
(https://example.com/aaa+../../../xxx) -
aaaと..が打ち消しあう。
(https://example.com/+../../xxx) -
..は 空のpathnameについて意味がないので、先頭の..が消える
(https://example.com/+xxx) -
https://example.com/xxxになる
みたいなイメージです。
結合先を/で分割したものに.が含まれる場合
- 変化なし:
protocol,username,password,hostname,port - 変化あり:
pathname,search
これは無視されます。
例えば、結合先が./a/b/cの場合はa/b/cと等価になります。
結合先が通常の文字列
- 変化なし:
protocol,username,password,hostname,port - 変化あり:
pathname,search
これの挙動を完全に理解していませんでした。
結合元を/で分割した最後の部分以降を、結合先で置き換えます。
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 と挙動が違うので、気をつけたほうがいいです。
Discussion