OSSTech株式会社

Apacheの REQUEST_URI で勘違いしていた話

2020-12-24 - 相本 智仁

Apacheの REQUEST_URI で勘違いしていた話

Apache設定時等に出てくる REQUEST_URI の値は QueryString が「含まれるケース」と「含まれないケース」があります。

例えば次のようにアクセスしたときに REQUEST_URI の値は「/cgi-bin/env.cgi?test=1」「/cgi-bin/env.cgi」どちらなのかというお話です。

  • http://www.example.co.jp/cgi-bin/env.cgi?test=1

具体的には次のとおりです。

QueryString が「含まれるケース」

  • php や CGI で使用可能な Apache がセットした 環境変数REQUEST_URI
    • 特に設定なし(デフォルト)の場合であり、Apache の設定により QueryString を含めなくすることも出来ます。

QueryString が「含まれないケース」

  • mod_rewrite で使用する %{REQUEST_URI}
  • mod_setenvif で使用する %{REQUEST_URI}
  • Ifディレクティブ などの expr で使用する %{REQUEST_URI}
  • mod_ssl の環境変数(例: mod_headers では %{REQUEST_URI}s と指定して使用)

調べた経緯

ある時「アクセスURL の拡張子が mp4 以外のときだけに特定の処理を行いたい」という要件がありました。 Ifディレクティブ を使って以下のように出来そうです。

<If "%{REQUEST_URI} !~ m!\.mp4$!i">
  [やりたい処理]
</IF>

私はこの設定の場合 QueryString が含まれたアクセスでは「Ifが真にならない」と思っていました。 しかし、実際に動作確認をしてみると QueryString が含まれたアクセスをしても If が真になり 要件を満たす動作になります。

php で 環境変数REQUEST_URI を表示する

一方で CGI や php で Apache がセットする 環境変数REQUEST_URI を表示すると QueryString が含まれます。

  • 例えば次の index.php を作成します。
<?php
print $_SERVER['REQUEST_URI'];
?>

このファイルに対して「http://localhost/index.php?a=b」とアクセスすれば「/index.php?a=b」と表示されます。 環境変数REQUEST_URI はアクセス URL の QueryString が含まれた値であることが分かります。

私はもともとこの「 環境変数REQUEST_URI にセットされる値は QueryString が含まれる」と頭にあり、 上記の Ifディレクティブ では「真にならない」と思っていました。 そして IFディレクティブ の動作から REQUEST_URI は使うケースによって値が違うことに気が付きました。 どのような仕様なのか Apache のソースコードで確認しました。1

php や CGI で使うREQUEST_URI

php や CGI に渡す 環境変数REQUEST_URI は Apache のどんな値が渡るのかを見てみます。

  • server/util_script.c
    381 AP_DECLARE(void) ap_add_cgi_vars(request_rec *r)
    382 {

    386     int request_uri_from_original = 1;

    394     if (conf->cgi_var_rules) {
    395         request_uri_rule = apr_hash_get(conf->cgi_var_rules, "REQUEST_URI",
    396                                         APR_HASH_KEY_STRING);
    397         if (request_uri_rule && !strcmp(request_uri_rule, "current-uri")) {
    398             request_uri_from_original = 0;
    399         }
    400     }
    401     apr_table_setn(e, "REQUEST_URI",
    402                    request_uri_from_original ? original_uri(r) : r->uri);

401行目が環境変数REQUEST_URI値をセットする処理です。 変数request_uri_from_original の値が真なら 関数original_uri(r) の結果が、偽なら r->uri がセットされます。 関数original_uri はリクエストラインの URI に該当する部分を返す関数です。従ってこれは QueryString が含まれた値 です。

一方 r->uri は、URI のパス部分のみの値です。QueryString が含まれていない値です。2

変数request_uri_from_original の値は、394行目から400行目の処理により決まりますがこれは設定ディレクティブ CGIVar の設定値3で決まります。 設定がない場合は 1 となりますので、環境変数REQUEST_URIは QueryString が含まれた値がセットされることがわかります。

次のように設定すると環境変数REQUEST_URIr->uri がセットされることになり、デフォルト動作から変わります。

CGIVar REQUEST_URI current-uri

expr で使う REQUEST_URI

Apache の設定の様々な箇所で使用可能な expr で REQUEST_URI を指定した場合 Apache のどの値が渡るのかを見てみます。

  • server/util_expr_eval.c
   1315 static const char *request_var_names[] = {
   1316     "REQUEST_METHOD",           /*  0 */
   1317     "REQUEST_SCHEME",           /*  1 */
   1318     "REQUEST_URI",              /*  2 */
   1319     "REQUEST_FILENAME",         /*  3 */
   1320     "REMOTE_HOST",              /*  4 */

   1349 static const char *request_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
   1350 {
   1351     int index = ((const char **)data - request_var_names);

   1356     switch (index) {
   1357     case 0:
   1358         return r->method;
   1359     case 1:
   1360         return ap_http_scheme(r);
   1361     case 2:
   1362         return r->uri;

REQUEST_URI は 2 で、r->uri の値が使われています。

SetEnvif で使う REQUEST_URI

SetEnvifディレクティブ で REQUEST_URI を指定した場合 Apache のどの値が渡るのかを見てみます。

  • server/util_expr_eval.c
    364         else if (!strcasecmp(fname, "request_uri")) {
    365             new->special_type = SPECIAL_REQUEST_URI;
    366         }

    536                 case SPECIAL_REQUEST_URI:
    537                     val = r->uri;
    538                     break;

設定で request_uri (大文字小文字区別なし)が指定されると、special_typeSPECIAL_REQUEST_URI となり SPECIAL_REQUEST_URI は、 r->uri の値が使われています。

mod_rewrite で使う REQUEST_URI

mod_rewrite で REQUEST_URI を指定した場合 Apache のどの値が渡るのかを見てみます。

  • modules/mappers/mod_rewrite.c
   2114             case 'U':
   2115                 if (!strcmp(var, "REQUEST_URI")) {
   2116                     result = r->uri;
   2117                 }
   2118                 break;
   2119             }

r->uri の値が使われます。

まとめ

ソースコードを見ると 環境変数REQUEST_URI にセットする時だけ、違う値がセットされることがわかります。 そしてそれも設定により r->uri にすることが出来ることもわかりました。

一つの設定箇所でこう動いたからといって、他の箇所でも同じように動くとは限らない、 思い込みに注意しなければと思った出来事でした。


  1. CentOS8のパッケージのバージョン 2.4.37-21.module_el8.2.0+494+1df74eae のソースコードで確認しました。 ↩︎

  2. Apacheがパースした値です。厳密には QueryString が含まれない以外にもパース処理で実際の リクエストURI とは差異が出ることはあります。 詳細はソースコードのserver/protocol.cap_parse_uriを参照ください。 ↩︎

  3. CGIVar ディレクティブは 2.4.21 以降のバージョン で使用可能です。 ↩︎