nginx請求行讀取流程


nginx請求行讀取流程

在讀取數據完成之後,nginx會將讀取事件的回調方法設置為ngx_http_process_request_line(),這個方法主要有如下幾個作用:

  • 讀取客戶端請求的數據,如果客戶端數據讀取不全,則繼續監聽客戶端讀事件以讀取完整數據;
  • 解析讀取到的客戶端數據,將各個參數存儲到表徵當前請求的ngx_http_request_t結構體中;
  • 將讀事件的回調方法設置為ngx_http_process_request_headers(),以繼續處理客戶端發送來的header數據。

這裡需要說明的一點是,所謂的請求行指的是http請求報文中類似於GET /index HTTP/1.1的部分,根據http協議,這一部分下面的數據才是各個header數據,而這裡解析請求行數據的過程是不包含如何解析header數據的(這部分我們將在下一篇文章中進行講解)。

1. 請求行處理主流程

請求行處理的主流程主要是在ngx_http_process_request_line()方法中,如下是該方法的源碼:

<code>static void ngx_http_process_request_line(ngx_event_t *rev) {
ssize_t n;
ngx_int_t rc, rv;
ngx_str_t host;
ngx_connection_t *c;

ngx_http_request_t *r;

c = rev->data;
r = c->data;

if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}

rc = NGX_AGAIN;
for (;;) {
if (rc == NGX_AGAIN) {
// 這裡的ngx_http_read_request_header()方法的主要作用是返回在連接上讀取到的數據
n = ngx_http_read_request_header(r);

// n為NGX_AGAIN時,說明已經將當前事件添加到事件隊列中了,因而直接返回,而NGX_ERROR則說明
// 當前讀取失敗了,也直接返回
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}

// 這裡主要是解析請求行的數據,比如"GET /index HTTP/1.1"
rc = ngx_http_parse_request_line(r, r->header_in);

// NGX_OK表示請求行的數據是完整的,並且已經解析完成
if (rc == NGX_OK) {
r->request_line.len = r->request_end - r->request_start;
r->request_line.data = r->request_start;
r->request_length = r->header_in->pos - r->request_start;
r->method_name.len = r->method_end - r->request_start + 1;
r->method_name.data = r->request_line.data;

if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
}

// 標記與參數相關的屬性
if (ngx_http_process_request_uri(r) != NGX_OK) {
return;
}

if (r->host_start && r->host_end) {
host.len = r->host_end - r->host_start;
host.data = r->host_start;

// 校驗host,並且將host轉換為小寫形式
rc = ngx_http_validate_host(&host, r->pool, 0);

if (rc == NGX_DECLINED) {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}

if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}

// 設置用於處理當前請求的server和location塊
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return;
}

r->headers_in.server = host;
}

// NGX_HTTP_VERSION_10表示當前請求是http 1.0,這裡就是判斷當前請求是否為0.9版本的請求,
// 如果是0.9版本的請求,那麼是沒有請求頭的
if (r->http_version < NGX_HTTP_VERSION_10) {
if (r->headers_in.server.len == 0
&& ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR) {
return;
}

// 對於0.9版本的請求,直接處理請求
ngx_http_process_request(r);
return;

}

// 初始化headers鏈表
if (ngx_list_init(&r->headers_in.headers,
r->pool, 20, sizeof(ngx_table_elt_t)) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}

c->log->action = "reading client request headers";

// 將事件的處理方法設置為ngx_http_process_request_headers()方法
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}

if (rc != NGX_AGAIN) {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}

/* NGX_AGAIN: a request line parsing is still incomplete */
// 走到這裡,說明返回值是NGX_AGAIN,也即讀取請求行還未讀取完全,如果緩衝區還有可用數據,
// 則不做任何處理,否則繼續下一次循環
if (r->header_in->pos == r->header_in->end) {
// 為當前request申請大塊內存
rv = ngx_http_alloc_large_header_buffer(r, 1);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}

if (rv == NGX_DECLINED) {
r->request_line.len = r->header_in->end - r->request_start;
r->request_line.data = r->request_start;
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
return;
}
}
}
}/<code>

對於這裡的主流程,我們可以看到,其首先是檢查讀事件是否過期,如果過期了,則直接返回。如果沒有過期,則進入一個無限for循環,如下是這個循環的工作:

  • 調用ngx_http_read_request_header()方法讀取數據,在這個方法中,nginx會檢查當前連接句柄上是否存在可讀的數據,如果不存在,繼續將當前事件添加到事件框架中。由於當前事件的回調方法是上面的主流程方法,也即ngx_http_process_request_line(),因而當再次觸發讀事件時,還是會走到這裡的ngx_http_read_request_header()方法讀取數據,這也就是為什麼在ngx_http_read_request_header()方法返回NGX_AGAIN時,主流程可以直接返回而不做任何處理的原因;
  • 調用ngx_http_parse_request_line()方法解析請求行的數據,這個方法比較長,但是邏輯非常簡單,主要工作就是一個字符一個字符的比對請求行中的各個數據,然後將其設置到ngx_http_request_t中,我們這裡不對齊進行深入講解;
  • 根據前面兩個方法的返回值處理當前請求,主要分為NGX_OK、NGX_AGAIN、NGX_DECLINED和NGX_ERROR。當返回值是NGX_DECLINED時,表示請求行太長,此時會給客戶端返回414狀態碼;當返回值是NGX_ERROR時,會直接關閉當前連接,並且返回500狀態碼;而NGX_AGAIN則會繼續等待觸發下一次的事件循環;
  • 在響應碼為NGX_OK時,會設置ngx_http_request_t中的相關字段,並且會檢查請求行數據的合法性。需要注意的是,在這個分支最後檢查完時會分情況,如果當前http請求版本為0.9版本,由於0.9版本沒有請求頭,因而直接調用ngx_http_process_request()進入http模塊的11個階段進行處理。如果當前版本是1.0以上,由於其是需要傳請求頭的,因而會調用ngx_http_process_request_headers()方法讀取並處理請求頭數據。

2. 請求行數據讀取

我們這裡主要看一下nginx是如何讀取請求行數據的,如下是ngx_http_read_request_header()方法的源碼:

<code>static 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->connection;
rev = c->read;

// 計算當前還有多少數據未處理
n = r->header_in->last - r->header_in->pos;

// 如果n大於0,說明還有讀取到的數據未處理,則直接返回n
if (n > 0) {
return n;
}

// 走到這裡,說明當前讀取到的數據都已經處理完了,因而這裡會進行判斷,如果當前事件的ready參數為1,
// 則表示當前連接的句柄上存儲還未讀取的數據,因而調用c->recv()方法讀取數據,否則繼續將當前事件
// 添加到事件隊列中,並且繼續監聽當前連接句柄的讀事件
// 這裡的c->recv()方法實際指向的是ngx_unix_recv()方法,主要作用就是讀取指定連接上的數據
if (rev->ready) {
// 在連接文件描述符上讀取數據

n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last);
} else {
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);
}

if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}

return NGX_AGAIN;
}

// 如果n為0,說明客戶端關閉了連接
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection");
}

// 如果客戶端關閉了連接或者讀取異常,則回收當前的request結構體
if (n == 0 || n == NGX_ERROR) {
c->error = 1;
c->log->action = "reading client request headers";
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}

// 更新當前讀取到的數據指針
r->header_in->last += n;

return n;
}/<code>

這裡讀取請求行的數據流程整體也比較簡單,其主要流程如下:

  • 檢查當前讀取緩衝區中是否存在還未處理的數據,如果存在,則直接返回,以讓後面的ngx_http_parse_request_line()方法解析這些數據;
  • 檢查當前的事件是否處於可執行狀態,是則調用c->recv()方法從連接的句柄上讀取數據,返回值n表示讀取到的數據大小;
  • 如果當前事件處於不可執行狀態,則將當前事件添加到事件框架中,並且在epoll句柄上為當前連接註冊讀事件;
  • 在第二步中,如果讀取數據的返回值是NGX_ERROR或者0(0表示客戶端斷開了連接),則給客戶端返回400響應碼。

可以看到,這裡對於請求行數據的處理過程,簡單的說就是如果存在數據就讀取,如果不存在則註冊當前的讀取事件。


分享到:


相關文章: