静的サイトジェネレーターを使うような時にたまに苦労する話。仕様的な厳密さを欠いた用語で話します。
結論としては、「静的HTMLファイルを生成する時に、相対パスでリンクを作るのは結構難しいので、ライブラリーを使いましょう」ということ。
絶対URIと相対URI
例えば https://blogs.kitaitimakoto.net/~/Apehuci/article1
という記事を見ているとしよう。この全体をURIと言い、 /~/Apehuci/article1
という部分をパスと言う。
この記事から、別の https://blogs.kitaitimakoto.net/~/Announcement/article2
という記事へリンクを張る時、
<a href="https://blogs.kitaitimakoto.net/~/Announcement/article2">article2</a>
という風に書くことができる。こういう書き方のURIを「絶対URI」と言う。定義で言うと「スキーム( https
の部分)が書かれているURI」が絶対URIだ。
また、こういう書き方もできる。
<a href="/~/Announcement/article2">article2</a>
<a href="../Announcement/article2">article2</a>
こういう、スキームがないURIを「相対URI」と言う。
https://blogs.kitaitimakoto.net/~/Announcement/article2
は絶対URI(スキームがある)/~/Announcement/article2
は 相対 URI(スキームがない。スラッシュで始まっていても相対)../Announcement/article2
は相対URI
絶対パスと相対パス
ところで、コンピューター上のファイルの場所を表す際に「パス」という物を使う。
/var/www/html
とか ~/bin
とか ../another-dir/file
などだ。ここにも「絶対」と「相対」がある。
「/
」から始まるパスが「絶対パス」で、
/var/www/html
/etc/password
といった書き方。この時、「/
」一字であらわされる場所を「ルート(root)」と言う。
一方で「/
」で始まらないのが「相対パス」で、
./cmd
../dir/file
../../dir/subdir
などと書く。
ファイルではなくURIでも「パス」という言い方があり、スキームとホスト(ドメイン)部分の後の部分を指す。絶対URIの場合は必ず「/
」から始まる。
/~/Apehuci/article1
みたいなのがURIでのパス。また ../../Announcement/article2
みたいなのもある。ここで、「/
」で始まるURIを「ルート相対URI」と呼ぼう。ファイルシステムで「/
」をルートと呼ぶのに対比させた言い方だ。
URIとファイルパスの違う所
さてここでお立ち会い。
- 現在のページ:
/~/Apehuci/article1
- リンクURI:
./article3
- リンク先ルート相対URI:
/~/Apehuci/article3
これはいいね? リンク先URIの「./
」を外しても同じだ。
- 現在のページ:
/~/Apehuci/article1
- リンクURI:
article4
- リンク先ルート相対URI:
/~/Apehuci/article4
ではこれはどうだろう。
- 現在のページ:
/~/Apehuci/article1
- リンクURI:
../Announcement/article2
- リンク先ルート相対URI:
/~/Announcement/article2
たぶん、大丈夫だと思う。
今度は?
- 現在のページ:
/~/Apehuci/article1/
- リンクURI:
../Announcement/article2
- リンク先ルート相対URI:
/~/Apehuci/Announcement/article2
Apehuci
と Announcement
の両方がURIに含まれている。「現在のページ」の最後の「/
」の有無で、リンク先に違いが生じている。 /~/Announcement/article2
にリンクさせたい場合は、リンクURIを ../../Announcement/article2
としなくてはいけない。
これがURIでルート相対でないパスを使う際のややこしさだ。
- ファイルシステムを使う際には、「
/
」で終わるパスという物は存在しない(少なくとも今みたいなリンクを考える場合は) - しかしURIでは「
/~/Apehuci/article1/
」のように「/
」で終わるパスも存在する - それは、ルート相対ではない相対パスを扱う際に「
/~/Apehuci/article1
」とは違う働きをする - 世の中には「
/~/Apehuci/article1/
」と「/~/Apehuci/article1
」とで同じ内容を配信するサーバー(ウェブアプリケーション)が多数存在する
静的HTMLファイル内で機械的にリンク先URIを相対パスにしようとした時に、この組み合わせがややこしさを生む。
静的HTMLファイルでのリンクURIの計算
./site
というディレクトリーにファイルを入れて、ウェブサイトを作っているとしよう。./site/page1.html
というファイルが /page1.html
というウェブページとして表示される。で、 ./site/index.html
のようにindex.html
はそのファイル名を省略できる。この場合は /
というURIで index.html
の内容が表示されるし、サブディレクトリーも同様で ./site/category1/index.html
は /category1/
だけでアクセスできる。よくある想定だと思う。
/category1/item1/
というページから/category1/item2/
というページにリンクを張りたい時は、./site/category1/item1/index.html
というファイルに../item2/
と書く。
/category1/item1/
+ ../item2/
-> /category1/item2/
さて、世の中のサーバーには/category1/item1/
と/category1/item1
(最後の「/
」が無い)とで同じ内容を表示する物も多い。あなたが/category1/item1
を見ている時に../item2/
というリンクを踏んだらどうなるだろう?
/category1/item1
+ ../item2/
-> /item2/
これは意図しないページへのリンクとなってしまっている。
プログラムでファイルを一括処理して相対パスのリンクURIを作ろうとしている時に、ファイルのパス用のライブラリーを使うとこういうことが起こりやすい。ファイル(ディレクトリー)としては ./site/category1/item1
も./site/category1/item1/
も同じだからだ。
Pathname("./site/category1/item2").relative_path_from("./site/category1/item1/")
# => #<Pathname:../item2>
Pathname("./site/category1/item2").relative_path_from("./site/category1/item1")
# => #<Pathname:../item2>
このことを気にせず何となくファイルパス処理用のライブラリー(ここではRubyのPathname)を使っていると、意図しないリンクが出来上がることになる。
- 必ず
item1
方式かitem1/
方式かを統一した上で、 - URIとしての相対パス計算を行う
という必要がある。
item1= Addressable::URI.parse("http://example.net/category1/item1")
item1.path = item1.path + "/" unless item1.path.end_with? "/"
item2 = Addressable::URI.parse("http://example.net/category1/item2/")
item2.route_from(item1)
# => #<Addressable::URI:0x280 URI:../item2/>
これは最後に「/
」を使うことにした場合だけど、使わないことにした場合もAddressableは計算できる。
item1= Addressable::URI.parse("http://example.net/category1/item1/")
item1.path = item1.path[0..-2] if item1.path.end_with? "/"
item2 = Addressable::URI.parse("http://example.net/category1/item2")
item2.route_from(item1)
# => #<Addressable::URI:0x2e4 URI:item2>
このややこしい計算を自分で実装するのはよした方がいいやね……。
Comments
No comments yet. Be the first to react!