2014年8月28日木曜日

リクエスト処理の流れ: ap_process_request_after_handler

ap_process_async_request()関数の最後に実行されている幾つかの関数を見ておく。
(リクエストの処理の流れ)
書き散らかしているだけで、分かりやすくはできていないのだが、一通り見ておきたい。

(httpd-2.4.9/modules/http/http_request.c)

272 void ap_process_async_request(request_rec *r)

273 {
  :

ここまでのリクエストの処理結果がOKかそうでないかで分岐している。

345     if (access_status == OK) {
346         ap_finalize_request_protocol(r);
347     }
348     else {
349         r->status = HTTP_OK;
350         ap_die(access_status, r);
351     }
352
353     ap_process_request_after_handler(r);
354 }

(6)ap_finalize_request_protocol

レスポンスの送信完了時に呼び出される。
この唯一の目的は、(transfer-encodingのような)レスポンスボディまわりのラッパーに対してプロトコル終了情報を通知することだ、とコメントにある。

(httpd-2.4.9/server/protocol.c)

1228 /* finalize_request_protocol is called at completion of sending the
1229  * response.  Its sole purpose is to send the terminating protocol
1230  * information for any wrappers around the response message body
1231  * (i.e., transfer encodings).  It should have been named finalize_response.
1232  */
1233 AP_DECLARE(void) ap_finalize_request_protocol(request_rec *r)
1234 {
1235     (void) ap_discard_request_body(r);

ap_discard_request_body()はリクエストボディを読み込む(読み捨てる)。
通常、既に読み込んでいるはずだ。

1236
1237     /* tell the filter chain there is no more content coming */
1238     if (!r->eos_sent) {
1239         end_output_stream(r);

end_output_stream()では、bucket brigadeを作成、
EOSメタデータバケットを作成し、bucket brigadeの末尾に追加する。
bucket brigadeをrequest_recのoutput_filtersに引き渡す処理が行われる。
出力フィルタでは、EOSメタデータバケットを受け取ると各種の終了処理が行われる。
CHUNK出力フィルタ(データをchunkedエンコーディングするフィルタ)では確かに、終了を示すchunkを作成している。

1240     }
1241 }

(7)ap_die

ap_die関数は、エラーレスポンスを返す。
ErrorDocumentの指定にも対応する。
ここでは取り上げない。

(8)ap_process_request_after_handler

ここはこれまで何度か名前を出した EOR メタデータバケットの追加処理が行われている。
つまり、リクエスト処理の終了を準備していることになる。

242 AP_DECLARE(void) ap_process_request_after_handler(request_rec *r)

243 {
244     apr_bucket_brigade *bb;
245     apr_bucket *b;
246     conn_rec *c = r->connection;
247
248     /* Send an EOR bucket through the output filter chain.  When
249      * this bucket is destroyed, the request will be logged and
250      * its pool will be freed
251      */

EORメタデータバケットを作成し、出力フィルタチェーンに引き渡す。
EORバケットが破棄されるタイミングで、アクセスログへの出力が実行され、requestメモリプールが解放される。

252     bb = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc);

bucket brigade bbを作成

253     b = ap_bucket_eor_create(r->connection->bucket_alloc, r);

EORメタデータバケットを作成

254     APR_BRIGADE_INSERT_HEAD(bb, b);

EORメタデータバケットを brigadeの先頭に挿入。
この時点ではbucket brigade bbにはEORバケットだけが含まれている。

255
256     ap_pass_brigade(r->connection->output_filters, bb);

その bucket brigade bb を出力フィルタに引き渡す。
この出力フィルタは、r->output_filtersではなく、conn_recに登録されている出力フィルタに直接、引き渡す。
コネクションフィルタによる処理だけ行うということだ。
プロトコルフィルタ以上の階層(request_recに登録されている階層)の処理は終わっているということだろう。
ただし、この処理タイミングでEORバケットが破棄されている(処理されている)とは限らない。

257
258     /* From here onward, it is no longer safe to reference r
259      * or r->pool, because r->pool may have been destroyed
260      * already by the EOR bucket's cleanup function.
261      */

EORバケットが処理済みであれば、requestメモリプールが解放されているため、
request_rec情報は無効になっている。
request_rec情報 *rは使ってはいけないことになる。

262
263     if (c->cs)
264         c->cs->state = CONN_STATE_WRITE_COMPLETION;

ここで、コネクションの状態情報がCONN_STATE_WRITE_COMPLETIONにセットされる。

265     check_pipeline(c);

処理待ちの読み込みデータがないかを確認している。
読込可能なデータがあった場合、conn_rec情報 *cのdata_in_input_filters変数に1がセットされる。

266     AP_PROCESS_REQUEST_RETURN((uintptr_t)r, r->uri, r->status);

DTrace関連のマクロと考えているが、詳細はよく分からない。

267     if (ap_extended_status) {
268         ap_time_process_request(c->sbh, STOP_PREQUEST);
269     }
270 }


ここまで。

2014年8月18日月曜日

リクエスト処理の流れ: ap_read_request

ハンドラ関数を実行しているap_invoke_handler()はap_process_async_request()から呼ばれていた(handlerフック関数の実行処理)。

そして、ap_process_async_request()関数は、ap_process_http_async_connection()から呼ばれていた(リクエストの処理の流れ)。

このap_process_http_async_connection()は、process_connectionフック関数内で、event MPMの場合に実行され、リクエストを1件処理している。
ap_process_async_request()は、読み込んだリクエストをチェック・変換し、レスポンスを返す。
そして、その前段でリクエストを読み込んでいるのが、ap_read_request()関数だ。

これを少し追っておく。

(5) ap_read_request

この関数は、HTTPリクエスト行、HTTPリクエストヘッダ行を読み込んで、request_rec情報に格納し、この情報を返却している。
ヘッダの読み込み完了前にエラーが発生した場合には、エラーレスポンスを返すこともする。

(httpd-2.4.9/server/protocol.c)

    895 request_rec *ap_read_request(conn_rec *conn)
    896 {
    897     request_rec *r;
    898     apr_pool_t *p;
    899     const char *expect;
    900     int access_status = HTTP_OK;
    901     apr_bucket_brigade *tmp_bb;
    902     apr_socket_t *csd;
    903     apr_interval_time_t cur_timeout;
    904
    905
    906     apr_pool_create(&p, conn->pool);
    907     apr_pool_tag(p, "request");

requestメモリプールを生成する。
メモリプールの説明はまだしていない書いていないが、apr_pcalloc()やapr_palloc()などは、メモリプールからメモリ領域を確保する。
EORメタデータバケットの破棄(apr_bucket_destroy())処理内で、requestメモリプールに対してapr_pool_destroy()が実行されている。
これよりrequestメモリプールから確保されたメモリ領域は再利用可能なメモリリストに戻される(解放される)。
このEORメタデータの処理は、httpd-2.2と異なっている(httpd-2.2系にはEORメタデータが存在しない)。
EORメタデータバケットの破棄時には、ap_run_log_transaction()も実行される。リクエスト処理の終了時の処理がここに集約されたのかもしれない。

    908     r = apr_pcalloc(p, sizeof(request_rec));
    909     AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn);
    910     r->pool            = p;

requestプールは、request_rec情報のpool変数に保持される。

    911     r->connection      = conn;

request_recのconnection変数には、渡されてきたconn_rec情報をセットする。

    912     r->server          = conn->base_server;

conn_recにセットされているbase_server(このタイミングではメインサーバか合致するIPベースのVirtualHostの情報)をセットする。

    913
    914     r->user            = NULL;
    915     r->ap_auth_type    = NULL;
    916
    917     r->allowed_methods = ap_make_method_list(p, 2);
    918
    919     r->headers_in      = apr_table_make(r->pool, 25);
    920     r->subprocess_env  = apr_table_make(r->pool, 25);
    921     r->headers_out     = apr_table_make(r->pool, 12);
    922     r->err_headers_out = apr_table_make(r->pool, 5);
    923     r->notes           = apr_table_make(r->pool, 5);
    924
    925     r->request_config  = ap_create_request_config(r->pool);
    926     /* Must be set before we run create request hook */
    927
    928     r->proto_output_filters = conn->output_filters;
    929     r->output_filters  = r->proto_output_filters;
    930     r->proto_input_filters = conn->input_filters;
    931     r->input_filters   = r->proto_input_filters;

conn_recに登録済みのコネクションタイプの入出力フィルタの設定をrequest_recに設定する。

    932     ap_run_create_request(r);

そして、create_requestフック関数を実行する。

    933     r->per_dir_config  = r->server->lookup_defaults;
    934
    935     r->sent_bodyct     = 0;                      /* bytect isn't for body */
    936
    937     r->read_length     = 0;
    938     r->read_body       = REQUEST_NO_BODY;
    939
    940     r->status          = HTTP_OK;  /* Until further notice */
    941     r->the_request     = NULL;
    942
    943     /* Begin by presuming any module can make its own path_info assumptions,
    944      * until some module interjects and changes the value.
    945      */
    946     r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
    947
    948     r->useragent_addr = conn->client_addr;
    949     r->useragent_ip = conn->client_ip;
    950
    951     tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

入力フィルタを通してデータを吸い上げるためのbucket brigadeを用意する。
このbucket brigadeは、この関数内で利用され、破棄される。

    952
    953     ap_run_pre_read_request(r, conn);

pre_read_requestフック関数を実行する。

    954
    955     /* Get the request... */
    956     if (!read_request_line(r, tmp_bb)) {

read_request_line()はHTTPリクエスト行を読み込む。
内部では、更に ap_rgetline_core()を呼び出して、入力フィルタからデータ1行分を吸い上げている。
入力フィルタの細かい処理は、別途確認したい。



ここの条件分岐は、何らかのエラー発生時の分岐になる。
ステータス(r->status)にエラー理由に応じた値がセットされている。

    957         if (r->status == HTTP_REQUEST_URI_TOO_LARGE
    958             || r->status == HTTP_BAD_REQUEST) {

414 Request-URI Too Long または 400 Bad Requestがセットされている場合の条件分岐だ。

    959             if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
    960                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565)
    961                               "request failed: client's request-line exceeds LimitRequestLine (longer than %d)",
    962                               r->server->limit_req_line);

これはエラーログメッセージ。
APLOG_INFOはinfoレベルのログであることを意味している。

    963             }
    964             else if (r->method == NULL) {
    965                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00566)
    966                               "request failed: invalid characters in URI");
    967             }
    968             ap_send_error_response(r, 0);

エラー応答を送信する処理だが、ErrorDocumentの設定は無視され、組み込みのエラーメッセージが返される。
(ErrorDocumentディレクティブでメッセージを直接書いている場合は有効)

以下の記述の関係だろう。
http://httpd.apache.org/docs/2.4/en/mod/core.html#errordocument
Although most error messages can be overridden, there are certain circumstances
where the internal messages are used regardless of the setting of ErrorDocument.
In particular, if a malformed request is detected, normal request processing
will be immediately halted and the internal error message returned. This is
necessary to guard against security problems caused by bad requests.

    969             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
    970             ap_run_log_transaction(r);

log_transactionフック関数の実行。
アクセスログが出力される。

    971             apr_brigade_destroy(tmp_bb);

作成していたbucket brigadeを破棄する。

    972             goto traceout;
    973         }
    974         else if (r->status == HTTP_REQUEST_TIME_OUT) {

ここは、リクエスト行の読み込みでタイムアウトとなった場合の分岐だ。
このケースではレスポンスを返していない。

    975             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
    976             if (!r->connection->keepalives) {
    977                 ap_run_log_transaction(r);
    978             }
    979             apr_brigade_destroy(tmp_bb);
    980             goto traceout;
    981         }

それ以外のステータスの場合は、何も行っていない。
アクセスログもエラーログも出力されない。
接続は受け付けたものの、何も送られないで切断された場合(空行のみとか)等がこの経路になる。

    982
    983         apr_brigade_destroy(tmp_bb);
    984         r = NULL;
    985         goto traceout;
    986     }
    987
    988     /* We may have been in keep_alive_timeout mode, so toggle back
    989      * to the normal timeout mode as we fetch the header lines,
    990      * as necessary.
    991      */
    992     csd = ap_get_conn_socket(conn);
    993     apr_socket_timeout_get(csd, &cur_timeout);
    994     if (cur_timeout != conn->base_server->timeout) {
    995         apr_socket_timeout_set(csd, conn->base_server->timeout);
    996         cur_timeout = conn->base_server->timeout;
    997     }

現在のソケットタイムアウトの値をapr_socket_timeout_get()で取得し、base_serverの設定値を比較し、変わっていたら、更新する。
値はapr_socket_t構造体のtimeout変数に保持されている。

    998
    999     if (!r->assbackwards) {

リクエスト行を処理して、メソッドとURLしかなかった場合、assbackwards変数に1がセットされている。
HTTP/0.9とみなされている。この場合、HTTPリクエストヘッダを持たない。
この分岐は否定"!"なので、0の場合がここに入ることになる。
HTTP/1.0や1.1などだ。

   1000         const char *tenc;
   1001
   1002         ap_get_mime_headers_core(r, tmp_bb);

HTTPリクエストヘッダの読込処理を実行する。
内部では、更に ap_rgetline_core()を呼び出して、入力フィルタからデータ1行分を吸い上げては処理を繰り返している。
タイムアウトや領域不足、制限超過、形式不備(ヘッダ名の後のコロンが見つからない)といったことでなければ、request_rec構造体のheadres_in配列に読み込んだヘッダを追記していく。
処理としては、複数行に折りたたまれた(fold)ヘッダの整形もここで行われている。
空行に達したら処理を終える。
見ての通り、戻り値はない。
エラーはrequest_rec構造体のstatus変数の値で判断される。

   1003         if (r->status != HTTP_OK) {

この分岐はリクエストヘッダ読み込み中のエラーが発生したケースとなる。
タイムアウトであるとか、ヘッダの行数超過や、ヘッダ一行あたりの文字数超過などがエラーとなる。
ステータスはrequest_recのstatusヘッダにセットされている。

   1004             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00567)
   1005                           "request failed: error reading the headers");
   1006             ap_send_error_response(r, 0);
   1007             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
   1008             ap_run_log_transaction(r);
   1009             apr_brigade_destroy(tmp_bb);
   1010             goto traceout;
   1011         }
   1012
   1013         tenc = apr_table_get(r->headers_in, "Transfer-Encoding");

Transfer-Encidingヘッダのチェックを行う。
このヘッダが存在する場合、エンコーディングはchunkedしか許容されていない。
chunked以外のエンコーディングが指定されていた場合は、エラーとする(BAD REQUEST)。
また、chunkedエンコーディングが指定されていた場合に、COntent-Lengthヘッダがあるとこれを無視するので、ここで、Content-Lengthヘッダを削除している(存在していれば削除される)。

   1014         if (tenc) {
   1015             /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
   1016              * Section 3.3.3.3: "If a Transfer-Encoding header field is
   1017              * present in a request and the chunked transfer coding is not
   1018              * the final encoding ...; the server MUST respond with the 400
   1019              * (Bad Request) status code and then close the connection".
   1020              */
   1021             if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */
   1022                     || ap_find_last_token(r->pool, tenc, "chunked"))) {
   1023                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02539)
   1024                               "client sent unknown Transfer-Encoding "
   1025                               "(%s): %s", tenc, r->uri);
   1026                 r->status = HTTP_BAD_REQUEST;
   1027                 conn->keepalive = AP_CONN_CLOSE;
   1028                 ap_send_error_response(r, 0);
   1029                 ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
   1030                 ap_run_log_transaction(r);
   1031                 apr_brigade_destroy(tmp_bb);
   1032                 goto traceout;
   1033             }
   1034
   1035             /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
   1036              * Section 3.3.3.3: "If a message is received with both a
   1037              * Transfer-Encoding and a Content-Length header field, the
   1038              * Transfer-Encoding overrides the Content-Length. ... A sender
   1039              * MUST remove the received Content-Length field".
   1040              */
   1041             apr_table_unset(r->headers_in, "Content-Length");
   1042         }
   1043     }
   1044     else {

こちらは、HTTP/0.9の分岐になる。

   1045         if (r->header_only) {

HEADメソッドによるリクエストの場合にheader_onlyフラグに1がセットされている。
ヘッダを処理しないはずのバージョンで、ヘッダを要求するメソッドというのは矛盾しているということだろう。
エラーが返されている(BAD REQUEST)。

   1046             /*
   1047              * Client asked for headers only with HTTP/0.9, which doesn't send
   1048              * headers! Have to dink things just to make sure the error message
   1049              * comes through...
   1050              */
   1051             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00568)
   1052                           "client sent invalid HTTP/0.9 request: HEAD %s",
   1053                           r->uri);
   1054             r->header_only = 0;
   1055             r->status = HTTP_BAD_REQUEST;
   1056             ap_send_error_response(r, 0);
   1057             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
   1058             ap_run_log_transaction(r);
   1059             apr_brigade_destroy(tmp_bb);
   1060             goto traceout;
   1061         }
   1062     }
   1063
   1064     apr_brigade_destroy(tmp_bb);

リクエスト読込に利用したbucket brigadeを破棄する。

   1065
   1066     /* update what we think the virtual host is based on the headers we've
   1067      * now read. may update status.
   1068      */
   1069     ap_update_vhost_from_headers(r);

r->server変数(server_rec)をHostヘッダの情報から得た名前ベースのVirtualHostのserver_rec情報に切り替える。
名前ベースのVirtualHostの設定が有効になる。

   1070
   1071     /* Toggle to the Host:-based vhost's timeout mode to fetch the
   1072      * request body and send the response body, if needed.
   1073      */
   1074     if (cur_timeout != r->server->timeout) {

名前ベースのVirtualHostに切り替えて、Timeoutの設定値が異なっている場合、設定しなおす。

   1075         apr_socket_timeout_set(csd, r->server->timeout);
   1076         cur_timeout = r->server->timeout;
   1077     }
   1078
   1079     /* we may have switched to another server */
   1080     r->per_dir_config = r->server->lookup_defaults;
   1081
   1082     if ((!r->hostname && (r->proto_num >= HTTP_VERSION(1, 1)))
   1083         || ((r->proto_num == HTTP_VERSION(1, 1))
   1084             && !apr_table_get(r->headers_in, "Host"))) {

HTTPバージョンがHTTP 1.1以上なのに、Hostヘッダが指定されていない場合、エラーとしている。

   1085         /*
   1086          * Client sent us an HTTP/1.1 or later request without telling us the
   1087          * hostname, either with a full URL or a Host: header. We therefore
   1088          * need to (as per the 1.1 spec) send an error.  As a special case,
   1089          * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain
   1090          * a Host: header, and the server MUST respond with 400 if it doesn't.
   1091          */
   1092         access_status = HTTP_BAD_REQUEST;
   1093         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00569)
   1094                       "client sent HTTP/1.1 request without hostname "
   1095                       "(see RFC2616 section 14.23): %s", r->uri);
   1096     }
   1097
   1098     /*
   1099      * Add the HTTP_IN filter here to ensure that ap_discard_request_body
   1100      * called by ap_die and by ap_send_error_response works correctly on
   1101      * status codes that do not cause the connection to be dropped and
   1102      * in situations where the connection should be kept alive.
   1103      */
   1104

以下でHTTP_IN入力フィルタを追加する。
この実体は、ap_http_filter()関数だ。chunkedエンコードされたか、Content-Lengthの指定されたリクエストボディの読み込みを行っている。
ボディ部を読み込むために必要なフィルタだ。

   1105     ap_add_input_filter_handle(ap_http_input_filter_handle,
   1106                                NULL, r, r->connection);
   1107
   1108     if (access_status != HTTP_OK

HTTP_INフィルタを追加してから、statusを判定している。

   1109         || (access_status = ap_run_post_read_request(r))) {

access_statusがHTTP_OKだった場合に、post_read_requestフック関数を実行する。

   1110         ap_die(access_status, r);
   1111         ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
   1112         ap_run_log_transaction(r);
   1113         r = NULL;
   1114         goto traceout;
   1115     }
   1116
   1117     if (((expect = apr_table_get(r->headers_in, "Expect")) != NULL)
   1118         && (expect[0] != '\0')) {

Expectヘッダを受信していた場合の処理。
"Expect: 100-continue" の場合、expecting_100に1をセットするが、それ以外の場合はエラー応答を返す(417 Expectation Failed)。
   1119         /*
   1120          * The Expect header field was added to HTTP/1.1 after RFC 2068
   1121          * as a means to signal when a 100 response is desired and,
   1122          * unfortunately, to signal a poor man's mandatory extension that
   1123          * the server must understand or return 417 Expectation Failed.
   1124          */
   1125         if (strcasecmp(expect, "100-continue") == 0) {
   1126             r->expecting_100 = 1;
   1127         }
   1128         else {
   1129             r->status = HTTP_EXPECTATION_FAILED;
   1130             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570)
   1131                           "client sent an unrecognized expectation value of "
   1132                           "Expect: %s", expect);
   1133             ap_send_error_response(r, 0);
   1134             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
   1135             ap_run_log_transaction(r);
   1136             goto traceout;
   1137         }
   1138     }
   1139
   1140     AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, (char *)r->uri, (char *)r->server->defn_name, r->status);

DTraceが利用可能な場合に有効化されるマクロのようだが、まだよく分かっていない。。

   1141     return r;

作成したrequest_rec情報が返される。
エラーかどうかは statusで判定されることになる。

   1142     traceout:
   1143     AP_READ_REQUEST_FAILURE((uintptr_t)r);
   1144     return r;

エラーステータスの場合も、作成したrequest_rec情報が返される点は同じ。

   1145 }

httpd-2.2から細かい点が変更になっているが、大きな流れに違いはないようだ。

2014年8月5日火曜日

リクエスト処理の流れ: ap_process_request_internal

(4) ap_process_request_internal

多数のフック関数が実行されている、この関数の中を覗いてみる。
handlerフック関数の実行処理 に書いたap_process_async_request()で実行されている関数だ。

ちなみに2.4.9と2.4.10の間では、server/request.cに相違点はないようだった。
従って、ap_process_request_internal関数にも違いはない。

 (httpd-2.4.9/server/request.c)
    126 AP_DECLARE(int) ap_process_request_internal(request_rec *r)
    127 {
    128     int file_req = (r->main && r->filename);

request_recのmain変数は、リクエストのサブリクエストから見た親リクエストを指す。
mainに値があるということは処理中のrequest_recはサブリクエストということだ。
サブリクエストというのは、リクエスト処理中に副次的に発生した処理すべきパスに対してリクエスト処理と一定の例外を除いて同様の操作を行う仕掛けだ。
サブリクエストが直接レスポンスを返すことはないが、リソースのファイルシステム上のパスの判定や、あるいは、アクセスチェックなどは行われる必要があったりする。
そうした処理のために用いられる。
(見たところ、ap_sub_req_method_uri()や、ap_sub_req_lookup_dirent()、ap_sub_req_lookup_file()といった 関数から処理が行われている)

リソースのファイルシステム上のパスは、request_recのfilenameに収められる。
URLパスから生成されるサブリクエストの場合はfilenameはnulと考えられる。

    129     int access_status;
    130     core_dir_config *d;
    131
    132     /* Ignore embedded %2F's in path for proxy requests */

コメントによれば、proxyへのリクエストのパスに含まれている "%2F"(エンコードされたスラッシュ)は 無視するとある。
proxyへのリクエストでなければ、無視しないという意味なんだろう。

    133     if (!r->proxyreq && r->parsed_uri.path) {

request_recのproxyreq変数は、このリクエストがproxyサーバへのリクエストの場合に種別が記録される。
proxyの設定が行われていて、proxyモジュールが受け取ったリクエストを処理対象と判断した場合に値が入る。
このif文はその否定なので、proxyへのリクエストではないリクエストが処理対象となる。

かつ、parsed_uri.path にも値が入っている場合だ。

    134         d = ap_get_core_module_config(r->per_dir_config);

これはcoreモジュールの設定情報のアドレスを取得するマクロだ。
r->per_dir_configにモジュールの設定情報が入っている。
この中のcoreモジュールの設定情報の 場所を返してくれる。

    135         if (d->allow_encoded_slashes) {

これは、AllowEncodedSlashes ディレクティブの設定情報だ。
こちらのif文は Onか、NoDecodeの場合だ。

    136             access_status = ap_unescape_url_keep2f(r->parsed_uri.path, d->decode_encoded_slashes);

この場合、%2Fがパスに含まれていてもエラーとはしない。
Onの場合はデコードする。
NoDecodeの場合はそのまま残す。

    137         }
    138         else {

こちらのelseは Offの場合だ。

    139             access_status = ap_unescape_url(r->parsed_uri.path);

その場合、エンコードされているURLパスをデコードし、不正なエンコードがされていたり 禁止文字(通常は"/")がエンコードされていた場合にエラーとしている。
禁止文字がスラッシュ(%2F)だ。
禁止文字があったら 404(not found)を返している。

On/Offいずれであっても、エンコード自体が誤っていればデコードできないので、エラーとなる。
その場合は 400(bad request)が返ってくる。

    140         }
    141         if (access_status) {

ここは、AllowEncodedSlashes Offで%2Fが見つかるか、エンコードそのものが 誤りの場合。

    142             if (access_status == HTTP_NOT_FOUND) {
    143                 if (! d->allow_encoded_slashes) {
    144                     ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026)
    145                                   "found %%2f (encoded '/') in URI "
    146                                   "(decoded='%s'), returning 404",
    147                                   r->parsed_uri.path);
    148                 }
    149             }
    150             return access_status;
    151         }
    152     }
    153
    154     ap_getparents(r->uri);     /* OK --- shrinking transformations... */

ap_getparent()は、urlから . や .. の相対パス指定を除去する。
. の場合は単に除去し、
.. の場合は前のパスセグメントごと除去する("xx/.."のまとまりで除去する)。

    155
    156     /* All file subrequests are a huge pain... they cannot bubble through the
    157      * next several steps.  Only file subrequests are allowed an empty uri,
    158      * otherwise let translate_name kill the request.
    159      */
    160     if (!file_req) {

ここはサブリクエストではないか、サブリクエストでもfilename(リソースのローカルパス)が未詳のリクエストが処理対象となる。

    161         if ((access_status = ap_location_walk(r))) {

ここでは、<Location><LocationMatch>節でこのリクエストの合致する条件をマージする処理が行われる。
マージの結果は request_rec.per_dir_configに格納される(マージされる)。


    162             return access_status;
    163         }
    164         if ((access_status = ap_if_walk(r))) {

ここでは、<If><IfElse><Else>節でこのリクエストの合致する条件をマージする処理が行われる。
マージの結果は、request_rec.per_dir_configに格納される(マージされる)。
<If>関連のセクションは2.2系には存在しなかった。


    165             return access_status;
    166         }
    167
    168         d = ap_get_core_module_config(r->per_dir_config);
    169         if (d->log) {
    170             r->log = d->log;
    171         }

マージされたディレクトリのログレベルの情報をrequest_recのlog変数(struct ap_logconfig *)にコピーする。

 
    172
    173         if ((access_status = ap_run_translate_name(r))) {

translate_nameフック関数を実行する。

 
    174             return decl_die(access_status, "translate", r);
    175         }
    176     }
    177
    178     /* Reset to the server default config prior to running map_to_storage
    179      */
    180     r->per_dir_config = r->server->lookup_defaults;

request_rec.per_dir_configをリセットする。

 
    181
    182     if ((access_status = ap_run_map_to_storage(r))) {

map_to_storageフック関数を実行する。

 
    183         /* This request wasn't in storage (e.g. TRACE) */
    184         return access_status;
    185     }
    186
    187     /* Rerun the location walk, which overrides any map_to_storage config.
    188      */
    189     if ((access_status = ap_location_walk(r))) {

再度、<Location><LocationMatch>節のマージ処理。
2度めはキャッシュが効くと思われる。


    190         return access_status;
    191     }
    192     if ((access_status = ap_if_walk(r))) {

再度、<If><IfElse><Else>節のマージ処理。
こちらも2度めはキャッシュが効くと思われる。


    193         return access_status;
    194     }
    195
    196     d = ap_get_core_module_config(r->per_dir_config);
    197     if (d->log) {
    198         r->log = d->log;
    199     }

同じく、ログレベルの情報をコピー


    200
    201     if ((access_status = ap_run_post_perdir_config(r))) {

post_perdir_configフック関数の実行 このフック関数は2.2系では存在していない。


    202         return access_status;
    203     }
    204
    205     /* Only on the main request! */
    206     if (r->main == NULL) {

サブリクエストでない場合の処理


    207         if ((access_status = ap_run_header_parser(r))) {

header_parserフック関数の実行


    208             return access_status;
    209         }
    210     }
    211
    212     /* Skip authn/authz if the parent or prior request passed the authn/authz,
    213      * and that configuration didn't change (this requires optimized _walk()
    214      * functions in map_to_storage that use the same merge results given
    215      * identical input.)  If the config changes, we must re-auth.
    216      */

内部リダイレクトされたリクエストやサブリクエストの処理中で、per_dir_configが元のリクエストのper_dir_configと同じだった場合、認証認可の処理を省略する(元のリクエストで設定されている認証の情報(userとap_auth_type)を引き継ぐ)。
設定が変わっていれば、再度認証認可処理を実行する必要がある。


    217     if (r->prev && (r->prev->per_dir_config == r->per_dir_config)) {

こちらは内部リダイレクトの認証認可が省略される条件。


    218         r->user = r->prev->user;
    219         r->ap_auth_type = r->prev->ap_auth_type;
    220     }
    221     else if (r->main && (r->main->per_dir_config == r->per_dir_config)) {

こちらはサブリクエストの認証認可が省略される条件


    222         r->user = r->main->user;
    223         r->ap_auth_type = r->main->ap_auth_type;
    224     }
    225     else {

認証認可処理を行う。
mod_access_compatを使用している場合は、ap_satisfies()がSatisfyディレクティブの指定を返す。
そうでない場合は、SATISFY_NOSPECが返る。


    226         switch (ap_satisfies(r)) {
    227         case SATISFY_ALL:
    228         case SATISFY_NOSPEC:

SATISFY_NOSPECなので、ここが、2.4系での標準的な処理となる。


    229             if ((access_status = ap_run_access_checker(r)) != OK) {

access_checkerフック関数を実行する。
見たところ、AllowMethodsディレクティブのチェック処理が行われている。
その他、mod_access_compatモジュールによるOrder/Deny/Allow処理がここで行われている。


    230                 return decl_die(access_status,
    231                                 "check access (with Satisfy All)", r);
    232             }
    233
    234             access_status = ap_run_access_checker_ex(r);

access_cheker_exフック関数を実行する。
access_cheker_exフック関数は 2.2系には存在していない。
ap_hook_access_checker_ex()関数を呼び出しているのは、ap_hook_check_access_ex()だけで、 ap_hook_check_access_ex()を経由してフック関数を登録するようになっている。
見たところ、ここでは、Require ipなどのユーザに依存しない認可処理を行っている。


    235             if (access_status == OK) {
    236                 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
    237                               "request authorized without authentication by "
    238                               "access_checker_ex hook: %s", r->uri);
    239             }
    240             else if (access_status != DECLINED) {
    241                 return decl_die(access_status, "check access", r);
    242             }
    243             else {

ユーザ認証が必要な場合の処理が行われる。


    244                 if ((access_status = ap_run_check_user_id(r)) != OK) {

check_user_idフック関数を実行する。
このフック関数も直接ap_hook_check_user_id関数で登録している場合と、
ap_hook_check_authn()関数を経由して登録している場合とがある。
見たところ、ここではAuthTypeに応じたユーザ認証処理が行われているようだ。


    245                     return decl_die(access_status, "check user", r);
    246                 }
    247                 if (r->user == NULL) {
    248                     /* don't let buggy authn module crash us in authz */
    249                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00027)
    250                                   "No authentication done but request not "
    251                                   "allowed without authentication for %s. "
    252                                   "Authentication not configured?",
    253                                   r->uri);
    254                     access_status = HTTP_INTERNAL_SERVER_ERROR;
    255                     return decl_die(access_status, "check user", r);
    256                 }
    257                 if ((access_status = ap_run_auth_checker(r)) != OK) {

auth_checkerフック関数を実行する。
ap_hook_auth_checker()を直接利用している個所は見当たらない(mod_luaだけ)。 ap_hook_check_authz()経由で登録している。
見たところ、ここではRequireに応じた認可処理が行われている。


    258                     return decl_die(access_status, "check authorization", r);
    259                 }
    260             }
    261             break;
    262         case SATISFY_ANY:
    263             if ((access_status = ap_run_access_checker(r)) == OK) {
    264                 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
    265                               "request authorized without authentication by "
    266                               "access_checker hook and 'Satisfy any': %s",
    267                               r->uri);
    268                 break;
    269             }
    270
    271             access_status = ap_run_access_checker_ex(r);
    272             if (access_status == OK) {
    273                 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
    274                               "request authorized without authentication by "
    275                               "access_checker_ex hook: %s", r->uri);
    276             }
    277             else if (access_status != DECLINED) {
    278                 return decl_die(access_status, "check access", r);
    279             }
    280             else {
    281                 if ((access_status = ap_run_check_user_id(r)) != OK) {
    282                     return decl_die(access_status, "check user", r);
    283                 }
    284                 if (r->user == NULL) {
    285                     /* don't let buggy authn module crash us in authz */
    286                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00028)
    287                                   "No authentication done but request not "
    288                                   "allowed without authentication for %s. "
    289                                   "Authentication not configured?",
    290                                   r->uri);
    291                     access_status = HTTP_INTERNAL_SERVER_ERROR;
    292                     return decl_die(access_status, "check user", r);
    293                 }
    294                 if ((access_status = ap_run_auth_checker(r)) != OK) {
    295                     return decl_die(access_status, "check authorization", r);
    296                 }
    297             }
    298             break;
    299         }
    300     }
    301     /* XXX Must make certain the ap_run_type_checker short circuits mime
    302      * in mod-proxy for r->proxyreq && r->parsed_uri.scheme
    303      *                              && !strcmp(r->parsed_uri.scheme, "http")
    304      */
    305     if ((access_status = ap_run_type_checker(r)) != OK) {

type_chekerフック関数を実行する。


    306         return decl_die(access_status, "find types", r);
    307     }
    308
    309     if ((access_status = ap_run_fixups(r)) != OK) {

fixupsフック関数を実行する。


    310         ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "fixups hook gave %d: %s",
    311                       access_status, r->uri);
    312         return access_status;
    313     }
    314
    315     return OK;
    316 }

これで、ap_process_request_internalは終わる。

2014年7月22日火曜日

httpd-2.4.10 Release

httpd-2.4.10がリリースされた
http://httpd.apache.org/

CHANGESを見ると、幾つかの脆弱性が修正され、バグ修正や機能拡張も多数ある。
CHNAGESにある脆弱性の項目を直訳してみた。

CVE-2014-0117
Connectionヘッダの処理中にクラッシュする問題を修正。
この問題は、スレッドMPMの リバースproxyに対してDoS攻撃を可能にしていた。
http://svn.apache.org/viewvc?view=revision&revision=1610737

CVE-2014-3523
Windows環境で使用される WinNT MPMにあった大量のメモリ消費によるDoSの問題が修正された。
回避策: AcceptFilter <protocol> {none|connect}
http://svn.apache.org/viewvc?view=revision&revision=1610653

CVE-2014-0226
scoreboardの操作にあった競合条件を修正。
これはヒープバッファオーバフローを引き起こす可能性があった。
http://svn.apache.org/viewvc?view=revision&revision=1610499

CVE-2014-0118
DAFLATE入力フィルタ(圧縮されたリクエストボディを伸長する)は、データ長と伸長後のリクエストボディの圧縮レート)を制限するようした。
これにより高レートで圧縮されたリクエストボディによるDoS状態を避けられる。
DeflateInflateLimitRequestBody、DeflateInflateRatioLimit、DeflateInflateRatioBurstディレクティブで指定する。
http://svn.apache.org/viewvc?view=revision&revision=1610503

CVE-2014-0231
mod_cgid:stdinを読み込まないCGIスクリプトに対するDoSの問題を修正した。
これは、長引くHTTPD子プロセスがscoreboardを一杯にしてしまい、最終的にサーバをハングさせてしまった。
スクリプトとの通信に対して、デフォルトでは、クライアントのI/Oタイムアウト(TimeOutディレクティブで指定)を適用するようにした。
スクリプトとの通信時のタイムアウトを違う値にしたい場合はCGIDScriptTimeoutディレクティブを使用する。
http://svn.apache.org/viewvc?view=revision&revision=1610512


新機能
announceを見ると新機能として次の項目が紹介されている。
http://mail-archives.us.apache.org/mod_mbox/www-announce/201407.mbox/%3CAE2C52FD-C787-4F30-82B9-A9446C13246C@apache.org%3E

*) Proxy FGI and websockets improvements
   FCGIとwebsocketのproxyの改善

*) Proxy capability via handler
   ハンドラを介したproxyの利用
   http://httpd.apache.org/docs/2.4/en/mod/mod_proxy.html#handler

*) Finer control over scoping of RewriteRules
   RewriteRuleの適用範囲のより細やかな管理

これは、RewriteOptionsにいくつか新しいオプションがあるが、2.4.8(2.4.9)からあるものだった。
http://httpd.apache.org/docs/2.4/en/mod/mod_rewrite.html#rewriteoptions
2.4.8のCHANGESに以下の項目がある。
*) mod_rewrite: Add RewriteOptions InheritDown, InheritDownBefore,
   and IgnoreInherit to allow RewriteRules to be pushed from parent scopes
   to child scopes without explicitly configuring each child scope.
   PR56153. [Edward Lu <Chaosed0 gmail com>]

*) Unix Domain Socket (UDS) support for mod_proxy backends.
   mpd_proxyのバックエンドに対する Unix Domain Socketのサポート

これは、CHANGESの2.4.7で追加になっている項目だが...
   *) mod_proxy: Added support for unix domain sockets as the
      backend server endpoint [Jim Jagielski, Blaise Tarr
      <blaise tarr gmail com>]
下記のコミットがそれなら、時期的には、2.4.8のようだ。 
http://svn.apache.org/viewvc?view=revision&revision=1560081
確認してみると、2.4.7に同梱のCHANGESにはこの記載はない。
何が起きていたのか。

*) Support for larger shared memory sizes for mod_socache_shmcb
   mod_sochache_shmcbにおけるより大容量のメモリのサポート

これも2.4.8のCHANGESにそれらしい項目がある。
*) mod_socache_shmcb.c: Remove arbitrary restriction on shared memory size
    previously limited to 64MB. [Jens Laas <jelaas gmail.com>]

*) mod_lua and mod_ssl enhancements
   mod_luaとmod_sslの拡張

*) Support named groups and backreferences within the LocationMatch,
   DirectoryMatch, FilesMatch and ProxyMatch directives.
   LocalMatch、SirectoryMatch、FilesMatchおよびProxyMatchディレクティブ
   での名前付きグループと後方参照のサポート

これも2.4.8のCHANGESにそのままの項目がある。
*) core: Support named groups and backreferences within the LocationMatch,
    DirectoryMatch, FilesMatch and ProxyMatch directives. (Requires
    non-ancient PCRE library) [Graham Leggett]

それで、2.4.9のANNOUNCEを見てみると(2.4.8はリリースされていない)、
http://mail-archives.us.apache.org/mod_mbox/www-announce/201403.mbox/%3C6DC3A229-80BC-4F57-8420-990C40DD3244%40apache.org%3E

*) Finer control over scoping of RewriteRules
*) Unix Domain Socket (UDS) support for mod_proxy backends.
*) Support for larger shared memory sizes for mod_socache_shmcb
*) mod_lua and mod_ssl enhancements
*) Support named groups and backreferences within the LocationMatch,
   DirectoryMatch, FilesMatch and ProxyMatch directives.


2.4.7を見ると
http://mail-archives.us.apache.org/mod_mbox/www-announce/201311.mbox/%3C4E18E0C7-8E6E-417E-B36C-3A04C880E4CA%40apache.org%3E

*) Major updates to mod_proxy_fcgi
*) Higher performant event MPM
*) Enhancements to the WinNT MPM

別にマージされているわけではない。
よく分からない。


2014年7月15日火曜日

リクエストの処理の流れ

handlerフック関数の実行の続きで、ap_process_async_request()関数から呼び出し関係を遡ってみる。


(1) ap_process_http_async_connection関数

呼び出し元の関数ではリクエストを読込み(ap_read_request())、ap_process_async_request()処理を実行している。

(httpd-2.4.9/modules/http/http_core.c)

    124 static int ap_process_http_async_connection(conn_rec *c)
    125 {
    126     request_rec *r;
    127     conn_state_t *cs = c->cs;
  :
    132     while (cs->state == CONN_STATE_READ_REQUEST_LINE) {
    133         ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
    134
    135         if ((r = ap_read_request(c))) {
リクエスト行とリクエストヘッダを読み込み、request_rec情報に格納する。
ここで直接呼び出されるフック関数の実行関数には次のものがある
 * ap_run_create_request
 * ap_run_pre_read_request
 * ap_run_post_read_request
 * ap_run_log_transaction(エラー発生の場合)

     :
    141             if (r->status == HTTP_OK) {
    142                 cs->state = CONN_STATE_HANDLER;
    143                 ap_process_async_request(r);
     :
    151                 r = NULL;
    152             }
    153
    154             if (cs->state != CONN_STATE_WRITE_COMPLETION &&
    155                 cs->state != CONN_STATE_SUSPENDED) {
    156                 /* Something went wrong; close the connection */
    157                 cs->state = CONN_STATE_LINGER;
    158             }
    159         }
    160         else {   /* ap_read_request failed - client may have closed */
    161             cs->state = CONN_STATE_LINGER;
    162         }
    163     }
    164
    165     return OK;
    166 }

ここで conn_state_t という構造体が出てくる。
これは、コネクションの状態を管理している。

(httpd-2.4.9/include/httpd.h)

   1187 struct conn_state_t {
   1188     /** Current state of the connection */
   1189     conn_state_e state;
   1190     /** Whether to read instead of write, or write instead of read */
   1191     conn_sense_e sense;
   1192 };

ここにあるconn_state_e(列挙型)変数 stateは次の通り

(httpd-2.4.9/include/httpd.h)

   1167 typedef enum  {
   1168     CONN_STATE_CHECK_REQUEST_LINE_READABLE,
   1169     CONN_STATE_READ_REQUEST_LINE,
   1170     CONN_STATE_HANDLER,
   1171     CONN_STATE_WRITE_COMPLETION,
   1172     CONN_STATE_SUSPENDED,
   1173     CONN_STATE_LINGER,          /* connection may be closed with lingering */
   1174     CONN_STATE_LINGER_NORMAL,   /* MPM has started lingering close with normal timeout */
   1175     CONN_STATE_LINGER_SHORT     /* MPM has started lingering close with short timeout */
   1176 } conn_state_e;

ap_process_http_async_connection()でチェックされている CONN_STATE_READ_REQUEST_LINE は、その名の通り、リクエストの読み込みということだ。
リクエストの読み込み後、CONN_STATE_HANDLERに更新される(142行目)。
そして、ap_process_async_request()が実行される。

その処理を終えると、CONN_STATE_WRITE_COMPLETIONか、CONN_STATE_SUSPENDEDかチェックされ、いずれでもなければ、CONN_STATE_LINGERで更新される(157行目)。

この時点で、cs->stateは、CONN_STATE_WRITE_COMPLETIONか、CONN_STATE_SUSPENDEDか、CONN_STATE_LINGERということになるので、132行目からのwhile (cs->state == CONN_STATE_READ_REQUEST_LINE)ループからは抜ける。

この処理だけに着目する限り、whileループを実査にループする経路はなさそうだ。

(2)process_connectionフック関数

さて、もう一段、呼び出し元を辿ると、次の関数に辿り着く。

(httpd-2.4.9/modules/http/http_core.c)

    225 static int ap_process_http_connection(conn_rec *c)
    226 {
    227     if (async_mpm && !c->clogging_input_filters) {
    228         return ap_process_http_async_connection(c);
    229     }
    230     else {
    231         return ap_process_http_sync_connection(c);
    232     }
    233 }

これはフック関数だ。
以下で登録されている。process_connectionフック関数である。

(httpd-2.4.9/modules/http/http_core.c)

    275     ap_hook_process_connection(ap_process_http_connection, NULL, NULL,
    276                                APR_HOOK_REALLY_LAST);

上記のap_process_http_connection()で使用されているフラグasync_mpmはap_mpm_query()関数で設定されている。

    263     if (ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm) != APR_SUCCESS) {
    264         async_mpm = 0;
    265     }

このMPMの情報を取得する関数自体が内部でmpm_queryフック関数を呼び出している。
MPMによって、登録されているフック関数が異なり、実際にセットされる値も変わるという訳だ。
event MPMの場合は、このmpm_queryフック関数は、event_query()だ。
この関数が、AP_MPMQ_IS_ASYNCというコードを与えられたときに、引数async_mpmにセットする値は、1である。

ちなみに、worker MPMにおける、mpm_queryフック関数はworker_query()だが、AP_MPMQ_IS_ASYNCに対して返す値の指定がない。ここでは、APR_ENOTIMPLがエラーとして返されるので、結果として、264行目によって値が0となる。
prefork MPMも worker MPMと同様だった。

clogging_input_filtersは、2.4.9では使用されていないので、初期値の0のままだ。

従って、ap_process_http_connection()関数は、event MPMでは、ap_process_http_async_connection()が実行され、worker MPM/prefork MPMでは、ap_process_http_sync_connection()が実行される。

clogging_input_filtersをセットすると、worker MPMと同様にリクエスト処理でワーカスレッドを占有する、というのは、要はこの処理の分岐の話だ。


(3)event MPM: process_socket関数


続いて、このap_process_http_connection()を実行するprocess_connectionフック関数の呼び出し箇所を見てみる。
event MPMでは process_socket()関数の処理で呼ばれている。
これは、event MPMのworkerスレッドで実行される。
listenerスレッドが受け付けたリクエストの処理を行うのが、workerスレッドのprocess_socket()関数だ。

もう少し細かく見ると、listenerスレッドは、listenソケットのacceptを監視し、既に接続済みのソケットが読込可能/書込可能となるのを監視している。
またapr_skiplistを利用したタイマー処理のタイムアップも監視しており、そのほかにも4種類のタイムアウト処理を実行する。

読込可能チェックには、以下の状態がある。
  • KeepAlive接続における次リクエストの受信待ち
  • lingeringクローズでの通信相手ソケットからのクローズ(FIN)待ち

書込可能チェックは、以下の状態だ。
  • クライアントに送ろうとしたデータがブロックされてしまった場合(送信できなかった場合)、再び、書込可能なるのを待っている状態
これらのうち、lingeringクローズソケットのクローズ処理以外は、workerスレッドで処理される。
そして、acceptされたソケットの処理、KeepAlive接続の読込処理、書込み可能になったソケットの書込処理がprocess_socket関数で行われる。
タイマー処理は、process_socket関数では処理されない。

新たなソケットの処理とKeepAlive接続の読込処理では、conn_state_tがCONN_STATE_CHECK_REQUEST_LINE_READABLE状態になる。
書込み可能となったソケットの場合は、conn_state_tがCONN_STATE_WRITE_COMPLETION状態だ。

ここで、process_socket関数でap_run_process_connection()を実行するのは、CONN_STATE_CHECK_REQUEST_LINE_READABLE状態の場合の処理となる。

    881 static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock,
    882                           event_conn_state_t * cs, int my_child_num,
    883                           int my_thread_num)
    884 {
    :

    893     if (cs == NULL) {           /* This is a new connection */

新しくacceptされたソケットの処理の場合、csはNULLとなっている。
KeepAlive接続の処理の場合はcsは作成済みなので、ここの処理を経由しない。
ここでコネクション情報の初期設定やevent_conn_state_t情報の初期設定
などの初期処理が実行される。
    897         c = ap_run_create_connection(p, ap_server_conf, sock,
    898                                      conn_id, sbh, cs->bucket_alloc);
     :
    920         ap_update_vhost_given_ip(c);
    921
    922         rc = ap_run_pre_connection(c, sock);


    942         cs->pub.state = CONN_STATE_READ_REQUEST_LINE;
    943
    944         cs->pub.sense = CONN_SENSE_DEFAULT;
    945     }
     :
    952     if (c->clogging_input_filters && !c->aborted) {

clogging_input_filtersフラグが立っている場合の処理。
実行されるのはap_run_process_connection()で、フラグが立っていない場合と
同じだが、終了時のconn_state_tに設定される状態が、CONN_STATE_LINGER状態
に限定されている(SUSPENDでなければ)。
     :
    959         apr_atomic_inc32(&clogged_count);
    960         ap_run_process_connection(c);
    961         if (cs->pub.state != CONN_STATE_SUSPENDED) {
    962             cs->pub.state = CONN_STATE_LINGER;
    963         }
    964         apr_atomic_dec32(&clogged_count);
    965     }
     :


    967 read_request:
    968     if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE) {

1件のリクエスト処理はここから始まる。
    969         if (!c->aborted) {
    970             ap_run_process_connection(c);

これこそ、今まで見てきた処理を実行する process_connectionフック関数の
実行だ。
     :
    976         }
    977         else {
    978             cs->pub.state = CONN_STATE_LINGER;
    979         }
    980     }
    981
    982     if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) {

process_connectionフック関数からreturnすると、通常、conn_state_tは
CONN_STATE_WRITE_COMPLETION状態となっている。
ap_process_async_request()関数の最後に実行される 1件のリクエスト処理の
終了処理であるap_process_request_after_handler関数内で、
CONN_STATE_WRITE_COMPLETIONがセットされる。
そして、ここでは bucket brigadeが NULLで出力フィルタに引き渡されている。
内部に保持されているbucket brigadeが処理対象ということだ。
    983         ap_filter_t *output_filter = c->output_filters;
     :

    989         rv = output_filter->frec->filter_func.out_func(output_filter, NULL);

     :
   1065     return;
   1066 }


この処理は、listenerスレッドと相互に関係しながら処理を進めるので、少し分かりにくい気がする。
が、リクエスト処理の流れを追うという意味では、ここが最上流、源流にあたる。
そういうわけで、上流から下流に簡単な図を書いてみた。


フック関数を中心に追ってみた。今日は終わり。

2014年7月7日月曜日

handlerフック関数の実行処理

handlerフック関数の例の続きだ。

(1)ap_invoke_handler関数

フック関数は以下のap_run_handler()処理で実行される。

(httpd-2.4.9/server/config.c)
    385 AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r)
    386 {
     :
    401     ap_run_insert_filter(r);
ap_run_insert_filterもフック関数の実行処理だ。
基本的には、リクエスト処理に用いる入力フィルタや出力フィルタがセットアッ
プされる。

     :

    439     result = ap_run_handler(r);

ここでhandlerフック関数を実行する

     :
    465     return result == DECLINED ? HTTP_INTERNAL_SERVER_ERROR : result;
    466 }


(2)ap_process_async_request関数

これを行っている、ap_invoke_handler()関数は、内部リダイレクトの処理や、サブリクエストの処理などを含め数か所で呼ばれている。
そのうち、外部から受け取ったリクエストをそのまま処理するのは、次の箇所だった。
受信したリクエストの情報が引数のrequest_rec情報に保存されている。
この関数がリクエスト処理の本体だと言えそうだ。

(httpd-2.4.9/modules/http/http_request.c)

    272 void ap_process_async_request(request_rec *r)
    273 {
     :
    313     access_status = ap_run_quick_handler(r, 0);  /* Not a look-up request */
    314     if (access_status == DECLINED) {

ap_run_quick_hanlder()もフック関数の実行処理だ。
その戻り値がDECLINEDの場合にこのif文内の処理が行われる。
quick_handlerフック関数は、AP_IMPLEMENT_HOOK_RUN_FIRSTタイプなので、
handlerフック関数と同じだ。
フック関数の戻り値がDECLINE以外の場合に処理を終了する。
フック関数は処理を行うと、エラーステータスやOKを返す。このタイプでDECLINEDを
返すのは、次のフック関数に処理を継続させるためだ。
この戻り値がDECLINEDということは、登録されたフック関数はすべて実行されたが、
結局、エラーやOKを返すような処理は行われなかったことになる。
これが何を意味するかだが、例えば、特定の条件でキャッシュがヒットした場合には
ここの処理には入らない(キャッシュが返される)、というようなことだ。キャッ
シュについては、あとで少し細かく見たい。

    315         access_status = ap_process_request_internal(r);

この関数内で、アクセス権限チェックを含む、多くの処理が実行される。
フック関数の実行も、直接呼び出されるだけで以下のものがある。
* ap_run_post_perdir_config * ap_run_translate_name * ap_run_map_to_storage * ap_run_header_parser * ap_run_access_checker * ap_run_access_checker_ex * ap_run_check_user_id * ap_run_auth_checker * ap_run_type_checker * ap_run_fixups
このチェックに合格すると、OKが返される
316 if (access_status == OK) { 317 access_status = ap_invoke_handler(r); ap_invoke_handle()はap_process_request_internal()がOKを返した場合にのみ実行 される。 ここから、ap_run_handler()が実行された。 318 } 319 } : リクエストの処理結果はaccess_statusにセットされている。 結果に応じて(OKか否か)処理が振り分けられている。 345 if (access_status == OK) { 346 ap_finalize_request_protocol(r); 347 } 348 else { 349 r->status = HTTP_OK; 350 ap_die(access_status, r); 351 } 352 353 ap_process_request_after_handler(r); 最後のこの処理は、リクエスト処理1件分の終了処理だ。 EOR(END of Request)バケットを生成し、これを収めたbucket brigadeを出力フィル タにap_pass_brigadeする。 default_handler等リクエスト処理中は、bucket brigadeを引き渡す出力フィルタが request_recのoutput_filters だったが、ここでは、conn_recのoutput_filtersに なっていた。 354 }


これだけでは、ap_run_handler()がどういうタイミングで処理されるのか分からない。
そこで、このあと少し、リクエスト処理の流れを追ってみる。
その際には、再度、ap_process_async_request()やap_invoke_handler()の処理内容も確認したい。
行ったり来たりだが、リクエスト処理の流れを追いながらフック関数の呼出し位置を確認し、その後また、フック関数を幾つか確認するという作業に戻る予定とする。

2014年7月1日火曜日

ハンドラ(handler)フック関数の例: send-as-is ハンドラ

send-as-isハンドラ

default_handerの続き
もう一つ、asisモジュール(mod_asis.so)の提供するハンドラを見てみる。
まず簡単に、使い方を確認する。
default_handlerは何もしない場合に使われるが、send-as-isは適用条件を指定してやる必要がある。

(1)使い方

send-as-isハンドラを利用するには、AddHandlerディレクティブを使って、拡張子.asisのファイルと結びつけるなどして、ハンドラとリソースを結び付けておく必要がある。

AddHandler send-as-is asis

send-as-isハンドラの処理するファイルは、特定の書式に従って書かれている。
と言っても、内容はほぼ、HTTPレスポンスをそのまま書いたものだ。
ただ1行目のステータス行だけはHTTPヘッダ風(Status:値)で、実際のレスポンスと異なる書式で定義している。
そして、Apacheは、この内容をほぼそのままクライアントへのレスポンスとする。
プロジェクトのドキュメントには以下のような例が書かれている。

Status: 301 Now where did I leave that URL
Location: http://xyz.example.com/foo/bar.html
Content-type: text/html

<html>
<head>
<title>Lame excuses'R'us</title>
</head>
<body>
<h1>Fred's exceptionally wonderful page has moved to
<a href="http://xyz.example.com/foo/bar.html">Joe's</a> site.
</h1>
</body>
</html>
</pre>

このファイルは301応答を生成し、Locationヘッダにここに書かれているURLを渡す。
上記の内容をsample.asisというファイルに保存して、curlコマンドでアクセスしてみる。

$ curl -v http://localhost:8080/sample.asis
* About to connect() to localhost port 8080 (#0)
*   Trying ::1... connected
* Connected to localhost (::1) port 8080 (#0)
> GET /sample.asis HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.3.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 301 Now where did I leave that URL
ステータス行のReason-Phraseも、ファイルに書かれたものが使われている
< Date: Wed, 25 Jun 2014 08:24:38 GMT
Dateヘッダは自動的に追加
< Server: Apache/2.4.9 (Unix)
Serverヘッダは自動的に追加
< Location: http://xyz.example.com/foo/bar.html < Content-Length: 199
Content-Lengthも自動的に追加
< Content-Type: text/html < <html> <head> <title>Lame excuses'R'us</title> </head> <body> <h1>Fred's exceptionally wonderful page has moved to <a href="http://xyz.example.com/foo/bar.html">Joe's</a> site. </h1> </body> </html> * Connection #0 to host localhost left intact * Closing connection #0

この結果、一般的なブラウザは http://xyz.example.com/foo/bar.html へのリクエストを自動的に実行するはずだ。

参考: http://httpd.apache.org/docs/2.4/en/mod/mod_asis.html

(2)フック関数の登録

フック関数の登録は以下の処理で行われている。


(httpd-2.4.9/modules/generators/mod_asis.c)

    114 static void register_hooks(apr_pool_t *p)
    115 {
    116     ap_hook_handler(asis_handler,NULL,NULL,APR_HOOK_MIDDLE);
    117 }


タイミングはAPR_HOOK_MIDDLEになっている。
つまり、default_handlerより先に実行される。

(3)ハンドラフック関数の処理


処理の流れを追ってみる。
処理対象のリクエストかどうかの判定を行い、対象でなければ、DECLINEで終了する処理が開始時に行われる。
あとは、リクエストのファイルを読み込み、解析し、必要なレスポンスを生成し、出力フィルタに引き渡す処理が行われている。
以下の処理を眺めるだけでは、Dateヘッダや、Serverヘッダや、Content-Lengthヘッダが追加されている様子はない。これは出力フィルタで行われる。


(httpd-2.4.9/modules/generators/mod_asis.c)
     31 static int asis_handler(request_rec *r)
     32 {
     33     apr_file_t *f;
     :
     37     if (strcmp(r->handler, ASIS_MAGIC_TYPE) && strcmp(r->handler, "send-as-is")) {
リクエストが、send-as-isハンドラで処理されるべきかどうかを判定している 箇所。ASIS_MAGIC_TYPEは "httpd/send-as-is"で、仮想的なMIME Typeになって いる。これでも指定できるということだ。 AddHandler send-as-is asis と指定しているなら、r->handlerに"send-as-is" が設定されている場合に処理される。
     38         return DECLINED;
DECLINEDで返るのは、次のhandlerフック関数の処理を継続するためだ
     39     }
     :
     42     if (r->method_number != M_GET) {
send-as-isハンドラはGETリクエストだけを処理する。 POSTリクエストなどの場合は、処理対象としない。
     43         return DECLINED;
     44     }
     45
     46     if (r->finfo.filetype == APR_NOFILE) {
     47         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01233)
     48                     "File does not exist: %s", r->filename);
     49         return HTTP_NOT_FOUND;
指定したファイルが存在しない場合は 404 not foundを返す。
     50     }
     51
     52     if ((rv = apr_file_open(&f, r->filename, APR_READ,
     53                 APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
     54         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01234)
     55                     "file permissions deny server access: %s", r->filename);
     56         return HTTP_FORBIDDEN;
指定したファイルをオープンする。 失敗した場合には、 403 Forbidden を返す
     57     }
     58
     59     ap_scan_script_header_err_ex(r, f, NULL, APLOG_MODULE_INDEX);
ファイルのHTTPレスポンスヘッダ部分(空行までの部分)の処理を実行している。 Status: XXX の場合は、ステータス行にセットされ、 その他、適宜、レスポンス用のヘッダ情報にセットされる。
     60     location = apr_table_get(r->headers_out, "Location");
     61
     62     if (location && location[0] == '/' &&
     63         ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) {
これは、指定したファイルにLocationヘッダが指定されており、 リクエストがリダイレクトされるように指定されていたことを意味している。 しかもリダイレクト先が "/"で始まるので、自サーバ上のパスと考えられる。 ここではこのlocationへのリクエスト(内部リダイレクトリクエスト)を実行 している。
     64
     65         apr_file_close(f);
     66
     67         /* Internal redirect -- fake-up a pseudo-request */
     68         r->status = HTTP_OK;
     69
     70         /* This redirect needs to be a GET no matter what the original
     71          * method was.
     72          */
     73         r->method = "GET";
     74         r->method_number = M_GET;
     75
     76         ap_internal_redirect_handler(location, r);
     77         return OK;
     78     }
     79
     80     if (!r->header_only) {
HEADではない場合の処理となる。 先に指定されたファイルのHTTPレスポンスヘッダ部分の処理を終えている。 ここは、引き続きボディ部の処理を実行する。
     81         conn_rec *c = r->connection;
     82         apr_bucket_brigade *bb;
     83         apr_bucket *b;
     84         apr_off_t pos = 0;
     85
     86         rv = apr_file_seek(f, APR_CUR, &pos);
     87         if (rv != APR_SUCCESS) {
     88             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01235)
     89                           "mod_asis: failed to find end-of-headers position "
     90                           "for %s", r->filename);
     91             apr_file_close(f);
     92             return HTTP_INTERNAL_SERVER_ERROR;
     93         }
     94
     95         bb = apr_brigade_create(r->pool, c->bucket_alloc);
bucket brigade bbを作成する
     96         apr_brigade_insert_file(bb, f, pos, r->finfo.size - pos, r->pool);
指定したファイルの現在位置(ボディ部の開始)~終了位置(ファイルサイ ズから、開始位置を引いた値)でファイルbucketを生成し それを bucket brigade bb に挿入する。
     97
     98         b = apr_bucket_eos_create(c->bucket_alloc);
     99         APR_BRIGADE_INSERT_TAIL(bb, b);
EOS bucket(メタデータ)を作成し、これを bucket brigade bb の末尾に 追加する
    100         rv = ap_pass_brigade(r->output_filters, bb);
そして bucket brigade bb を出力フィルタに引き渡す。
    101         if (rv != APR_SUCCESS) {
    102             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01236)
    103                           "mod_asis: ap_pass_brigade failed for file %s", r->filename);
    104             return HTTP_INTERNAL_SERVER_ERROR;
    105         }
    106     }
    107     else {

HEADリクエストの場合は、ボディ部の処理はないので、終了する。
ヘッダ情報は、request_rec情報のheaders_out配列に登録されているが、
まだ送信はされていない。
空のbucket brigadeを出力フィルタに渡すなどの処理も行われていない。


    108         apr_file_close(f);
    109     }
    110
    111     return OK;
    112 }


その他のハンドラフック関数も順に調査していきたいが、とりあえず、今回はここまで。