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から細かい点が変更になっているが、大きな流れに違いはないようだ。

0 件のコメント:

コメントを投稿