nginx による http パッケージ本体の処理例の詳細な説明
http フレームワークは http リクエスト パッケージ本体を破棄し、前の記事 http フレームワークはパッケージ本体を受け取ります。両方とも、 http フレームワーク パッケージ本体をどうするかを決定するために各 http モジュールによって呼び出されるメソッド。破棄するか受信するかを選択するかどうかはモジュールによって決定されます。たとえば、静的リソース モジュールがブラウザから get リクエストを受け取り、ファイルをリクエストした場合、ファイルのコンテンツをブラウザに直接返すことができます。パッケージ本体データを受信する必要はなく、get リクエストには実際にはパッケージ本体が含まれません。そのため、静的リソースモジュールは、httpフレームワークが提供するパケットボディ破棄関数を呼び出してパケット破棄処理を行うことになります。
パッケージ本体を受信する処理に比べて、パッケージ本体を破棄する操作は非常に簡単で、少なくとも http 構造体の request_body バッファにパッケージ本体を格納する必要はありません。また、パッケージ本体がメモリにのみ保存されるのか、ファイルにのみ保存されるのかなどを考慮する必要がなく、フレームワークはパッケージ本体を受け取った直後にそれを破棄します。破棄されるパケット本体は 3 つの部分で構成されます。
(1) http モジュールは、フレームワークによって提供される ngx_http_discard_request_body 関数を初めて呼び出し、いくつかの初期化操作を実行します。たとえば、1 回の操作ですべてのパケット本体を破棄できない場合、再度実行がスケジュールされたときにパケット破棄操作を継続できるように、読み取りイベントを epoll に再度登録する必要があります。さらに、実際のパケット破棄関数ngx_http_read_discarded_request_bodyを呼び出してパケット本体を破棄します。
(2) 一度の操作で全ての袋を廃棄できなかった場合は、再度イベントを開催した際に残りの袋データを受信し続けて廃棄します。
(3) 実際のパケットロス処理、つまりパケット受信後にパケット本体を直接破棄します。
図から、これら 3 つの処理のうち、パケットロス処理が共通の機能であることがわかります。つまり、http モジュールが ngx_http_discard_request_body 関数を呼び出してパケット損失処理を開始するか、パケットボディ全体が 1 つのスケジュールで受信されない場合、ngx_http_discarded_request_body_handler が残りのパケットボディ操作を破棄する責任を負い、パブリック パケット損失関数 ngx_http_read_discarded_request_bodyパケット本体を直接受信するために呼び出されます。操作を破棄します。
1. パケットロスの初期化処理
ngx_http_discard_request_body は、パケットボディを破棄するために http モジュールによって呼び出される関数です。これはモジュールに対する透過的な操作です。つまり、モジュールは、http フレームワークがこのインターフェイスをどのように実装するかを知らなくても、http リクエスト パケット本体を破棄するためにこのインターフェイスを呼び出すだけで済みます。フレームワークが 1 回のスケジューリングですべてのパケット本体を破棄しなくても、次のスケジューリングが実行されるときに再度パケット破棄操作が実行されますが、モジュールはこれを知りません。
//功能: 丢弃http包体的首次回调函数,如果一次性不能全部接收完成并丢弃,则设置 // 读事件的回调为ngx_http_discarded_request_body_handler ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r) { //需要丢弃的包体不用考虑超时问题 if (rev->timer_set) { ngx_del_timer(rev); } //包体长度小于等于0,则直接返回。表示丢弃包体 //如果已经接收过包体了,这时也不需要在接收。通常情况下get请求没有包体,因此包体长度为0 if (r->headers_in.content_length_n <= 0 || r->request_body) { return ngx_ok; } size = r->header_in->last - r->header_in->pos; //已经预先接收了部分包体 if (size) { //包体未全部接收完成 if (r->headers_in.content_length_n > size) { r->header_in->pos += size; r->headers_in.content_length_n -= size; } else { //包体已经全部接收 r->header_in->pos += (size_t) r->headers_in.content_length_n; r->headers_in.content_length_n = 0; return ngx_ok; } } //设置后续读事件的回调 r->read_event_handler = ngx_http_discarded_request_body_handler; //注册读事件回调,插入到epoll ngx_handle_read_event(rev, 0)); //接收包体内容 if (ngx_http_read_discarded_request_body(r) == ngx_ok) { //表示已经接收到完整的包体了,将延迟关闭清0 r->lingering_close = 0; } else { //表示需要多次调度才能完成丢弃包体这个操作,于是把引用计数加1,防止这边在丢弃包体,而其他 //事件却已经让请求意外销毁 r->count++; //标识为正在丢弃包体 r->discard_body = 1; } return ngx_ok; }
http リクエスト ヘッダーを受信したときに、http パッケージ本体のデータも受信した場合は、この時点で残りの操作を続行する必要はありません。正常に破棄された場合、関数は直接戻ります。 1 つのスケジュールですべてのパケット ボディが破棄されない場合、http リクエスト構造体 ngx_http_request_s の読み取りイベント read_event_handler が ngx_http_discarded_request_body_handler に設定されます。この関数は、次回スケジュールされたときに残りのパケット ボディを破棄します。したがって、ngx_http_discard_request_body は http モジュールによって初めて呼び出されます。
この関数は、実際のパケット損失関数 ngx_http_read_discarded_request_body を呼び出して、パケット本体の受信を開始し、直接破棄します。
2. パケット損失処理
ngx_http_read_discarded_request_body 関数は、クライアントからパケット本体データを受信し、それを破棄します。したがって、モジュールにとってはパッケージ本体を破棄する操作ですが、フレームワークにとってパッケージ本体を破棄する操作は実際にはパッケージ本体を受け取る操作になりますが、受け取ったパッケージ本体のデータはモジュールには渡されません使用するために。なぜフレームワークはパッケージ本体を受け取って直接破棄する必要があるのでしょうか?それは不要ではないでしょうか?そうではなく、このようにするのには理由があります。堅牢でないクライアント ブラウザーがブロッキング メソッドを使用して http パケット本体データを nginx サーバーに送信すると仮定します。nginx フレームワークがデータを受信しない場合、クライアント ブラウザーはタイムアウトして応答しなくなり、クライアント ブラウザーが繋がり。 。したがって、nginxのhttpフレームワークは、まずクライアントからのパッケージ本体データをカーネルから受信する必要がありますが、このデータはモジュールにとっては役に立たないため、受信したデータは直接破棄されます。
//功能: 从内核中读取数据到nginx中,nginx不对收到的数据进行处理。相当于丢弃包体 static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r) { //用于接收包体的临时缓冲区 u_char buffer[ngx_http_discard_buffer_size]; for ( ;; ) { //已经全部丢弃成功 if (r->headers_in.content_length_n == 0) { //设置丢弃后的读事件回调,再有读事件时,不做任何处理 r->read_event_handler = ngx_http_block_reading; return ngx_ok; } //从内核中接收包体到临时缓冲区 n = r->connection->recv(r->connection, buffer, size); //更新剩余需要接收的包体大小 r->headers_in.content_length_n -= n; } }
函数内部只是使用一个临时的缓冲区变量存放每次接收来自内核的包体数据。并没有把这部分数据保存到http请求结构中的request_body缓冲区。因此包体数据没有交给http模块,相当于被丢弃了。在所有包体从内核中接收完成时,设置http请求结构ngx_http_request_s的读事件read_event_handler回调设置为: ngx_http_block_reading, 表示再收到来自客户端的数据,则不进行任何处理了。因为已经接收完所有的包体数据,也就不需要理会来自客户端浏览器的其它数据。
三、丢弃剩余的包体
ngx_http_discarded_request_body_handler用于在一次调度中没有丢弃完所有包体,则该函数会表调用,用于丢弃剩余的包体。函数内部也会调用实际的丢弃包体函数,进行接收包体然后丢弃操作。nginx服务器做了一个优化处理,会设置一个总超时时间,如果超过这个时间都还没有丢弃完全部的包体,则会关闭这个连接。这是一种对服务器保护的措施,避免长时间的丢包操作占用服务器资源。
//功能: 第1次未能全部丢弃包体时,该函数被调用。之后有读事件时,该函数被调用 void ngx_http_discarded_request_body_handler(ngx_http_request_t *r) { //检测延迟关闭时间,如果总时长超过了lingering_time,则不再接收任何包体,这是一个总时间。 //总超时后,将直接光比连接 if (r->lingering_time) { timer = (ngx_msec_t) (r->lingering_time - ngx_time()); //已经到达了延迟关闭时间 if (timer <= 0) { //清空丢弃包体标识,表示包体已经丢弃 r->discard_body = 0; //延迟关闭开关清0 r->lingering_close = 0; ngx_http_finalize_request(r, ngx_error); return; } } //接收包体后丢弃 rc = ngx_http_read_discarded_request_body(r); //表示包体已经全部丢弃 if (rc == ngx_ok) { r->discard_body = 0; //包体已经全部接收完 r->lingering_close = 0; //清空延迟关闭标志 ngx_http_finalize_request(r, ngx_done); return; } }
ngx_http_discarded_request_body_handler这个函数是怎么被事件对象调用的呢? 在前面的文章已经分析了,ngx_connection_s读事件的回调设置为ngx_http_request_handler。 因此在读事件发生时,会回调请求结构的读回调。如果还不是不清楚这个调用过程,可以参考:
static void ngx_http_request_handler(ngx_event_t *ev) { //如果同时发生读写事件,则只有写事件才会触发。写事件优先级更高 if (ev->write) { r->write_event_handler(r); //在函数ngx_http_handler设置为ngx_http_core_run_phases } else { r->read_event_handler(r); //在函数ngx_http_process_request设置为ngx_http_block_reading } }
以上がNginxがhttpパケットボディを破棄する場合の対処方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。