nginx リクエスト ヘッダー データ読み取りプロセスの詳細な説明

nginx リクエスト ヘッダー データ読み取りプロセスの詳細な説明

前回の記事では、nginx がリクエスト ラインのデータを読み取って、リクエスト ラインを解析する方法について説明しました。この記事では、nginx がクライアントから送信されたリクエスト ヘッダー データを読み取って解析する方法について主に説明します。本質的には、リクエスト ラインとリクエスト ヘッダーのデータ読み取りプロセスは基本的に同じです。これは、どちらも断続的なデータ ストリームからデータを読み取る方法と、そのデータを処理する方法という問題に直面しているためです。

1. リクエストヘッダー読み取りメインプロセス

リクエスト ヘッダーの読み取りプロセスを紹介する前に、まず http リクエスト メッセージの例を示します。

POST /web/book/read HTTP/1.1
ホスト: ローカルホスト
接続: キープアライブ
コンテンツの長さ: 365
受け入れる: application/json、text/plain、*/*

例の最初のデータ行はリクエスト行であり、次の行はリクエスト ヘッダーです。各リクエスト ヘッダーは、名前: 値の形式で組み立てられ、各リクエスト ヘッダーは 1 行を占めます。 リクエスト ラインの読み取りプロセスを紹介した前回の記事では、リクエスト ラインが読み取られると、nginx は現在の読み取りイベントのコールバック関数を ngx_http_process_request_headers() メソッドに変更し、このメソッドを直接呼び出してリクエスト ヘッダー データの読み取りを試行することを説明しました。このメソッドは、リクエスト ライン データを読み取るメイン プロセスです。以下はこのメソッドのソース コードです。

/**
 * クライアントから送信されたヘッダーデータを解析する*/
静的 void ngx_http_process_request_headers(ngx_event_t *rev) {
 u_char *p;
 size_t 長さ;
 ssize_t n;
 ngx_int_t rc、rv;
 ngx_table_elt_t *h;
 ngx_connection_t *c;
 ngx_http_header_t *hh;
 ngx_http_request_t *r;
 ngx_http_core_srv_conf_t *cscf;
 ngx_http_core_main_conf_t *cmcf;

 c = rev->データ;
 r = c->データ;

 もし (rev->timedout) {
  ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "クライアントがタイムアウトしました");
  c->タイムアウト = 1;
  ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
  戻る;
 }

 ngx_http_get_module_main_conf を r に設定します。
 rc = NGX_AGAIN;

 のために (;;) {
  rc == NGX_AGAINの場合{
   // 現在のヘッダーバッファに空きスペースがない場合は、新しいスペースを申請します if (r->header_in->pos == r->header_in->end) {
    // 新しいスペースを申請する rv = ngx_http_alloc_large_header_buffer(r, 0);
    rv == NGX_ERRORの場合{
     ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
     戻る;
    }

    // クライアントから送信されたヘッダーが長すぎて、large_client_header_buffers で指定された最大サイズを超えています if (rv == NGX_DECLINED) {
     p = r->ヘッダー名の開始;
     r->lingering_close = 1;
     (p == NULL)の場合{
      ngx_log_error(NGX_LOG_INFO, c->log, 0, "クライアントが大きすぎるリクエストを送信しました");
      ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
      戻る;
     }

     len = r->header_in->end - p;
     長さが NGX_MAX_ERROR_STR - 300 以上のとき
      長さ = NGX_MAX_ERROR_STR - 300;
     }

     ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
     戻る;
    }
   }

   // 接続されたクライアントから新しく送信されたデータを読み取ろうとします n = ngx_http_read_request_header(r);
   (n == NGX_AGAIN || n == NGX_ERROR) の場合 {
    戻る;
   }
  }

  cscf = ngx_http_get_module_srv_conf(r、ngx_http_core_module);
  // ここでは主に読み取ったデータを変換します rc = ngx_http_parse_header_line(r, r->header_in, cscf->underscores_in_headers);

  // NGX_OKは、解析とヘッダーデータの取得が成功したことを示します。if (rc == NGX_OK) {
   r->request_length += r->header_in->pos - r->header_name_start;
   // 無効なヘッダーをフィルタリングする
   (r->無効なヘッダー&&cscf->無効なヘッダーを無視){
    続く;
   }

   // ヘッダーを保存する構造体を作成します。h = ngx_list_push(&r->headers_in.headers);
   h == NULLの場合{
    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
    戻る;
   }

   h->ハッシュ = r->ヘッダーハッシュ;
   // ヘッダー名をハッシュテーブルのキーとして使用します
   h->key.len = r->header_name_end - r->header_name_start;
   h->key.data = r->header_name_start;
   h->key.data[h->key.len] = '\0';

   // ヘッダー値をハッシュテーブルの値として使用します
   h->value.len = r->header_end - r->header_start;
   h->value.data = r->header_start;
   h->値.data[h->値.len] = '\0';

   h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
   h->lowcase_key == NULLの場合{
    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
    戻る;
   }

   h->key.len == r->lowcase_indexの場合{
    ngx_memcpy(h->lowcase_key、r->lowcase_header、h->key.len);
   } それ以外 {
    ngx_strlow(h->lowcase_key、h->key.data、h->key.len);
   }

   // headers_in_hash はすべてのヘッダーを格納します。ここでは、現在のクライアントから送信されたヘッダーが有効なヘッダーであるかどうかを確認します。
   hh = ngx_hash_find(&cmcf->headers_in_hash、h->hash、h->lowcase_key、h->key.len);
   // ここでのハンドラは、ngx_http_headers_in 内の各ヘッダーに対して定義された処理メソッドです。各ヘッダーの // handler() メソッドによって処理された後、クライアントから送信されたヘッダーは、r->headers_in 構造内のさまざまな属性に変換されます if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
    戻る;
   }

   続く;
  }

  // NGX_HTTP_PARSE_HEADER_DONE はすべてのヘッダーが処理されたことを意味します if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
   r->request_length += r->header_in->pos - r->header_name_start;
   r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
   // クライアントから送信されたヘッダーデータの正当性を確認します rc = ngx_http_process_request_header(r);
   rc が NGX_OK の場合 {
    戻る;
   }

   ngx_http_process_request() は、リクエストを処理するプロセスです。
   戻る;
  }

  // NGX_AGAIN は、読み取ったヘッダー行のデータが不完全であり、さらに読み取る必要があることを示します if (rc == NGX_AGAIN) {
   続く;
  }
  
  ngx_log_error(NGX_LOG_INFO, c->log, 0, "クライアントが無効なヘッダー行を送信しました");
  ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
  戻る;
 }
}

ここでのリクエスト ヘッダーの読み取りは、主に次の手順に分かれます。

  • まず、現在の読み取りイベントがタイムアウトしていないかどうかを確認します。タイムアウトしている場合は、現在の接続を直接閉じます。
  • r->header_in->pos == r->header_in->end が真かどうかを判断します。これは主に、現在の読み取りバッファに新しく読み取ったデータを格納できるメモリ領域があるかどうかを確認するためです。ない場合は、メモリ プールから新しいメモリ領域を申請します。
  • ngx_http_read_request_header() メソッドを呼び出して、現在の接続ハンドル上のデータを読み取ります。戻り値が 0 より大きい場合は、読み取られたデータの長さを示します。0 に等しい場合は、クライアントが切断されたことを意味します。NGX_ERROR の場合は、読み取り中に例外が発生したことを意味します。NGX_AGAIN の場合は、今回はデータが読み取られなかったため、新しいデータを読み取る必要があります。ご覧のとおり、ここではまず戻り値が NGX_AGAIN かどうかを判断します。そうである場合は、他の処理を行わずにそのまま戻ります。これは主に、現在の読み取りイベントのコールバック関数がまだ ngx_http_process_request_headers() であるためです。新しい読み取りイベントがトリガーされると、引き続き ngx_http_read_request_header() を呼び出してデータを再度読み取ります。一方、ngx_http_read_request_header() メソッドでは、戻り値が NGX_AGAIN の場合、現在の読み取りイベントがイベント キューに再度追加され、現在の接続の epoll ハンドルに読み取りイベントが登録されます。
  • ngx_http_parse_header_line() メソッドを呼び出して、読み取られたリクエスト ヘッダー データを解析します。このメソッドの各呼び出しでは 1 つのリクエスト ヘッダーのみが解析されますが、無限の for ループと継続的なイベント トリガー メカニズムの後、最終的にすべてのリクエスト ヘッダー データが読み取られることに注意してください。
  • ngx_http_parse_header_line() メソッドの戻り値に従って、それが NGX_OK の場合、新しく読み取られたヘッダーは r->headers_in.headers リストに格納されます。
  • ngx_http_parse_header_line() メソッドの戻り値が NGX_HTTP_PARSE_HEADER_DONE の場合、すべてのヘッダーが正常に読み取られたことを意味します。このとき、まず ngx_http_process_request_header() メソッドが呼び出され、読み取られたヘッダーの正当性が確認され、次に ngx_http_process_request() メソッドが呼び出され、nginx の http モジュールの 11 段階が開始されます。このメソッドの実装原理については、次の記事で説明します。

2. リクエストヘッダーデータの読み取り

ご覧のとおり、リクエスト ヘッダーを読み取るための主なメソッドは ngx_http_read_request_header() と ngx_http_parse_header_line() の 2 つあります。ここでの 2 番目のメソッドは比較的長いですが、そのロジックは非常に単純です。主に、読み取ったデータを解析して、完全なリクエスト ヘッダー (名前: 値の形式で 1 ​​行を占める) を形成できるかどうかを確認します。形成できる場合は NGX_OK を返し、そうでない場合は、データの読み取りを続行することを期待して NGX_AGAIN を返します。ここではこの方法については説明しません。読者は自分でソース コードを読むことができます。ここでは主に、ngx_http_read_request_header() メソッドがクライアントから送信されたリクエスト ヘッダー データを読み取る方法について説明します。

静的ssize_t ngx_http_read_request_header(ngx_http_request_t *r) {
 ssize_t n;
 ngx_event_t *rev;
 ngx_connection_t *c;
 ngx_http_core_srv_conf_t *cscf;

 c = r->接続;
 rev = c->読み取り;

 // まだ処理されていないデータの量を計算しま n = r->header_in->last - r->header_in->pos;

 // nが0より大きい場合は、まだ処理されていないデータがあることを意味するので、nを直接返します
 (n > 0) の場合 {
  n を返します。
 }

 // ここに行くということは、現在読み込まれているデータが処理されたということなので、ここで判断が行われます。現在のイベントのreadyパラメータが1の場合、
 // これは、現在の接続のハンドルに未読データが格納されているため、c->recv() メソッドが呼び出されてデータが読み取られることを意味します。それ以外の場合は、現在のイベントがイベント キューに追加され、現在の接続ハンドルの読み取りイベントが引き続き監視されます if (rev->ready) {
  // 接続ファイル記述子上のデータを読み取る n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last);
 } それ以外 {
  n = NGX_AGAIN;
 }

 // n が NGX_AGAIN の場合、現在のイベントをイベント リスナーに追加し、現在の epoll ハンドルの読み取りイベントをリッスンし続けます if (n == NGX_AGAIN) {
  if (!rev->timer_set) {
   cscf = ngx_http_get_module_srv_conf(r、ngx_http_core_module);
   ngx_add_timer(rev、cscf->client_header_timeout);
  }

  ngx_handle_read_event(rev, 0) != NGX_OK の場合 {
   ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
   NGX_ERROR を返します。
  }

  NGX_AGAIN を返します。
 }

 // nが0の場合、クライアントが接続を閉じたことを意味します if (n == 0) {
  ngx_log_error(NGX_LOG_INFO, c->log, 0, "クライアントが接続を途中で閉じました");
 }

 // クライアントが接続を閉じるか異常な読み取りをした場合は、現在のリクエスト構造をリサイクルします if (n == 0 || n == NGX_ERROR) {
  c->エラー = 1;
  c->log->action = "クライアント要求ヘッダーを読み取っています";
  ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
  NGX_ERROR を返します。
 }

 // 現在読み取られているデータ ポインターを更新します r->header_in->last += n;
 n を返します。
}



ここでのリクエスト ヘッダー データの読み取りは、主に次の手順に分かれています。

  • 現在のバッファに未処理のデータがあるかどうかを判断します。ある場合は、直接戻ります。読み込まれていないデータが存在する主な理由は、以前にリクエスト ライン データを読み取るプロセスで、リクエスト ヘッダー データの一部または全部が読み取られる可能性があるため、ここでチェックが実行されるためです。
  • 現在の読み取りイベントの準備ができているかどうかを判断します。準備ができている場合は、c->recv() メソッドを呼び出して、現在の接続ハンドル上のデータを読み取ります。
  • 現在の読み取りイベントの準備ができていない場合、現在の読み取りイベントをイベント キューに再度追加し、現在の接続の epoll ハンドルに読み取りイベントを登録します。
  • 2 番目のステップの戻り値が判定されます。0 の場合は、クライアントが切断されたことを意味します。NGX_ERROR の場合は、データの読み取りが異常であることを意味します。どちらの場合も、現在の接続は閉じられ、400 ステータス コードがクライアントに返されます。戻り値が NGX_AGAIN の場合は、手順 3 に進み、読み取りイベントのリッスンを続行します。戻り値が 0 より大きい場合、読み取りが成功したことを意味し、この 0 より大きい値は読み取られたデータの長さを示します。
  • 読み取ったデータを格納するバッファのポインタデータを更新します。

3. まとめ

この記事では、主に nginx がリクエスト ヘッダーを読み取って解析する方法について説明し、データを読み取るためのメイン プロセス コードと読み取りの詳細な手順に焦点を当てます。

nginx リクエスト ヘッダー データ読み取りプロセスの詳細な説明に関するこの記事はこれで終わりです。より関連性の高い nginx リクエスト ヘッダー データ読み取りコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Nginx と GeoIP モジュールを使用して IP の地域情報を読み取る方法
  • NodeJS を使用して Nginx エラー ログを読み取り、分析する方法
  • リクエストボディを読み込むためのNginx設定機能の詳細な説明

<<:  React クラスコンポーネントのライフサイクルと実行順序

>>:  mysql8.0.18 で winx64 をインストールするための詳細なチュートリアル (画像とテキスト付き)

推薦する

モバイル開発チュートリアル: ピクセル表示の問題の概要

序文モバイル端末の開発の過程で、モバイル端末のディスプレイはデスクトップ端末のディスプレイとは一般的...

MySQL 分離列とプレフィックスインデックスの使用の概要

目次データ列を分離するプレフィックスインデックスとインデックスの選択性データ列を分離するMySQL ...

JSで実施された機雷掃海プロジェクトの概要

この記事では、JS掃海プロジェクトの概要を参考までに紹介します。具体的な内容は次のとおりです。プロジ...

MySQL の自己結合重複排除に関する注意事項

機能シナリオを簡単に説明しましょう。データ行フィールドは次のとおりです。名前開始日時タイプこの表では...

バックアップ データをインポートするときに innodb_index_stats がエラーを報告する場合の主キー競合の解決方法

障害の説明percona5.6、mysqldump フルバックアップ、バックアップデータのインポート...

GaussDB for MySQL パフォーマンス最適化の詳細な説明

目次背景インスピレーションは人生から生まれる速達配送の最適化原則GaussDB の最適化 (MySQ...

高度な CSS の 3 つの方法を使用して複数行の省略を実装するサンプル コード

序文これは古くからの要望ですが、オンラインで解決策を探している人はまだ多く、特に検索結果の上位にラン...

CSS 要素を表示および非表示にする 9 つの方法

Web ページの制作では、要素の表示と非表示は非常に一般的な要件です。この記事では、要素を表示したり...

HTML テーブルタグチュートリアル (19): 行タグ

<TR> タグの属性は、次の表に示すように、テーブル内の各行のプロパティを設定するために...

Zabbix の psk 暗号化と zabbix_get 値の組み合わせ

Zabbix バージョン 3.0 以降、Zabbix サーバー、Zabbix プロキシ、Zabbix...

CSS3はリストの無限スクロール/カルーセル効果を実現します

効果プレビューアイデア現在のリストを最後の項目までスクロールし、すぐに最初の項目に戻ります。問題1....

Vue3+Vue-cli4 プロジェクトで Tencent スライダー検証コードを使用する方法

導入:従来の画像検証コードと比較して、スライダー検証コードには次の利点があります。サーバーは検証コー...

MySQL クロスデータベーストランザクション XA 操作の例

この記事では、例を使用して、MySQL のデータベース間トランザクション XA 操作について説明しま...

MySQL SQL文の特殊処理文のまとめ(必読)

1.テーブル全体を更新します。データ行の列の値が空の場合は、別の列フィールドの値と同じにします。 ...

CentOSにDockerをインストールする方法

ここでは比較的簡単なインストール方法のみを紹介します。 1. yumを使用してインストールするyum...