私はクエリ文字列を禁止した
(chrismorgan.info)- Chris Morganは自身のサイトで無断のクエリ文字列を全面的に遮断することにしており、現在の実装はCaddyfileに入っている
?ref=example.comのような追跡用パラメータをURLに付けられたくなく、必要なら Referer ヘッダーを見ればよいと考えている?utm_source=example&utm_*&c.*のような UTM parameters はサイト所有者が使うためのものであり、外部が付けるものではないと考えている- 現在このサイトではクエリ文字列をまったく使っておらず、今後使うようになった場合は 既知のパラメータ だけを許可する予定
- 最終URLは
/no-query-stringsに決めており、/%3FはCaddyのtry_filesリライトで問題があるため選ばなかった
無断のクエリ文字列の遮断
- Chris Morganは自身のサイトで無断のクエリ文字列を全面的に遮断することにした
?ref=example.comのような追跡用パラメータを自分のURLに付けられたくなく、必要ならRefererヘッダーを見ればよいと考えている?utm_source=example&utm_*&c.*のような UTM parameters はサイト所有者が使うためのものであり、外部が付けるものではないと考えている- 現在このサイトではクエリ文字列をまったく使っておらず、今後使うようになった場合は 既知のパラメータ だけを許可する予定
- 過去にはスタイルシートURLに
?t=…,?h=…形式のキャッシュ無効化URLを使っていたが、そのようなリクエストが壊れても構わないと判断している - この遮断は現在Caddyfileに実装されている
URLの選定過程
-
/?を使おうとしていた計画- 最初はこのページを
https://chrismorgan.info/?に公開したい誘惑が強かった - 空のパスと空のクエリという形なので、よくあるが誤った前提を数多く崩し、一部のツールを困らせる可能性があった
curlはコマンドラインで末尾の疑問符を不当に削除してしまうように見え、ライブラリ使用はテストしていない- 結局、パスという概念を尊重し、人々に寛容であることにし、特にCaddyをすでに十分厄介な方向へ押し進めていると考えた
- 最初はこのページを
-
/%3Fを使おうとしていた計画- 次の計画は、パスが
?でクエリはない/%3Fに公開することだった - しかしCaddyで
try_filesリライト(rewriting)が関わると処理できない問題があった - 関連するIssueは
try_filesが?や%のような文字を含むパスを壊す という内容
- 次の計画は、パスが
-
最終的な選択
- 最終URLは
/no-query-stringsに決めた /?または/%3Fは、将来クエリ文字列に関する別の用途で使う可能性がある
- 最終URLは
1件のコメント
Hacker News のコメント
気になったので HTML と URL の W3C 標準を見直してみたが、意外にもクエリ文字列の形式は パーセントエンコーディング 以外には特に定義されていなかった。
クエリ文字列を “form-urlencoded”[0] のクエリ文字列と混同することはあり得るが、これは相互運用可能な形式の一つにすぎない。一般にクエリ文字列とは URL の
?の後に来る任意のパーセントエンコード文字列[1]であり、応答生成に利用できる HTMLURLオブジェクトの別の属性でもある。URLSearchParamsオブジェクトは form-urlencoded パーサでクエリ文字列を解析した結果だが、これは JavaScript のための相互運用レイヤーにすぎない。正直、標準を見る前は反対意見を言うつもりだったが、標準はかなり明確だった。想定外のクエリ文字列に 404 で応答するのも妥当かもしれない。クエリ文字列はパスと同じくらい URL API の一部であり、パスに任意の文字列を付け足すのが望ましくなく未定義の動作だという点には、多くの人が同意できるだろう。
[0]: https://url.spec.whatwg.org/#application/x-www-form-urlencod...
[1]: https://url.spec.whatwg.org/#url-class
index.phpだけを置いて、ルーティングをすべて クエリ文字列 で処理するのがかなり一般的だった。もちろん form-urlencoded 形式だったし、みんなそこまで野蛮ではなかった。だから
index.php?p=home、index.php?p=shop、あるいはindex.php?action=showthread&forum=42&thread=17976のような URL になっていた。こうした構造では、不明なクエリパラメータに 404 を返すのが正しい応答だとはっきり分かる。実際、今でも多くのサイトはそう動いていて、SEO のために Apache/nginx の書き換えルールの背後に隠しているだけだ。
結局のところ URL は、サーバーがどう処理するかを決める 文字列 にすぎない。
この議論で本当に面白いのは、404 を返したときの副作用を心配しながら、Web の歴史の中でパスがどれほど長く無意味だったかを完全に忘れていたことだ。今ではパスが勝った。もう
/item?id=…のような URL で新規に始めることはほとんどない。いいことだ!「リクエストを修正して再試行せよ」と読めるし、自分の提供する API でもそうしている。406 よりこちらを好むのは、こちら側で処理不能な問題ではないからだ。クエリ文字列に何かを付け足して壊そうとした、あるいは文書どおりにリクエストを作らなかったのなら、それはリクエスト側の責任だ。
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/...
たとえばキャッシュの観点では、
url?a=b&c=dはurl?c=d&a=bと一致すると見なせる。正式な標準として文書化されたことはなくても、従わなければ広範に壊れる慣習は山ほどあるし、文字どおり従うと愚かに見える「標準」も多い。
元の記事の場合、困るのはそのサイトを訪れようとする人だけで、おそらくブラウザの戻るボタンを押して用事を続けるだろう。その程度の不利益を受け入れるかどうかは本人が決めればいい。ただし、どの標準でも禁止されていないからといって定義上許されるわけではなく、逆に標準が禁止しているからといって急に許されなくなるわけでもない。
理解した限りでは、ほかの Web サイトが著者のサイトへのリンクに
?ref=origin.comのような クエリ文字列 を付けることに苛立っているようだ。それが元のサイトにどんな利益をもたらし、著者のサイトにどんな害を与えるのかは分からない。
両者の振る舞いとも完全に混乱しているように見える。
広告キャンペーンを運用するときに Google が UTM クエリ文字列を付けて、どのキャンペーンからユーザーが来たのか追跡したいのは理解できる。その場合は送り手と受け手が協力関係にある。だがここでは、送り手が何の理由もなく何かを付け加えている。なぜだろう?
refクエリ文字列からxyz.comからかなりのトラフィックが来ていると見て、そのサイトに広告を出したり提携したりできるかもしれない、という流れになる。正直、ニッチ/スタートアップ系サイトではかなり有用だ。Web 分析でこうした値を見て始まった対話の両側を経験したことがあり、一度は流入トラフィックを見て自分から連絡し、別の一度は自分がリンクしたサイトから連絡を受けた。どちらも相互に利益のあるパートナーシップに終わった。
プライバシー上の論点もある程度は理解できるが、標準の
Refererヘッダー以上の情報を与えるわけではない。Simple Analytics や Plausible のような分析ツールを使うと、ずっと目立って見えるだけだ。クエリ文字列の追加はしばしば追跡に使われる。Firefox の “copy clean link” や、一部の UTM パラメータを事前に除去する Enhanced Tracking Protection のような機能が存在すること自体、多くの人がこれを望んでいない証拠だ。
ある種のサイトは、私が軽く「追跡経済」と呼ぶ仕組みに喜んで参加している。受信側がログを見て、多くの人がそのサイトから来ていると分かり、自分にとって有益な行動を取れるからだ。
クエリ文字列を拒否するのは、その仕組みに対するシンプルな抗議だ。
「Web サイト訪問者が、独立した個人 Web サイト運営者コミュニティが推薦する興味深い Web サイトやページを探索できるようにする、小さく分散型でセルフホスト可能な Web コンソール」という説明を見ると、昔はこういうものを Webring と呼んでいた。ただ、そこまで華やかではなかった。
オープンソースのアプリケーションフレームワークを開発していたときに直面した問題の一つは、FastCGI を使うホスティングが
Authヘッダーを尊重しないため、トークンをクエリで渡すしかなかったことだ。Web アドレスをコピー&ペーストするときにトークンが含まれてしまうことが多く、本当にひどかった。今では直っているかもしれない。自分で制御でき、全員に公開する必要のないバックエンドではヘッダーを使う。
Authヘッダーを壊していた、という意味だろうか?この部分をもう少し詳しく説明してもらえるとありがたい。技術的には、
PARAMレコードが期待した値を実際には渡していない、という話に聞こえる。「だからこのサイトでは包括的な禁止を試すことにした。承認されていないクエリ文字列は禁止だ」と言っているが、そのサイトはリクエストにクエリ文字列が含まれると 414 を返しているようで、私には誤った選択に思える。
この抗議がユーザーを擁護するためのものなら、そもそもその文字列を制御できなかったはずのユーザーをなぜ罰するのだろうか?
むしろ、ブラウザツールなどを通じてユーザー自身がこの判断を下せる方法を示すシグナルとして使う方がよいのではないか?
400 Bad Request、一般的なクライアントエラーコードとしては適切だが面白くない。
402 Payment Required、正直なところ、クエリ文字列付きの特定の URL を動くようにしてほしいと金を払うなら検討の余地はある。
404 Not Found、ただし副作用が多く生じやすく、私の意図する『リクエストの形式が間違っている』という感じを伝えられない。
Locationヘッダーなしの 303 See Other。今では極めて珍しいが正当だ。少なくとも RFC 2616 ではそうだった("The different URI SHOULD be given by the Location field in the response")。だが 7231 と 9110 では、Locationヘッダーの存在を前提とする形に変わった("… as indicated by a URI in the Location header field")。一方で 301、302、307、308 は "the server SHOULD generate a Location header field" となっている。いずれにせよ、Locationヘッダーなしの See Other も十分ありだと思う。でも URI Too Long の方が面白かった」https://chrismorgan.info/no-query-strings?foo
「私が 414 URI Too Long を誤用していると言うことはできるだろう。私の答えは、こちらの方がより面白い、というものだ。検討した他の選択肢は…」というくだりでは、別の候補として 418 I'm a teapot も考えられる。ティーポットも通常はクエリ文字列をサポートしないからだ。
適切に見えて、詳しく見るとそうでもない選択肢もいくつかある。406 “Not Acceptable” はコンテンツネゴシエーションヘッダーに基づくものだし、409 “Conflict” は主に WebDAV リクエスト向けで、411、422、431 などもここには関係のない特定条件用だ。
300 番台や 500 番台のエラーも不適切だ。これはリダイレクトでもサーバー側障害でもなく、クライアント側のリクエストの問題だからだ。
ティーポットか、長すぎる URI が最有力候補に思える。
この記事と Chris の記事の文体を見ると、こうしたクエリパラメータを含めることが有害であるかのように感じられるが、どう有害なのか分からない。
一部の URL を壊し得ることは理解できるし、それだけでもやめる理由としては十分だ。それでも些細な不便に見える。誰か説明してくれないだろうか?
技術的純粋主義の観点では、慣習として受け入れられていても、URL を改変するのは技術的に不正確だ。URL は基本的に 不透明な値 として扱うべきだ。
社会的観点では追跡であり、その点は兄弟コメントスレッドでうまく説明されているので繰り返さない。
ノイズの観点では、ユーザーが気にすべき部分を隠し、URL を過度に難解かつ複雑にして、一般の人々が URL に関心を持たなくなる一因になっている。
Refererヘッダーに関する問題を読むと、人々がなぜ嫌うのか分かる。 https://en.wikipedia.org/wiki/HTTP_refererあるサイトに到達する前にどこにいたかを、そのサイトに知られたくない理由は多い。要するに、訪問先のサイトに 閲覧履歴 を共有しているようなものだ。
そのため HTTP
Refererヘッダーには、送信条件の制限や機能全体を無効化できる仕組みなど、多くの更新が入ってきた。同じ情報を URL パラメータとして追加すると、こうした既存ルールや拒否機能を回避してしまう。素直に標準を使うべきだ。
ひどく極端な態度で、これがどうしてより良い Web につながるのか十分に説明できていない。
後者は理解しにくいかもしれないが、私の場合、ログにユーザーを害し得る情報が残ることを絶対に望まない。
個人的には、リンクをコピーしてメッセージで送ろうとしたときに、元の URL の倍もある追跡コードが付いていると本当にうんざりする。いちいち消さなければならないし、そうでなければ画面いっぱいのランダムな文字列を受け取った相手が、いったい何なのかと戸惑うことになる。
ユーザープライバシーを侵害し、ユーザー体験も悪く、何より誰も頼んでいない。
元記事のソースはまだ HN で議論されたことがなかったので、そのリンク(https://chrismorgan.info/no-query-strings)を一番上に置き、返信記事へのリンク(https://susam.net/no-query-strings.html)は上部の説明に移した。
どちらも良いが、元記事を優先する方が公平に思える。
このあたりでまだ GET クエリを使っているサイトの大半は、地方自治体が運営する 税金徴収サイト で、ログイン後に変数をあちこち受け渡している。
実際には GET リクエストと同じことをしているのに、本物の URL のふりをするルーティングパーサの方がずっと腹立たしい。
クエリ文字列は有用だ。ファイル検索や、ほかの種類の動的ファイルなどがそうだが、クエリ文字列を想定していない URL に付けるべきではない。
だから UTM などが追加されたリクエストを拒否するのは正しいと思う。
クエリ文字列が想定されていないのに存在するなら、応答としては 404 が最もしっくりくる気がするし、400 でも適切かもしれない。