Apacheのmod_proxyのコードを読んでみる(3)


# はじめに

前回の記事：[Apacheのmod_proxyのコードを読んでみる(2)](https://qiita.com/tomluck/items/5483f28a73875f6c0454)

前回はmod_proxyの概要についての記事を書きました。

今回はmod_proxyの実装の詳細として、各handler関数(register_hooksで登録される各hook関数の第一引数)の理解内容を記事にします。

呼び出されるフェーズ順に説明を記載します。

これまでと同様に、我流で読み進めているので、理解内容に間違いなどありましたらご指摘ください。

# リクエスト実行の前処理のhandler

## child_init

ap_hook_child_initのhandlerです。
構造体メンバの初期化ぐらいしかしていません。

## proxy_detect

ap_hook_post_read_requestのhandlerです。

URIを見てproxy対象かを判断します。

- virtual hostへのアクセス
- HTTPメソッドがCONNECTの場合

上記の場合にrequest_rec構造体に値を設定します。

```c
            r->proxyreq = PROXYREQ_PROXY;
            r->uri = r->unparsed_uri;
            r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
            r->handler = "proxy-server";
```

handlerやfilenameの値は、handler呼び出し時に処理対象か判定するためにも使われます。

## proxy_pre_config

ap_hook_pre_configのhandlerです。
ここでmod_proxy_balancerで使われるworkerのカウントをリセットしています。

## proxy_post_config

ap_hook_post_configのhandlerです。
SSLなどの設定内容を変数に格納しています。

```c
    proxy_ssl_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable);
    proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
    proxy_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
    proxy_ssl_val = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
    ap_proxy_strmatch_path = apr_strmatch_precompile(pconf, "path=", 0);
    ap_proxy_strmatch_domain = apr_strmatch_precompile(pconf, "domain=", 0);
```

## proxy_trans

ap_hook_translage_nameのhandlerです。
条件を満たした場合にfilenameに”proxy:”というprefixを付けたURLを設定します。

```c
    if (r->proxyreq) {
        /* someone has already set up the proxy, it was possibly ourselves
         * in proxy_detect
         */
        return OK;
    }
```

コメントの通りですが、すでに別のhandlerなどでproxyreqにNONE以外が設定されていたら、proxy対象として検知できているため、処理正常終了(OK)します。

ここでreturnしている場合はおそらくforward proxyと判定されていると思います。

次の条件文はDECLINEDを返すので説明を省略し、その次の条件文ではreverse proxyの判定を行っています。

```c
    dconf = ap_get_module_config(r->per_dir_config, &proxy_module);

    /* short way - this location is reverse proxied? */
    if (dconf->alias) {
        int rv = ap_proxy_trans_match(r, dconf->alias, dconf);
        if (DONE != rv) {
            return rv;
        }
    }

    conf = (proxy_server_conf *) ap_get_module_config(r->server->module_config,
                                                      &proxy_module);

    /* long way - walk the list of aliases, find a match */
    if (conf->aliases->nelts) {
        ent = (struct proxy_alias *) conf->aliases->elts;
        for (i = 0; i < conf->aliases->nelts; i++) {
            int rv = ap_proxy_trans_match(r, &ent[i], dconf);
            if (DONE != rv) {
                return rv;
            }
        }
    }
```

ディレクトリごとの設定(dconf変数)やサーバごとの設定(conf変数)の値を基にap_proxy_trans_match関数を呼び出し、エラーリターンもしくはreverse proxyと判定された時、DONE以外が返ってきて、returnします。

## proxy_map_location(あまり読めていないです)

ap_hook_map_to_storageのhandlerです。

私のコードリーディングの目的にはあまり関係ないと思い、あまりよく読めていないので説明を省略します。

## proxy_fixup

ap_hook_fixupsのhandlerです。

リクエストの最終準備フェーズに該当し、リクエスト構造体の最終調整を行っています。

mod_proxyで処理すべきリクエストであった場合、mod_proxyの後段モジュール(mod_proxy_wstunnelなど)のcanonicalize用関数(proxy_hook_canon_handler)をフックし処理を委譲します。

```c
    /* canonicalise each specific scheme */
    if ((access_status = proxy_run_canon_handler(r, url))) {
        return access_status;
    }
```

# リクエスト実行のhandler

## proxy_handler

ap_hook_handlerのhandlerです。

### do-while文よりも前

次のような処理を行っていますが、詳細な説明は省略します。

- 処理対象の判別
- Proxyのリクエストの有無チェック
- NoProxyのホストのチェック
- エラーチェック

### do-while文の処理内容

```c
        /* Try to obtain the most suitable worker */
        access_status = ap_proxy_pre_request(&worker, &balancer, r, conf, &url);
```

do-while文の序盤でmod_proxyの後段モジュール(mod_proxy_balancerなど)のrequest前処理用関数(proxy_hook_pre_request)をフックし適切なworkerを取得しています。

balancerが登録してある場合には初期化など必要な処理をし、
NoProxyが指定されていない場合はproxyであると仮定して処理を進めます。

```c
                    /* handle the scheme */
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01142)
                                  "Trying to run scheme_handler against proxy");
                    access_status = proxy_run_scheme_handler(r, worker,
                                                             conf, url,
                                                             ents[i].hostname,
                                                             ents[i].port);
```

proxy_hook_scheme_handlerをフックしschemeに応じた処理を後段へ委譲します。

後段からDECLINED以外のリターンが返って来た場合は、cleanupなどで後処理をして終了です。

NoProxyの場合でもproxy_hook_scheme_handlerをフックしschemeに応じた処理を後段へ委譲するのは同様です。


```c
        /* Try again if the worker is unusable and the service is
         * unavailable.
         */
    } while (!PROXY_WORKER_IS_USABLE(worker) &&
             max_attempts > attempts++);
```

whileループの条件文です。
workerがusableでない、かつ試行回数の設定値(max attempts)を超えたか
を判定しています。

statusがDECLINEDだった場合はHTTP_INTERNAL_SERVER_ERRORを設定してcleanupで後処理をして終了です。

### cleanup

エラーの場合や正常終了で後処理をする場合の処理を実施するためのgoto分のラベルです。

リクエスト構造体のstatusの設定や後段で実装されているproxy_hook_post_requestやproxy_hook_request_statusをフックし、最終的なstatusをreturnします。

# おわりに

不要なところを飛ばしながら読んだため、理解できていないところもあります。
また、理解内容に間違いがあるかもしれませんが、mod_proxyの主要な関数を一通り読んでみました。

備忘録も兼ねていますが、参考になる方がいれば幸いです。

本記事でmod_proxy本体の内容は終わりです。
時間があったら、mod_proxy_balanacerやmod_proxy_wstunnelの実装についても記事を書いてみようと思っています。