catalog
1. Nginx原始碼結構 2. HTTP Request Header解析流程 3. HTTP Request Body解析流程
1. Nginx原始碼結構
1. core:Nginx的核心原始碼,包括常用資料結構的以及Nginx核心實現的核心程式碼 2. event:Nginx事件驅動模型,以及定時器的實現相關程式碼 3. http:Nginx實現http伺服器相關的程式碼; 4. mail:Nginx實現郵件代理伺服器相關的程式碼 5. misc:輔助程式碼,測試C++頭的相容性,以及對Google_PerfTools的支援 6. os:不同體系統結構所提供的系統函式的封裝,提供對外統一的系統呼叫介面
本文的關注重點在於nginx的http模組,http目錄和event目錄一樣,通用包含了模組實現原始碼的module目錄檔案以及一些結構定義、初始化、網路連線建立、管理、關閉,以及資料包解析、伺服器組管理等功能的原始碼檔案。module目錄檔案實現了HTTP模組的功能
Relevant Link:
http://blog.csdn.net/chenhanzhun/article/details/42742097
2. HTTP Request Header解析流程
Method SP Request-URI SP HTTP-Version CRLF general-header request-header entity-header
關於HTTP請求報文的詳細格式,請參閱另一篇文章
http://www.cnblogs.com/LittleHann/p/5057295.html
0x1: HTTP包接收事件響應
nginx是基於非同步事件響應模型的HTTP Server,nginx在初始化階段,具體是在init process階段的ngx_event_process_init函式中會為每一個監聽套接字分配一個連線結構(ngx_connection_t),並將該連線結構的讀事件成員(read)的事件處理函式設定為ngx_event_accept,並且如果沒有使用accept互斥鎖的話,在這個函式中會將該讀事件掛載到nginx的事件處理模型上(poll或者epoll等),反之則會等到init process階段結束,在工作程式的事件處理迴圈中,某個程式搶到了accept鎖才能掛載該讀事件
\nginx-1.7.4\src\event\ngx_event.c
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ngx_uint_t m, i; ngx_event_t *rev, *wev; ngx_listening_t *ls; ngx_connection_t *c, *next, *old; ngx_core_conf_t *ccf; ngx_event_conf_t *ecf; ngx_event_module_t *module; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module); if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) { ngx_use_accept_mutex = 1; ngx_accept_mutex_held = 0; ngx_accept_mutex_delay = ecf->accept_mutex_delay; } else { ngx_use_accept_mutex = 0; } #if (NGX_WIN32) /* * disable accept mutex on win32 as it may cause deadlock if * grabbed by a process which can't accept connections */ ngx_use_accept_mutex = 0; #endif #if (NGX_THREADS) ngx_posted_events_mutex = ngx_mutex_init(cycle->log, 0); if (ngx_posted_events_mutex == NULL) { return NGX_ERROR; } #endif /* 初始化用來管理所有定時器的紅黑樹 */ if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { return NGX_ERROR; } /* 初始化事件模型 */ for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_EVENT_MODULE) { continue; } if (ngx_modules[m]->ctx_index != ecf->use) { continue; } module = ngx_modules[m]->ctx; if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { /* fatal */ exit(2); } break; } #if !(NGX_WIN32) if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) { struct sigaction sa; struct itimerval itv; ngx_memzero(&sa, sizeof(struct sigaction)); sa.sa_handler = ngx_timer_signal_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGALRM, &sa, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "sigaction(SIGALRM) failed"); return NGX_ERROR; } itv.it_interval.tv_sec = ngx_timer_resolution / 1000; itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000; itv.it_value.tv_sec = ngx_timer_resolution / 1000; itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000; if (setitimer(ITIMER_REAL, &itv, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed"); } } if (ngx_event_flags & NGX_USE_FD_EVENT) { struct rlimit rlmt; if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "getrlimit(RLIMIT_NOFILE) failed"); return NGX_ERROR; } cycle->files_n = (ngx_uint_t) rlmt.rlim_cur; cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n, cycle->log); if (cycle->files == NULL) { return NGX_ERROR; } } #endif cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log); if (cycle->connections == NULL) { return NGX_ERROR; } c = cycle->connections; cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->read_events == NULL) { return NGX_ERROR; } rev = cycle->read_events; for (i = 0; i < cycle->connection_n; i++) { rev[i].closed = 1; rev[i].instance = 1; #if (NGX_THREADS) rev[i].lock = &c[i].lock; rev[i].own_lock = &c[i].lock; #endif } cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->write_events == NULL) { return NGX_ERROR; } wev = cycle->write_events; for (i = 0; i < cycle->connection_n; i++) { wev[i].closed = 1; #if (NGX_THREADS) wev[i].lock = &c[i].lock; wev[i].own_lock = &c[i].lock; #endif } i = cycle->connection_n; next = NULL; do { i--; c[i].data = next; c[i].read = &cycle->read_events[i]; c[i].write = &cycle->write_events[i]; c[i].fd = (ngx_socket_t) -1; next = &c[i]; #if (NGX_THREADS) c[i].lock = 0; #endif } while (i); cycle->free_connections = next; cycle->free_connection_n = cycle->connection_n; /* for each listening socket */ /* 為每個監聽套接字分配一個連線結構 */ ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { c = ngx_get_connection(ls[i].fd, cycle->log); if (c == NULL) { return NGX_ERROR; } c->log = &ls[i].log; c->listening = &ls[i]; ls[i].connection = c; rev = c->read; rev->log = c->log; /* 標識此讀事件為新請求連線事件 */ rev->accept = 1; #if (NGX_HAVE_DEFERRED_ACCEPT) rev->deferred_accept = ls[i].deferred_accept; #endif if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) { if (ls[i].previous) { /* * delete the old accept events that were bound to * the old cycle read events array */ old = ls[i].previous->connection; if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT) == NGX_ERROR) { return NGX_ERROR; } old->fd = (ngx_socket_t) -1; } } #if (NGX_WIN32) if (ngx_event_flags & NGX_USE_IOCP_EVENT) { ngx_iocp_conf_t *iocpcf; rev->handler = ngx_event_acceptex; if (ngx_use_accept_mutex) { continue; } if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) { return NGX_ERROR; } ls[i].log.handler = ngx_acceptex_log_error; iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module); if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex) == NGX_ERROR) { return NGX_ERROR; } } else { rev->handler = ngx_event_accept; if (ngx_use_accept_mutex) { continue; } if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } } #else /* 將讀事件結構的處理函式設定為ngx_event_accept */ rev->handler = ngx_event_accept; /* 如果使用accept鎖的話,要在後面搶到鎖才能將監聽控制程式碼掛載上事件處理模型上 */ if (ngx_use_accept_mutex) { continue; } /* 否則,將該監聽控制程式碼直接掛載上事件處理模型 */ if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { if (ngx_add_conn(c) == NGX_ERROR) { return NGX_ERROR; } } else { if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } } #endif } return NGX_OK; }
處理流程如下
1. 一個使用者在瀏覽器的位址列內輸入一個域名,並且域名解析伺服器將該域名解析到一臺由nginx監聽的伺服器上,瀏覽器會構造對應格式的HTTP請求報文,傳送給目標Nginx Server 2. 當一個Nginx工作程式在某個時刻將監聽事件掛載上事件處理模型之後,nginx就可以正式的接收並處理客戶端過來的請求了 3. nginx的事件處理模型接收到這個讀事件之後,會速度交由之前註冊好的事件處理函式ngx_event_accept來處理
\nginx-1.7.4\src\event\ngx_event_accept.c
void ngx_event_accept(ngx_event_t *ev) { socklen_t socklen; ngx_err_t err; ngx_log_t *log; ngx_uint_t level; ngx_socket_t s; ngx_event_t *rev, *wev; ngx_listening_t *ls; ngx_connection_t *c, *lc; ngx_event_conf_t *ecf; u_char sa[NGX_SOCKADDRLEN]; #if (NGX_HAVE_ACCEPT4) static ngx_uint_t use_accept4 = 1; #endif if (ev->timedout) { if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { return; } ev->timedout = 0; } ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module); if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { ev->available = 1; } else if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) { ev->available = ecf->multi_accept; } lc = ev->data; ls = lc->listening; ev->ready = 0; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, "accept on %V, ready: %d", &ls->addr_text, ev->available); do { socklen = NGX_SOCKADDRLEN; #if (NGX_HAVE_ACCEPT4) if (use_accept4) { s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, SOCK_NONBLOCK); } else { s = accept(lc->fd, (struct sockaddr *) sa, &socklen); } #else //nginx呼叫accept函式,從已連線佇列得到一個連線以及對應的套接字 //配一個連線結構(ngx_connection_t),並將新得到的套接字儲存在該連線結構中 s = accept(lc->fd, (struct sockaddr *) sa, &socklen); #endif if (s == (ngx_socket_t) -1) { err = ngx_socket_errno; if (err == NGX_EAGAIN) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err, "accept() not ready"); return; } level = NGX_LOG_ALERT; if (err == NGX_ECONNABORTED) { level = NGX_LOG_ERR; } else if (err == NGX_EMFILE || err == NGX_ENFILE) { level = NGX_LOG_CRIT; } #if (NGX_HAVE_ACCEPT4) ngx_log_error(level, ev->log, err, use_accept4 ? "accept4() failed" : "accept() failed"); if (use_accept4 && err == NGX_ENOSYS) { use_accept4 = 0; ngx_inherited_nonblocking = 0; continue; } #else ngx_log_error(level, ev->log, err, "accept() failed"); #endif if (err == NGX_ECONNABORTED) { if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { ev->available--; } if (ev->available) { continue; } } if (err == NGX_EMFILE || err == NGX_ENFILE) { if (ngx_disable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { return; } if (ngx_use_accept_mutex) { if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); ngx_accept_mutex_held = 0; } ngx_accept_disabled = 1; } else { ngx_add_timer(ev, ecf->accept_mutex_delay); } } return; } #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); #endif ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; c = ngx_get_connection(s, ev->log); if (c == NULL) { if (ngx_close_socket(s) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno, ngx_close_socket_n " failed"); } return; } #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, 1); #endif c->pool = ngx_create_pool(ls->pool_size, ev->log); if (c->pool == NULL) { ngx_close_accepted_connection(c); return; } c->sockaddr = ngx_palloc(c->pool, socklen); if (c->sockaddr == NULL) { ngx_close_accepted_connection(c); return; } ngx_memcpy(c->sockaddr, sa, socklen); //分配日誌結構,並儲存在其中,以便後續的日誌系統使用 log = ngx_palloc(c->pool, sizeof(ngx_log_t)); if (log == NULL) { ngx_close_accepted_connection(c); return; } /* set a blocking mode for aio and non-blocking mode for others */ //初始化連線相應的io收發函式,具體的io收發函式和使用的事件模型及作業系統相關 if (ngx_inherited_nonblocking) { if (ngx_event_flags & NGX_USE_AIO_EVENT) { if (ngx_blocking(s) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno, ngx_blocking_n " failed"); ngx_close_accepted_connection(c); return; } } } else { if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) { if (ngx_nonblocking(s) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno, ngx_nonblocking_n " failed"); ngx_close_accepted_connection(c); return; } } } *log = ls->log; c->recv = ngx_recv; c->send = ngx_send; c->recv_chain = ngx_recv_chain; c->send_chain = ngx_send_chain; c->log = log; c->pool->log = log; c->socklen = socklen; c->listening = ls; //將本地套介面地址儲存在local_sockaddr欄位,因為這個值是從監聽結構ngx_listening_t中可得,而監聽結構中儲存的只是配置檔案中設定的監聽地址 //但是配置的監聽地址可能是萬用字元*,即監聽在所有的地址上,所以連線中儲存的這個值最終可能還會變動,會被確定為真正的接收地址 c->local_sockaddr = ls->sockaddr; c->local_socklen = ls->socklen; c->unexpected_eof = 1; #if (NGX_HAVE_UNIX_DOMAIN) if (c->sockaddr->sa_family == AF_UNIX) { c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED; c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; #if (NGX_SOLARIS) /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */ c->sendfile = 0; #endif } #endif rev = c->read; wev = c->write; //nginx預設連線第一次為可寫 wev->ready = 1; if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) { /* rtsig, aio, iocp */ //將連線的寫事件設定為已就緒,即設定ready為1 rev->ready = 1; } //如果監聽套接字設定了TCP_DEFER_ACCEPT屬性,則表示該連線上已經有資料包過來,於是設定讀事件為就緒 if (ev->deferred_accept) { rev->ready = 1; #if (NGX_HAVE_KQUEUE) rev->available = 1; #endif } rev->log = log; wev->log = log; /* * TODO: MT: - ngx_atomic_fetch_add() * or protection by critical section or light mutex * * TODO: MP: - allocated in a shared memory * - ngx_atomic_fetch_add() * or protection by critical section or light mutex */ c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); #endif #if (NGX_THREADS) rev->lock = &c->lock; wev->lock = &c->lock; rev->own_lock = &c->lock; wev->own_lock = &c->lock; #endif if (ls->addr_ntop) { //將sockaddr欄位儲存的對端地址格式化為可讀字串,並儲存在addr_text欄位 c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); if (c->addr_text.data == NULL) { ngx_close_accepted_connection(c); return; } c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, c->addr_text.data, ls->addr_text_max_len, 0); if (c->addr_text.len == 0) { ngx_close_accepted_connection(c); return; } } #if (NGX_DEBUG) { ngx_str_t addr; struct sockaddr_in *sin; ngx_cidr_t *cidr; ngx_uint_t i; u_char text[NGX_SOCKADDR_STRLEN]; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; ngx_uint_t n; #endif cidr = ecf->debug_connection.elts; for (i = 0; i < ecf->debug_connection.nelts; i++) { if (cidr[i].family != (ngx_uint_t) c->sockaddr->sa_family) { goto next; } switch (cidr[i].family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) c->sockaddr; for (n = 0; n < 16; n++) { if ((sin6->sin6_addr.s6_addr[n] & cidr[i].u.in6.mask.s6_addr[n]) != cidr[i].u.in6.addr.s6_addr[n]) { goto next; } } break; #endif #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) c->sockaddr; if ((sin->sin_addr.s_addr & cidr[i].u.in.mask) != cidr[i].u.in.addr) { goto next; } break; } log->log_level = NGX_LOG_DEBUG_CONNECTION|NGX_LOG_DEBUG_ALL; break; next: continue; } if (log->log_level & NGX_LOG_DEBUG_EVENT) { addr.data = text; addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, "*%uA accept: %V fd:%d", c->number, &addr, s); } } #endif if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) { if (ngx_add_conn(c) == NGX_ERROR) { ngx_close_accepted_connection(c); return; } } log->data = NULL; log->handler = NULL; ls->handler(c); if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { ev->available--; } } while (ev->available); }
0x2: HTTP Header解析
Nginx將連線的讀事件的處理函式設定為ngx_http_process_request_line函式,這個函式用來解析請求行
\nginx-1.7.4\src\http\ngx_http_request.c
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; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http process request line"); //將當前時間保持在start_sec和start_msec欄位,這個時間是該請求的起始時刻,將被用來計算一個請求的處理時間(request time) //nginx使用的這個起始點和apache略有差別,nginx中請求的起始點是接收到客戶端的第一個資料包開始,而apache則是接收到客戶端的整個request line後開始算起 if (rev->timedout) { //ngx_http_process_request_line函式的主要作用即是解析請求行,同樣由於涉及到網路IO操作,即使是很短的一行請求行可能也不能被一次讀完 //所以在之前的ngx_http_init_request函式中,ngx_http_process_request_line函式被設定為讀事件的處理函式,它也只擁有一個唯一的ngx_event_t *型別引數 //並且在函式的開頭,同樣需要判斷是否是超時事件,如果是的話,則關閉這個請求和連線;否則開始正常的解析流程 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); if (n == NGX_AGAIN || n == NGX_ERROR) { return; } } //如果ngx_http_read_request_header函式正常的讀取到了資料,ngx_http_process_request_line函式將呼叫ngx_http_parse_request_line函式來解析HTTP包頭 rc = ngx_http_parse_request_line(r, r->header_in); if (rc == NGX_OK) { /* 如果返回了NGX_OK,則表示請求行被正確的解析出來了 1. 這時先記錄好請求行的起始地址以及長度 2. 並將請求uri的path和引數部分儲存在請求結構的uri欄位,請求方法起始位置和長度儲存在method_name欄位,http版本起始位置和長度記錄在http_protocol欄位 3. 還要從uri中解析出引數以及請求資源的擴充名,分別儲存在args和exten欄位。接下來將要解析請求頭 */ /* the request line has been parsed successfully */ 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; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); 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; rc = ngx_http_validate_host(&host, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid host in request line"); 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; } if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { return; } //解析完請求行之後,如果請求行的uri裡面包含了域名部分,則將其保持在請求結構的headers_in成員的server欄位,headers_in用來儲存所有請求頭 r->headers_in.server = host; } //檢查進來的請求是否使用的是http0.9,如果是的話則使用從請求行裡得到的域名 if (r->http_version < NGX_HTTP_VERSION_10) { if (r->headers_in.server.len == 0 //呼叫ngx_http_find_virtual_server()函式來查詢用來處理該請求的虛擬伺服器配置,之前通過埠和地址找到的預設配置不再使用 && ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR) { return; } //找到相應的配置之後,則直接呼叫ngx_http_process_request()函式處理該請求,因為http0.9是最原始的http協議,它裡面沒有定義任何請求頭,顯然就不需要讀取請求頭的操作 ngx_http_process_request(r); return; } 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"; rev->handler = ngx_http_process_request_headers; ngx_http_process_request_headers(rev); return; } /* 如果返回NGX_AGAIN,則需要判斷一下是否是由於緩衝區空間不夠,還是已讀資料不夠 1. 如果是緩衝區大小不夠了,nginx會呼叫ngx_http_alloc_large_header_buffer函式來分配另一塊大緩衝區,如果大緩衝區還不夠裝下整個請求行,nginx則會返回414錯誤給客戶端 2. 否則分配了更大的緩衝區並拷貝之前的資料之後,繼續呼叫 */ if (rc != NGX_AGAIN) { /* there was error while a request line parsing */ ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } /* NGX_AGAIN: a request line parsing is still incomplete */ //nginx在處理request的時候,會預先分配一個client_header_buffer_size的buf,如果不夠就會分配large_client_header_buffers的buf //對於request line和每個header而言,每一個不應該超過large buf,所有的總和也不應該超過large buf size*num。Http 1.1的pipeline請求 //如果前面的請求分配的large buf,那麼後面的請求會繼承使用這個large buf分配的空間,當large buf 不夠了再去主動分配large buf if (r->header_in->pos == r->header_in->end) { /* nginx在接收到客戶端得請求之後,就開始解析http請求,也就是解析http header,需要分配一段buf來接收這些資料 nginx並不知道這個http header的大小,在nginx配置中client_header_buffer_size和large_client_header_buffers這兩個配置項起到了作用 1. client_header_buffer_size 1k 2. large_client_header_buffers 4 8k client_header_buffer_size預設是1024位元組。large_client_header_buffers預設最大分配4組8192位元組的buf,每次分配一個buf 1. nginx處理http header的過程是先處理request line(http 請求的第一行) 2. 然後在處理每一個header 3. 那麼處理request line的過程首先會分配client_header_buffer_size大小的空間 4. 如果這個空間不夠,那麼再分配一個large_client_header_buffers的空間,然後把之前的client_header_buffer_size copy到大buffer的前半部分中 5. 如果依然不夠,nginx就會返回給客戶端400的錯誤 每個header也是和如上的request line一個處理步驟,所以對於request line和每個header的大小應該不超過1個large_client_header_buffers 對於整個request line和所有header來講,總大小不應該超過4*8192位元組大小,否則也會產生400的錯誤 */ 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_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long URI"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE); return; } } } }
1. 讀取HTTP請求行(第一行)
ngx_http_read_request_header
\nginx-1.7.4\src\http\ngx_http_request.c
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; //由於可能多次進入ngx_http_process_request_line函式,ngx_http_read_request_header函式首先檢查請求的header_in指向的緩衝區內是否有資料,有的話直接返回 n = r->header_in->last - r->header_in->pos; if (n > 0) { return n; } //從連線讀取資料並儲存在請求的header_in指向的快取區,而且只要緩衝區有空間的話,會一次儘可能多的讀資料 if (rev->ready) { n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last); } else { //如果客戶端暫時沒有發任何資料過來,並返回NGX_AGAIN n = NGX_AGAIN; } /* 返回之前會做2件事情 1. 設定一個定時器,時長預設為60s,可以通過指令client_header_timeout設定,如果定時事件到達之前沒有任何可讀事件,nginx將會關閉此請求 2. 呼叫ngx_handle_read_event函式處理一下讀事件 1) 如果該連線尚未在事件處理模型上掛載讀事件,則將其掛載上 2) 如果客戶端提前關閉了連線或者讀取資料發生了其他錯誤,則給客戶端返回一個400錯誤 3) 最後函式返回NGX_ERROR */ 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; } if (n == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); } 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; }
2. 解析HTTP請求行
rc = ngx_http_parse_request_line(r, r->header_in);
\Nginx\nginx-1.7.4\src\http\ngx_http_parse.c
ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) { u_char c, ch, *p, *m; //有限狀態機 enum { sw_start = 0, sw_method, sw_spaces_before_uri, sw_schema, sw_schema_slash, sw_schema_slash_slash, sw_host_start, sw_host, sw_host_end, sw_host_ip_literal, sw_port, sw_host_http_09, sw_after_slash_in_uri, sw_check_uri, sw_check_uri_http_09, sw_uri, sw_http_09, sw_http_H, sw_http_HT, sw_http_HTT, sw_http_HTTP, sw_first_major_digit, sw_major_digit, sw_first_minor_digit, sw_minor_digit, sw_spaces_after_digit, sw_almost_done } state; state = r->state; for (p = b->pos; p < b->last; p++) { ch = *p; switch (state) { /* HTTP methods: GET, HEAD, POST */ case sw_start: r->request_start = p; //HTTP請求報文的第一行不允許有換行 if (ch == CR || ch == LF) { break; } //HTTP請求報文的第一行不允許有除了字母、數字、下劃線之外的字元出現 if ((ch < 'A' || ch > 'Z') && ch != '_') { return NGX_HTTP_PARSE_INVALID_METHOD; } state = sw_method; break; case sw_method: if (ch == ' ') { r->method_end = p - 1; m = r->request_start; switch (p - m) { //識別HTTP Method的狀態機 case 3: if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) { r->method = NGX_HTTP_GET; break; } if (ngx_str3_cmp(m, 'P', 'U', 'T', ' ')) { r->method = NGX_HTTP_PUT; break; } break; case 4: if (m[1] == 'O') { if (ngx_str3Ocmp(m, 'P', 'O', 'S', 'T')) { r->method = NGX_HTTP_POST; break; } if (ngx_str3Ocmp(m, 'C', 'O', 'P', 'Y')) { r->method = NGX_HTTP_COPY; break; } if (ngx_str3Ocmp(m, 'M', 'O', 'V', 'E')) { r->method = NGX_HTTP_MOVE; break; } if (ngx_str3Ocmp(m, 'L', 'O', 'C', 'K')) { r->method = NGX_HTTP_LOCK; break; } } else { if (ngx_str4cmp(m, 'H', 'E', 'A', 'D')) { r->method = NGX_HTTP_HEAD; break; } } break; case 5: if (ngx_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) { r->method = NGX_HTTP_MKCOL; break; } if (ngx_str5cmp(m, 'P', 'A', 'T', 'C', 'H')) { r->method = NGX_HTTP_PATCH; break; } if (ngx_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) { r->method = NGX_HTTP_TRACE; break; } break; case 6: if (ngx_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) { r->method = NGX_HTTP_DELETE; break; } if (ngx_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) { r->method = NGX_HTTP_UNLOCK; break; } break; case 7: if (ngx_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' ')) { r->method = NGX_HTTP_OPTIONS; } break; case 8: if (ngx_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D')) { r->method = NGX_HTTP_PROPFIND; } break; case 9: if (ngx_str9cmp(m, 'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H')) { r->method = NGX_HTTP_PROPPATCH; } break; } state = sw_spaces_before_uri; break; } if ((ch < 'A' || ch > 'Z') && ch != '_') { return NGX_HTTP_PARSE_INVALID_METHOD; } break; /* space* before URI */ case sw_spaces_before_uri: if (ch == '/') { r->uri_start = p; state = sw_after_slash_in_uri; break; } c = (u_char) (ch | 0x20); if (c >= 'a' && c <= 'z') { r->schema_start = p; state = sw_schema; break; } switch (ch) { case ' ': break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_schema: c = (u_char) (ch | 0x20); if (c >= 'a' && c <= 'z') { break; } switch (ch) { case ':': r->schema_end = p; state = sw_schema_slash; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_schema_slash: switch (ch) { case '/': state = sw_schema_slash_slash; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_schema_slash_slash: switch (ch) { case '/': state = sw_host_start; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; //解析HTTP請求報文第一行中的HOST(http://www.target.com:80) case sw_host_start: r->host_start = p; if (ch == '[') { state = sw_host_ip_literal; break; } state = sw_host; /* fall through */ case sw_host: c = (u_char) (ch | 0x20); if (c >= 'a' && c <= 'z') { break; } if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') { break; } /* fall through */ case sw_host_end: r->host_end = p; switch (ch) { case ':': state = sw_port; break; case '/': r->uri_start = p; state = sw_after_slash_in_uri; break; case ' ': /* * use single "/" from request line to preserve pointers, * if request line will be copied to large client buffer */ r->uri_start = r->schema_end + 1; r->uri_end = r->schema_end + 2; state = sw_host_http_09; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_host_ip_literal: if (ch >= '0' && ch <= '9') { break; } c = (u_char) (ch | 0x20); if (c >= 'a' && c <= 'z') { break; } //nginx允許URL中出現這些特殊字元 switch (ch) { case ':': break; case ']': state = sw_host_end; break; case '-': case '.': case '_': case '~': /* unreserved */ break; case '!': case '$': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case ';': case '=': /* sub-delims */ break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_port: if (ch >= '0' && ch <= '9') { break; } switch (ch) { case '/': r->port_end = p; r->uri_start = p; state = sw_after_slash_in_uri; break; case ' ': r->port_end = p; /* * use single "/" from request line to preserve pointers, * if request line will be copied to large client buffer */ r->uri_start = r->schema_end + 1; r->uri_end = r->schema_end + 2; state = sw_host_http_09; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; /* space+ after "http://host[:port] " */ case sw_host_http_09: switch (ch) { case ' ': break; case CR: r->http_minor = 9; state = sw_almost_done; break; case LF: r->http_minor = 9; goto done; case 'H': r->http_protocol.data = p; state = sw_http_H; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; /* check "/.", "//", "%", and "\" (Win32) in URI */ case sw_after_slash_in_uri: if (usual[ch >> 5] & (1 << (ch & 0x1f))) { state = sw_check_uri; break; } switch (ch) { case ' ': r->uri_end = p; state = sw_check_uri_http_09; break; case CR: r->uri_end = p; r->http_minor = 9; state = sw_almost_done; break; case LF: r->uri_end = p; r->http_minor = 9; goto done; case '.': r->complex_uri = 1; state = sw_uri; break; case '%': r->quoted_uri = 1; state = sw_uri; break; case '/': r->complex_uri = 1; state = sw_uri; break; #if (NGX_WIN32) case '\\': r->complex_uri = 1; state = sw_uri; break; #endif case '?': r->args_start = p + 1; state = sw_uri; break; case '#': r->complex_uri = 1; state = sw_uri; break; case '+': r->plus_in_uri = 1; break; case '\0': return NGX_HTTP_PARSE_INVALID_REQUEST; default: state = sw_check_uri; break; } break; /* check "/", "%" and "\" (Win32) in URI */ case sw_check_uri: if (usual[ch >> 5] & (1 << (ch & 0x1f))) { break; } switch (ch) { case '/': #if (NGX_WIN32) if (r->uri_ext == p) { r->complex_uri = 1; state = sw_uri; break; } #endif r->uri_ext = NULL; state = sw_after_slash_in_uri; break; case '.': r->uri_ext = p + 1; break; case ' ': r->uri_end = p; state = sw_check_uri_http_09; break; case CR: r->uri_end = p; r->http_minor = 9; state = sw_almost_done; break; case LF: r->uri_end = p; r->http_minor = 9; goto done; #if (NGX_WIN32) case '\\': r->complex_uri = 1; state = sw_after_slash_in_uri; break; #endif case '%': r->quoted_uri = 1; state = sw_uri; break; case '?': r->args_start = p + 1; state = sw_uri; break; case '#': r->complex_uri = 1; state = sw_uri; break; case '+': r->plus_in_uri = 1; break; case '\0': return NGX_HTTP_PARSE_INVALID_REQUEST; } break; /* space+ after URI */ case sw_check_uri_http_09: switch (ch) { case ' ': break; case CR: r->http_minor = 9; state = sw_almost_done; break; case LF: r->http_minor = 9; goto done; case 'H': r->http_protocol.data = p; state = sw_http_H; break; default: r->space_in_uri = 1; state = sw_check_uri; p--; break; } break; /* URI */ case sw_uri: if (usual[ch >> 5] & (1 << (ch & 0x1f))) { break; } switch (ch) { case ' ': r->uri_end = p; state = sw_http_09; break; case CR: r->uri_end = p; r->http_minor = 9; state = sw_almost_done; break; case LF: r->uri_end = p; r->http_minor = 9; goto done; case '#': r->complex_uri = 1; break; case '\0': return NGX_HTTP_PARSE_INVALID_REQUEST; } break; /* space+ after URI */ case sw_http_09: switch (ch) { case ' ': break; case CR: r->http_minor = 9; state = sw_almost_done; break; case LF: r->http_minor = 9; goto done; case 'H': r->http_protocol.data = p; state = sw_http_H; break; default: r->space_in_uri = 1; state = sw_uri; p--; break; } break; //解析HTTP 0.9/1.0/1.1 case sw_http_H: switch (ch) { case 'T': state = sw_http_HT; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_http_HT: switch (ch) { case 'T': state = sw_http_HTT; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_http_HTT: switch (ch) { case 'P': state = sw_http_HTTP; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; case sw_http_HTTP: switch (ch) { case '/': state = sw_first_major_digit; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; /* first digit of major HTTP version */ case sw_first_major_digit: if (ch < '1' || ch > '9') { return NGX_HTTP_PARSE_INVALID_REQUEST; } r->http_major = ch - '0'; state = sw_major_digit; break; /* major HTTP version or dot */ case sw_major_digit: if (ch == '.') { state = sw_first_minor_digit; break; } if (ch < '0' || ch > '9') { return NGX_HTTP_PARSE_INVALID_REQUEST; } r->http_major = r->http_major * 10 + ch - '0'; break; /* first digit of minor HTTP version */ case sw_first_minor_digit: if (ch < '0' || ch > '9') { return NGX_HTTP_PARSE_INVALID_REQUEST; } r->http_minor = ch - '0'; state = sw_minor_digit; break; /* minor HTTP version or end of request line */ case sw_minor_digit: if (ch == CR) { state = sw_almost_done; break; } if (ch == LF) { goto done; } if (ch == ' ') { state = sw_spaces_after_digit; break; } if (ch < '0' || ch > '9') { return NGX_HTTP_PARSE_INVALID_REQUEST; } r->http_minor = r->http_minor * 10 + ch - '0'; break; case sw_spaces_after_digit: switch (ch) { case ' ': break; case CR: state = sw_almost_done; break; case LF: goto done; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; /* end of request line */ case sw_almost_done: r->request_end = p - 1; switch (ch) { case LF: goto done; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } } } b->pos = p; r->state = state; return NGX_AGAIN; done: b->pos = p + 1; if (r->request_end == NULL) { r->request_end = p; } r->http_version = r->http_major * 1000 + r->http_minor; r->state = sw_start; if (r->http_version == 9 && r->method != NGX_HTTP_GET) { return NGX_HTTP_PARSE_INVALID_09_METHOD; } return NGX_OK; }
這個函式根據http協議規範中對請求行的定義實現了一個有限狀態機,經過這個狀態機,nginx會記錄請求行中的請求方法(Method),請求uri以及http協議版本在緩衝區中的起始位置,在解析過程中還會記錄一些其他有用的資訊,以便後面的處理過程中使用。如果解析請求行的過程中沒有產生任何問題,該函式會返回NGX_OK;如果請求行不滿足協議規範,該函式會立即終止解析過程,並返回相應錯誤號;如果緩衝區資料不夠,該函式返回NGX_AGAIN。在整個解析http請求的狀態機中始終遵循著兩條重要的原則
1. 減少記憶體拷貝和回溯 記憶體拷貝是一個相對比較昂貴的操作,大量的記憶體拷貝會帶來較低的執行時效率。nginx在需要做記憶體拷貝的地方儘量只拷貝記憶體的起始和結束地址而不是記憶體本身,這樣做的話僅僅只需要兩個賦值操作而已,大大降低了開銷,當然這樣帶來的影響是後續的操作不能修改記憶體本身,如果修改的話,會影響到所有引用到該記憶體區間的地方,所以必須很小心的管理,必要的時候需要拷貝一份 2. nginx中最能體現這一思想的資料結構,ngx_buf_t,它用來表示nginx中的快取,在很多情況下,只需要將一塊記憶體的起始地址和結束地址分別儲存在它的pos和last成員中,再將它的memory標誌置1,即可表示一塊不能修改的記憶體區間,在另外的需要一塊能夠修改的快取的情形中,則必須分配一塊所需大小的記憶體並儲存其起始地址,再將ngx_bug_t的temprary標誌置1,表示這是一塊能夠被修改的記憶體區域
3. 讀取HTTP請求頭Header欄位
如果是1.0或者更新的http協議,接下來要做的就是讀取請求頭了
\nginx-1.7.4\src\http\ngx_http_request.c
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; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http process request line"); //將當前時間保持在start_sec和start_msec欄位,這個時間是該請求的起始時刻,將被用來計算一個請求的處理時間(request time) //nginx使用的這個起始點和apache略有差別,nginx中請求的起始點是接收到客戶端的第一個資料包開始,而apache則是接收到客戶端的整個request line後開始算起 if (rev->timedout) { //ngx_http_process_request_line函式的主要作用即是解析請求行,同樣由於涉及到網路IO操作,即使是很短的一行請求行可能也不能被一次讀完 //所以在之前的ngx_http_init_request函式中,ngx_http_process_request_line函式被設定為讀事件的處理函式,它也只擁有一個唯一的ngx_event_t *型別引數 //並且在函式的開頭,同樣需要判斷是否是超時事件,如果是的話,則關閉這個請求和連線;否則開始正常的解析流程 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); if (n == NGX_AGAIN || n == NGX_ERROR) { return; } } //如果ngx_http_read_request_header函式正常的讀取到了資料,ngx_http_process_request_line函式將呼叫ngx_http_parse_request_line函式來解析HTTP包頭 rc = ngx_http_parse_request_line(r, r->header_in); if (rc == NGX_OK) { /* 如果返回了NGX_OK,則表示請求行被正確的解析出來了 1. 這時先記錄好請求行的起始地址以及長度 2. 並將請求uri的path和引數部分儲存在請求結構的uri欄位,請求方法起始位置和長度儲存在method_name欄位,http版本起始位置和長度記錄在http_protocol欄位 3. 還要從uri中解析出引數以及請求資源的擴充名,分別儲存在args和exten欄位。接下來將要解析請求頭 */ /* the request line has been parsed successfully */ 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; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); 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; rc = ngx_http_validate_host(&host, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid host in request line"); 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; } if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { return; } //解析完請求行之後,如果請求行的uri裡面包含了域名部分,則將其保持在請求結構的headers_in成員的server欄位,headers_in用來儲存所有請求頭 r->headers_in.server = host; } //檢查進來的請求是否使用的是http0.9,如果是的話則使用從請求行裡得到的域名 if (r->http_version < NGX_HTTP_VERSION_10) { if (r->headers_in.server.len == 0 //呼叫ngx_http_find_virtual_server()函式來查詢用來處理該請求的虛擬伺服器配置,之前通過埠和地址找到的預設配置不再使用 && ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR) { return; } //找到相應的配置之後,則直接呼叫ngx_http_process_request()函式處理該請求,因為http0.9是最原始的http協議,它裡面沒有定義任何請求頭,顯然就不需要讀取請求頭的操作 ngx_http_process_request(r); return; } 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"; rev->handler = ngx_http_process_request_headers; ngx_http_process_request_headers(rev); return; } /* 如果返回NGX_AGAIN,則需要判斷一下是否是由於緩衝區空間不夠,還是已讀資料不夠 1. 如果是緩衝區大小不夠了,nginx會呼叫ngx_http_alloc_large_header_buffer函式來分配另一塊大緩衝區,如果大緩衝區還不夠裝下整個請求行,nginx則會返回414錯誤給客戶端 2. 否則分配了更大的緩衝區並拷貝之前的資料之後,繼續呼叫 */ if (rc != NGX_AGAIN) { /* there was error while a request line parsing */ ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } /* NGX_AGAIN: a request line parsing is still incomplete */ //nginx在處理request的時候,會預先分配一個client_header_buffer_size的buf,如果不夠就會分配large_client_header_buffers的buf //對於request line和每個header而言,每一個不應該超過large buf,所有的總和也不應該超過large buf size*num。Http 1.1的pipeline請求 //如果前面的請求分配的large buf,那麼後面的請求會繼承使用這個large buf分配的空間,當large buf 不夠了再去主動分配large buf if (r->header_in->pos == r->header_in->end) { /* nginx在接收到客戶端得請求之後,就開始解析http請求,也就是解析http header,需要分配一段buf來接收這些資料 nginx並不知道這個http header的大小,在nginx配置中client_header_buffer_size和large_client_header_buffers這兩個配置項起到了作用 1. client_header_buffer_size 1k 2. large_client_header_buffers 4 8k client_header_buffer_size預設是1024位元組。large_client_header_buffers預設最大分配4組8192位元組的buf,每次分配一個buf 1. nginx處理http header的過程是先處理request line(http 請求的第一行) 2. 然後在處理每一個header 3. 那麼處理request line的過程首先會分配client_header_buffer_size大小的空間 4. 如果這個空間不夠,那麼再分配一個large_client_header_buffers的空間,然後把之前的client_header_buffer_size copy到大buffer的前半部分中 5. 如果依然不夠,nginx就會返回給客戶端400的錯誤 每個header也是和如上的request line一個處理步驟,所以對於request line和每個header的大小應該不超過1個large_client_header_buffers 對於整個request line和所有header來講,總大小不應該超過4*8192位元組大小,否則也會產生400的錯誤 */ 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_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long URI"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE); return; } } } }
headers_in用來儲存所有請求頭,它的型別為ngx_http_headers_in_t
/src/http/ngx_http_request.h
typedef struct { ngx_list_t headers; ngx_table_elt_t *host; ngx_table_elt_t *connection; ngx_table_elt_t *if_modified_since; ngx_table_elt_t *if_unmodified_since; ngx_table_elt_t *if_match; ngx_table_elt_t *if_none_match; ngx_table_elt_t *user_agent; ngx_table_elt_t *referer; ngx_table_elt_t *content_length; ngx_table_elt_t *content_type; ngx_table_elt_t *range; ngx_table_elt_t *if_range; ngx_table_elt_t *transfer_encoding; ngx_table_elt_t *expect; ngx_table_elt_t *upgrade; #if (NGX_HTTP_GZIP) ngx_table_elt_t *accept_encoding; ngx_table_elt_t *via; #endif ngx_table_elt_t *authorization; ngx_table_elt_t *keep_alive; #if (NGX_HTTP_X_FORWARDED_FOR) ngx_array_t x_forwarded_for; #endif #if (NGX_HTTP_REALIP) ngx_table_elt_t *x_real_ip; #endif #if (NGX_HTTP_HEADERS) ngx_table_elt_t *accept; ngx_table_elt_t *accept_language; #endif #if (NGX_HTTP_DAV) ngx_table_elt_t *depth; ngx_table_elt_t *destination; ngx_table_elt_t *overwrite; ngx_table_elt_t *date; #endif ngx_str_t user; ngx_str_t passwd; ngx_array_t cookies; ngx_str_t server; off_t content_length_n; time_t keep_alive_n; unsigned connection_type:2; unsigned chunked:1; unsigned msie:1; unsigned msie6:1; unsigned opera:1; unsigned gecko:1; unsigned chrome:1; unsigned safari:1; unsigned konqueror:1; } ngx_http_headers_in_t;
4. HTTP請求包Header欄位解析
如果讀到了一些資料則呼叫ngx_http_parse_header_line()函式來解析,同樣的該解析函式實現為一個有限狀態機,邏輯很簡單,只是根據http協議的解析一個請求頭的name/vale對,每次呼叫該函式最多解析出一個請求頭,該函式返回4種不同返回值,表示不同解析結果
1. 返回NGX_OK,表示解析出了一行請求頭,這時還要判斷解析出的請求頭名字裡面是否有非法字元,名字裡面合法的字元包括 1) 字母 2) 數字 3) 連字元(-) 4) 另外如果設定了underscores_in_headers指令為on,則下劃線也是合法字元,但是nginx預設下劃線不合法 當請求頭裡麵包含了非法的字元,nginx預設只是忽略這一行請求頭 如果一切都正常,nginx會將該請求頭及請求頭名字的hash值儲存在請求結構體的headers_in成員的headers連結串列 對於一些常見的請求頭,如Host,Connection,nginx採用了類似於配置指令的方式,事先給這些請求頭分配了一個處理函式,當解析出一個請求頭時,會檢查該請求頭是否有設定處理函式,有的話則呼叫之,nginx所有有處理函式的請求頭都記錄在ngx_http_headers_in全域性陣列中 2. 返回NGX_AGAIN,表示當前接收到的資料不夠,一行請求頭還未結束,需要繼續下一輪迴圈。在下一輪迴圈中,nginx首先檢查請求頭緩衝區header_in是否已滿,如夠滿了,則呼叫ngx_http_alloc_large_header_buffer()函式分配更多緩衝區 3. 返回NGX_HTTP_PARSE_INVALID_HEADER,表示請求頭解析過程中遇到錯誤,一般為客戶端傳送了不符合協議規範的頭部,此時nginx返回400錯誤 4. 返回NGX_HTTP_PARSE_HEADER_DONE,表示所有請求頭已經成功的解析,這時請求的狀態被設定為NGX_HTTP_PROCESS_REQUEST_STATE,意味著結束了請求讀取階段,正式進入了請求處理階段,但是實際上請求可能含有請求體,nginx在請求讀取階段並不會去讀取請求體,這個工作交給了後續的請求處理階段的模組,這樣做的目的是nginx本身並不知道這些請求體是否有用,如果後續模組並不需要的話,一方面請求體一般較大,如果全部讀取進記憶體,則白白耗費大量的記憶體空間,另一方面即使nginx將請求體寫進磁碟,但是涉及到磁碟io,會耗費比較多時間。所以交由後續模組來決定讀取還是丟棄請求體是最明智的辦法
\nginx-1.7.4\src\http\ngx_http_parse.c
ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t allow_underscores) { u_char c, ch, *p; ngx_uint_t hash, i; enum { sw_start = 0, sw_name, sw_space_before_value, sw_value, sw_space_after_value, sw_ignore_line, sw_almost_done, sw_header_almost_done } state; /* the last '\0' is not needed because string is zero terminated */ static u_char lowcase[] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0-\0\0" "0123456789\0\0\0\0\0\0" "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; state = r->state; hash = r->header_hash; i = r->lowcase_index; //迴圈逐字元處理HTTP Header中的key:value對 for (p = b->pos; p < b->last; p++) { ch = *p; switch (state) { /* first char */ case sw_start: r->header_name_start = p; r->invalid_header = 0; switch (ch) { //和apache不同,nginx對HTTP Header欄位中的換行直接認定為該欄位結束,而不會去跨行解析 case CR: r->header_end = p; state = sw_header_almost_done; break; case LF: r->header_end = p; goto header_done; default: state = sw_name; //nginx自動將大小寫轉換為小寫 c = lowcase[ch]; if (c) { hash = ngx_hash(0, c); r->lowcase_header[0] = c; i = 1; break; } //根據配置資訊underscores_in_headers決定是否允許下劃線字元 if (ch == '_') { if (allow_underscores) { hash = ngx_hash(0, ch); r->lowcase_header[0] = ch; i = 1; } else { r->invalid_header = 1; } break; } //禁止\0截斷這種非法字元 if (ch == '\0') { return NGX_HTTP_PARSE_INVALID_HEADER; } r->invalid_header = 1; break; } break; /* header name */ case sw_name: c = lowcase[ch]; if (c) { hash = ngx_hash(hash, c); r->lowcase_header[i++] = c; i &= (NGX_HTTP_LC_HEADER_LEN - 1); break; } if (ch == '_') { if (allow_underscores) { hash = ngx_hash(hash, ch); r->lowcase_header[i++] = ch; i &= (NGX_HTTP_LC_HEADER_LEN - 1); } else { r->invalid_header = 1; } break; } //HTTP Header的欄位都是key:value這種格式的,通過冒號:作為name解析的終止 if (ch == ':') { r->header_name_end = p; state = sw_space_before_value; break; } if (ch == CR) { r->header_name_end = p; r->header_start = p; r->header_end = p; state = sw_almost_done; break; } if (ch == LF) { r->header_name_end = p; r->header_start = p; r->header_end = p; goto done; } /* IIS may send the duplicate "HTTP/1.1 ..." lines */ if (ch == '/' && r->upstream && p - r->header_name_start == 4 && ngx_strncmp(r->header_name_start, "HTTP", 4) == 0) { state = sw_ignore_line; break; } if (ch == '\0') { return NGX_HTTP_PARSE_INVALID_HEADER; } r->invalid_header = 1; break; /* space* before header value */ case sw_space_before_value: switch (ch) { //忽略key:value中的空格 case ' ': break; //在value的解析中,同樣也不允許換行字元 case CR: r->header_start = p; r->header_end = p; state = sw_almost_done; break; case LF: r->header_start = p; r->header_end = p; goto done; case '\0': return NGX_HTTP_PARSE_INVALID_HEADER; default: r->header_start = p; state = sw_value; break; } break; /* header value */ case sw_value: switch (ch) { case ' ': r->header_end = p; state = sw_space_after_value; break; case CR: r->header_end = p; state = sw_almost_done; break; case LF: r->header_end = p; goto done; case '\0': return NGX_HTTP_PARSE_INVALID_HEADER; } break; /* space* before end of header line */ case sw_space_after_value: switch (ch) { case ' ': break; case CR: state = sw_almost_done; break; case LF: goto done; case '\0': return NGX_HTTP_PARSE_INVALID_HEADER; default: state = sw_value; break; } break; /* ignore header line */ case sw_ignore_line: switch (ch) { case LF: state = sw_start; break; default: break; } break; /* end of header line */ case sw_almost_done: switch (ch) { case LF: goto done; case CR: break; default: return NGX_HTTP_PARSE_INVALID_HEADER; } break; /* end of header */ case sw_header_almost_done: switch (ch) { case LF: goto header_done; default: return NGX_HTTP_PARSE_INVALID_HEADER; } } } b->pos = p; r->state = state; r->header_hash = hash; r->lowcase_index = i; return NGX_AGAIN; done: b->pos = p + 1; r->state = sw_start; r->header_hash = hash; r->lowcase_index = i; return NGX_OK; header_done: b->pos = p + 1; r->state = sw_start; return NGX_HTTP_PARSE_HEADER_DONE; }
返回NGX_AGAIN,表示當前接收到的資料不夠,一行請求頭還未結束,需要繼續下一輪迴圈。在下一輪迴圈中,nginx首先檢查請求頭緩衝區header_in是否已滿,如夠滿了,則呼叫ngx_http_alloc_large_header_buffer()函式分配更多緩衝區
static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, ngx_uint_t request_line) { u_char *old, *new; ngx_buf_t *b; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http alloc large header buffer"); //在解析請求行階段,如果客戶端在傳送請求行之前傳送了大量回車換行符將緩衝區塞滿了,針對這種情況,nginx只是簡單的重置緩衝區,丟棄這些垃圾資料,不需要分配更大的記憶體 if (request_line && r->state == 0) { /* the client fills up the buffer with "\r\n" */ r->header_in->pos = r->header_in->start; r->header_in->last = r->header_in->start; return NGX_OK; } //儲存請求行或者請求頭在舊緩衝區中的起始地址 old = request_line ? r->request_start : r->header_name_start; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); //如果一個大緩衝區還裝不下請求行或者一個請求頭,則返回錯誤 if (r->state != 0 && (size_t) (r->header_in->pos - old) >= cscf->large_client_header_buffers.size) { return NGX_DECLINED; } hc = r->http_connection; if (hc->nfree) { b = hc->free[--hc->nfree]; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header free: %p %uz", b->pos, b->end - b->last); //檢查給該請求分配的請求頭緩衝區個數是否已經超過限制,預設最大個數為4個 } else if (hc->nbusy < cscf->large_client_header_buffers.num) { if (hc->busy == NULL) { hc->busy = ngx_palloc(r->connection->pool, cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *)); if (hc->busy == NULL) { return NGX_ERROR; } } //如果還沒有達到最大分配數量,則分配一個新的大緩衝區 b = ngx_create_temp_buf(r->connection->pool, cscf->large_client_header_buffers.size); if (b == NULL) { return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header alloc: %p %uz", b->pos, b->end - b->last); } else { //如果已經達到最大的分配限制,則返回錯誤 return NGX_DECLINED; } //將從空閒佇列取得的或者新分配的緩衝區加入已使用佇列 hc->busy[hc->nbusy++] = b; /* 因為nginx中,所有的請求頭的儲存形式都是指標(起始和結束地址),所以一行完整的請求頭必須放在連續的記憶體塊中 如果舊的緩衝區不能再放下整行請求頭,則分配新緩衝區,並從舊緩衝區拷貝已經讀取的部分請求頭,拷貝完之後,需要修改所有相關指標指向到新緩衝區 status為0表示解析完一行請求頭之後,緩衝區正好被用完,這種情況不需要拷貝 */ if (r->state == 0) { /* * r->state == 0 means that a header line was parsed successfully * and we do not need to copy incomplete header line and * to relocate the parser header pointers */ r->header_in = b; return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header copy: %d", r->header_in->pos - old); new = b->start; //拷貝舊緩衝區中不完整的請求頭 ngx_memcpy(new, old, r->header_in->pos - old); b->pos = new + (r->header_in->pos - old); b->last = new + (r->header_in->pos - old); //修改相應的指標指向新緩衝區 if (request_line) { r->request_start = new; if (r->request_end) { r->request_end = new + (r->request_end - old); } r->method_end = new + (r->method_end - old); r->uri_start = new + (r->uri_start - old); r->uri_end = new + (r->uri_end - old); if (r->schema_start) { r->schema_start = new + (r->schema_start - old); r->schema_end = new + (r->schema_end - old); } if (r->host_start) { r->host_start = new + (r->host_start - old); if (r->host_end) { r->host_end = new + (r->host_end - old); } } if (r->port_start) { r->port_start = new + (r->port_start - old); r->port_end = new + (r->port_end - old); } if (r->uri_ext) { r->uri_ext = new + (r->uri_ext - old); } if (r->args_start) { r->args_start = new + (r->args_start - old); } if (r->http_protocol.data) { r->http_protocol.data = new + (r->http_protocol.data - old); } } else { r->header_name_start = new; r->header_name_end = new + (r->header_name_end - old); r->header_start = new + (r->header_start - old); r->header_end = new + (r->header_end - old); } r->header_in = b; return NGX_OK; }
5. HTTP Header欄位解析回撥處理函式
ngx_http_headers_in陣列當前包含了25個常用的請求頭,每個請求頭都設定了一個處理函式,當前其中一部分請求頭設定的是公共的處理函式,這裡有2個公共的處理函式:ngx_http_process_header_line、ngx_http_process_unique_header_line
\nginx-1.7.4\src\http\ngx_http_request.c
ngx_http_header_t ngx_http_headers_in[] = { { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host), ngx_http_process_host }, { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection), ngx_http_process_connection }, { ngx_string("If-Modified-Since"), offsetof(ngx_http_headers_in_t, if_modified_since), ngx_http_process_unique_header_line }, { ngx_string("If-Unmodified-Since"), offsetof(ngx_http_headers_in_t, if_unmodified_since), ngx_http_process_unique_header_line }, { ngx_string("If-Match"), offsetof(ngx_http_headers_in_t, if_match), ngx_http_process_unique_header_line }, { ngx_string("If-None-Match"), offsetof(ngx_http_headers_in_t, if_none_match), ngx_http_process_unique_header_line }, { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent), ngx_http_process_user_agent }, { ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer), ngx_http_process_header_line }, { ngx_string("Content-Length"), offsetof(ngx_http_headers_in_t, content_length), ngx_http_process_unique_header_line }, { ngx_string("Content-Type"), offsetof(ngx_http_headers_in_t, content_type), ngx_http_process_header_line }, { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range), ngx_http_process_header_line }, { ngx_string("If-Range"), offsetof(ngx_http_headers_in_t, if_range), ngx_http_process_unique_header_line }, { ngx_string("Transfer-Encoding"), offsetof(ngx_http_headers_in_t, transfer_encoding), ngx_http_process_header_line }, { ngx_string("Expect"), offsetof(ngx_http_headers_in_t, expect), ngx_http_process_unique_header_line }, { ngx_string("Upgrade"), offsetof(ngx_http_headers_in_t, upgrade), ngx_http_process_header_line }, #if (NGX_HTTP_GZIP) { ngx_string("Accept-Encoding"), offsetof(ngx_http_headers_in_t, accept_encoding), ngx_http_process_header_line }, { ngx_string("Via"), offsetof(ngx_http_headers_in_t, via), ngx_http_process_header_line }, #endif { ngx_string("Authorization"), offsetof(ngx_http_headers_in_t, authorization), ngx_http_process_unique_header_line }, { ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive), ngx_http_process_header_line }, #if (NGX_HTTP_X_FORWARDED_FOR) { ngx_string("X-Forwarded-For"), offsetof(ngx_http_headers_in_t, x_forwarded_for), ngx_http_process_multi_header_lines }, #endif #if (NGX_HTTP_REALIP) { ngx_string("X-Real-IP"), offsetof(ngx_http_headers_in_t, x_real_ip), ngx_http_process_header_line }, #endif #if (NGX_HTTP_HEADERS) { ngx_string("Accept"), offsetof(ngx_http_headers_in_t, accept), ngx_http_process_header_line }, { ngx_string("Accept-Language"), offsetof(ngx_http_headers_in_t, accept_language), ngx_http_process_header_line }, #endif #if (NGX_HTTP_DAV) { ngx_string("Depth"), offsetof(ngx_http_headers_in_t, depth), ngx_http_process_header_line }, { ngx_string("Destination"), offsetof(ngx_http_headers_in_t, destination), ngx_http_process_header_line }, { ngx_string("Overwrite"), offsetof(ngx_http_headers_in_t, overwrite), ngx_http_process_header_line }, { ngx_string("Date"), offsetof(ngx_http_headers_in_t, date), ngx_http_process_header_line }, #endif { ngx_string("Cookie"), offsetof(ngx_http_headers_in_t, cookies), ngx_http_process_multi_header_lines }, { ngx_null_string, 0, NULL } };
我們拿Host頭的處理函式ngx_http_process_host進行深入研究
static ngx_int_t ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_int_t rc; ngx_str_t host; if (r->headers_in.host == NULL) { r->headers_in.host = h; } host = h->value; //此函式的目的也是儲存Host頭的快速引用,它會對Host頭的值做一些合法性檢查,並從中解析出域名,儲存在headers_in.server欄位 //實際上前面在解析請求行時,headers_in.server可能已經被賦值為從請求行中解析出來的域名 //根據http協議的規範,如果請求行(第一行)中的uri帶有域名的話,則域名以它(URL)為準,而host會被忽略,所以這裡需檢查一下headers_in.server是否為空,如果不為空則不需要再賦值 rc = ngx_http_validate_host(&host, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid host header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } if (rc == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } if (r->headers_in.server.len) { return NGX_OK; } if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { return NGX_ERROR; } r->headers_in.server = host; return NGX_OK; }
Relevant Link:
http://m.blog.chinaunix.net/uid-27767798-id-3776815.html http://blog.csdn.net/yusiguyuan/article/details/41288343 http://lxr.nginx.org/source/src/http/ngx_http_request.h#0241 http://blog.csdn.net/yusiguyuan/article/details/41288417 http://www.pagefault.info/?p=220
3. HTTP Request Body解析流程
0x1: HTTP請求體(body)讀取
在HTTP請求處理流程中,針對有些模組需要對請求體做一些處理,那麼這個模組就需要在這個階段註冊函式,其中讀取請求體的函式ngx_http_read_client_request_body()是存在的,只不過不同的模組可能對請求體做不同的處理,讀取請全體的函式是在某個模組的conent_handler函式中包含的,比如比如proxy模組,fastcgi模組,uwsgi模組等這些模組對請求體感興趣,那麼讀取請求體的函式在這些模組的content_handler中註冊
nginx核心本身不會主動讀取請求體,這個工作是交給請求處理階段的模組來做
1. nginx核心提供了ngx_http_read_client_request_body()介面來讀取請求體 2. 另外還提供了一個丟棄請求體的介面: ngx_http_discard_request_body()
在請求執行的各個階段中,任何一個階段的模組如果對請求體感興趣或者希望丟掉客戶端發過來的請求體,可以分別呼叫這兩個介面來完成。這兩個介面是nginx核心提供的處理請求體的標準介面,如果希望配置檔案中一些請求體相關的指令(比如client_body_in_file_only、client_body_buffer_size等)能夠預期工作,以及能夠正常使用nginx內建的一些和請求體相關的變數(比如$request_body和$request_body_file),一般來說所有模組都必須呼叫這些介面來完成相應操作,如果需要自定義介面來處理請求體,也應儘量相容nginx預設的行為
1. 讀取請求體
請求體的讀取一般發生在nginx的content handler中,一些nginx內建的模組,比如proxy模組,fastcgi模組,uwsgi模組等,這些模組的行為必須將客戶端過來的請求體(如果有的話)以相應協議完整的轉發到後端服務程式,所有的這些模組都是呼叫了ngx_http_read_client_request_body()介面來完成請求體讀取。值得注意的是這些模組會把客戶端的請求體完整的讀取後才開始往後端轉發資料 \nginx-1.7.4\src\http\ngx_http_request_body.c
//ngx_http_mytest_body_handler的返回型別是void,Nginx不會根據返回值做一些收尾工作,因此,我們在該方法裡處理完請求時必須要主動呼叫ngx_http_finalize_request方法來結束請求 /* 1. ngx_http_request_t *r: 指向請求結構的指標 2. ngx_http_client_body_handler_pt post_handler: 函式指標,當請求體讀完時,它會被呼叫。之前也說到根據nginx現有行為,模組邏輯會在請求體讀完後執行,這個回撥函式一般就是模組的邏輯處理函式 */ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler) { size_t preread; ssize_t size; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out, *cl; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; r->main->count++; #if (NGX_HTTP_SPDY) if (r->spdy_stream && r == r->main) { rc = ngx_http_spdy_read_request_body(r, post_handler); goto done; } #endif if (r != r->main || r->request_body || r->discard_body) { post_handler(r); return NGX_OK; } //呼叫ngx_http_test_expect()檢查客戶端是否傳送了Expect: 100-continue頭,是的話則給客戶端回覆"HTTP/1.1 100 Continue" //根據http 1.1協議,客戶端可以傳送一個Expect頭來向伺服器表明期望傳送請求體,伺服器如果允許客戶端傳送請求體,則會回覆"HTTP/1.1 100 Continue",客戶端收到時,才會開始傳送請求體 if (ngx_http_test_expect(r) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); if (rb == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } /* * set by ngx_pcalloc(): * * rb->bufs = NULL; * rb->buf = NULL; * rb->free = NULL; * rb->busy = NULL; * rb->chunked = NULL; */ rb->rest = -1; rb->post_handler = post_handler; /* 為接收請求體做準備工作,分配一個ngx_http_request_body_t結構,並儲存在r->request_body,這個結構用來儲存請求體讀取過程用到的快取引用,臨時檔案引用,剩餘請求體大小等資訊,它的定義如下 typedef struct { ngx_temp_file_t *temp_file; //指向儲存請求體的臨時檔案的指標 ngx_chain_t *bufs; //指向儲存請求體的連結串列頭 ngx_buf_t *buf; //指向當前用於儲存請求體的記憶體快取 off_t rest; //當前剩餘的請求體大小 ngx_chain_t *to_write; ngx_http_client_body_handler_pt post_handler; //儲存傳給ngx_http_read_client_request_body()函式的回撥函式 } ngx_http_request_body_t; */ r->request_body = rb; //檢查請求是否帶有content_length頭,如果沒有該頭或者客戶端傳送了一個值為0的content_length頭,表明沒有請求體,這時直接呼叫回撥函式並返回NGX_OK即可 if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { post_handler(r); return NGX_OK; } preread = r->header_in->last - r->header_in->pos; if (preread) { /* there is the pre-read part of the request body */ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http client request body preread %uz", preread); out.buf = r->header_in; out.next = NULL; rc = ngx_http_request_body_filter(r, &out); if (rc != NGX_OK) { goto done; } //判斷儲存請求頭的快取(r->header_in)中是否還有未處理的資料 //1. 如果有預讀資料,則分配一個ngx_buf_t結構,並將r->header_in中的預讀資料儲存在其中 //2. 並且如果r->header_in中還有剩餘空間,並且能夠容下剩餘未讀取的請求體,這些空間將被繼續使用,而不用分配新的快取,甚至如果請求體已經被整個預讀了,則不需要繼續處理了,此時呼叫回撥函式後返回 r->request_length += preread - (r->header_in->last - r->header_in->pos); if (!r->headers_in.chunked && rb->rest > 0 && rb->rest <= (off_t) (r->header_in->end - r->header_in->last)) { /* the whole request body may be placed in r->header_in */ b = ngx_calloc_buf(r->pool); if (b == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } b->temporary = 1; b->start = r->header_in->pos; b->pos = r->header_in->pos; b->last = r->header_in->last; b->end = r->header_in->end; rb->buf = b; r->read_event_handler = ngx_http_read_client_request_body_handler; r->write_event_handler = ngx_http_request_empty_handler; rc = ngx_http_do_read_client_request_body(r); goto done; } } else { /* set rb->rest */ if (ngx_http_request_body_filter(r, NULL) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } } if (rb->rest == 0) { /* the whole request body was pre-read */ if (r->request_body_in_file_only) { if (ngx_http_write_request_body(r) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } if (rb->temp_file->file.offset != 0) { cl = ngx_chain_get_free_buf(r->pool, &rb->free); if (cl == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } b = cl->buf; ngx_memzero(b, sizeof(ngx_buf_t)); b->in_file = 1; b->file_last = rb->temp_file->file.offset; b->file = &rb->temp_file->file; rb->bufs = cl; } else { rb->bufs = NULL; } } post_handler(r); return NGX_OK; } if (rb->rest < 0) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "negative request body rest"); rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); //client_body_buffer_size:設定快取請求體的buffer大小,預設為系統頁大小的2倍,當請求體的大小超過此大小時,nginx會把請求體寫入到臨時檔案中 //可以根據業務需求設定合適的大小,儘量避免磁碟io操作 size = clcf->client_body_buffer_size; size += size >> 2; /* TODO: honor r->request_body_in_single_buf */ if (!r->headers_in.chunked && rb->rest < size) { size = (ssize_t) rb->rest; if (r->request_body_in_single_buf) { size += preread; } } else { size = clcf->client_body_buffer_size; } //由於記憶體的限制,ngx_http_read_client_request_body()介面讀取的請求體會部分或者全部寫入一個臨時檔案中 rb->buf = ngx_create_temp_buf(r->pool, size); if (rb->buf == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } r->read_event_handler = ngx_http_read_client_request_body_handler; r->write_event_handler = ngx_http_request_empty_handler; rc = ngx_http_do_read_client_request_body(r); done: if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { r->main->count--; } return rc; }
ngx_http_read_client_request_body是一個非同步方法,呼叫它只是說明要求Nginx開始接收請求的包體,並不表示是否已經接收完,當接收完所有的包體內容後,post_handler指向的回撥方法會被呼叫。因此,即使在呼叫了ngx_http_read_client_request_body方法後它已經返回,也無法確定這時是否已經呼叫過post_handler指向的方法。換句話說,ngx_http_read_client_request_body返回時既有可能已經接收完請求中所有的包體(假如包體的長度很小),也有可能還沒開始接收包體
在worker程式中,呼叫ngx_http_read_client_request_body是不會阻塞的,要麼讀完socket上的buffer發現不完整立刻返回,等待下一次EPOLLIN事件,要麼就是讀完body了,呼叫使用者定義的post_handler方法去處理body
ngx_http_read_client_request_body提供兩種儲存body的方式,一種是把body儲存在記憶體中,另一種是把body儲存到臨時檔案裡。這個臨時檔案也有不同的處理方法,一種是請求結束後nginx便清理掉,另外就是永久保留這個臨時檔案。例如下面這兩個引數就會設定為每個body都存放到臨時檔案裡,並且這個臨時檔案在請求結束後不會被刪除
r->request_body_in_persistent_file = 1; r->request_body_in_file_only = 1;
2. 丟棄請求體
一個模組想要主動的丟棄客戶端發過的請求體,可以呼叫nginx核心提供的ngx_http_discard_request_body()介面,主動丟棄的原因可能有很多種
1. 模組的業務邏輯不需要請求體 2. 客戶端傳送了過大的請求體 3. 為了相容http1.1協議的pipeline請求,模組有義務主動丟棄不需要的請求體。總之為了保持良好的客戶端相容性,nginx必須主動丟棄無用的請求體
\nginx-1.7.4\src\http\ngx_http_request_body.c
ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r) { ssize_t size; ngx_int_t rc; ngx_event_t *rev; #if (NGX_HTTP_SPDY) if (r->spdy_stream && r == r->main) { r->spdy_stream->skip_data = NGX_SPDY_DATA_DISCARD; return NGX_OK; } #endif /* 判斷了不需要再做處理的情況 1. 子請求不需要處理 2. 已經呼叫過此函式的也不需要再處理 */ if (r != r->main || r->discard_body || r->request_body) { return NGX_OK; } //呼叫ngx_http_test_expect() 處理http1.1 expect的情況 //根據http1.1的expect機制,如果客戶端傳送了expect頭,而服務端不希望接收請求體時,必須返回417(Expectation Failed)錯誤。nginx並沒有這樣做,它只是簡單的讓客戶端把請求體傳送過來,然後丟棄掉 if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } rev = r->connection->read; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body"); if (rev->timer_set) { //刪掉了讀事件上的定時器,因為這時本身就不需要請求體,所以也無所謂客戶端傳送的快還是慢了 ngx_del_timer(rev); } if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) { return NGX_OK; } size = r->header_in->last - r->header_in->pos; if (size || r->headers_in.chunked) { rc = ngx_http_discard_request_body_filter(r, r->header_in); if (rc != NGX_OK) { return rc; } //檢查請求頭中的content-length頭,客戶端如果打算髮送請求體,就必須傳送content-length頭 if (r->headers_in.content_length_n == 0) { return NGX_OK; } } rc = ngx_http_read_discarded_request_body(r); if (rc == NGX_OK) { r->lingering_close = 0; return NGX_OK; } if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } /* rc == NGX_AGAIN */ r->read_event_handler = ngx_http_discarded_request_body_handler; //如果還有剩餘的請求體未處理,該函式呼叫ngx_handle_read_event()在事件處理機制中掛載好讀事件,並把讀事件的處理函式設定為ngx_http_discarded_request_body_handler if (ngx_handle_read_event(rev, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } //做好這些準備之後,該函式最後呼叫ngx_http_read_discarded_request_body()介面讀取客戶端過來的請求體並丟棄 //如果客戶端並沒有一次將請求體發過來,函式會返回,剩餘的資料等到下一次讀事件過來時,交給ngx_http_discarded_request_body_handler()來處理 //這時,請求的discard_body將被設定為1用來標識這種情況。另外請求的引用數(count)也被加1,這樣做的目的是客戶端可能在nginx處理完請求之後仍未完整傳送待傳送的請求體,增加引用是防止nginx核心在處理完請求後直接釋放了請求的相關資源 r->count++; r->discard_body = 1; return NGX_OK; }
ngx_http_discarded_request_body_handler,這個函式每次讀事件來時會被呼叫
void ngx_http_discarded_request_body_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_msec_t timer; ngx_event_t *rev; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; c = r->connection; rev = c->read; //函式一開始就處理了讀事件超時的情況 if (rev->timedout) { c->timedout = 1; c->error = 1; ngx_http_finalize_request(r, NGX_ERROR); return; } if (r->lingering_time) { timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time(); if ((ngx_msec_int_t) timer <= 0) { r->discard_body = 0; r->lingering_close = 0; ngx_http_finalize_request(r, NGX_ERROR); return; } } else { timer = 0; } //如果讀事件發生在請求處理完之前,則不用處理超時事件,也不用設定定時器,函式只是簡單的呼叫ngx_http_read_discarded_request_body()來讀取並丟棄資料 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; } if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { c->error = 1; ngx_http_finalize_request(r, NGX_ERROR); return; } /* rc == NGX_AGAIN */ if (ngx_handle_read_event(rev, 0) != NGX_OK) { c->error = 1; ngx_http_finalize_request(r, NGX_ERROR); return; } if (timer) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); timer *= 1000; if (timer > clcf->lingering_timeout) { timer = clcf->lingering_timeout; } ngx_add_timer(rev, timer); } }
Relevant Link:
http://book.51cto.com/art/201303/386672.htm http://blog.csdn.net/russell_tao/article/details/5637545 http://blog.csdn.net/yusiguyuan/article/details/41288443 http://blog.csdn.net/yusiguyuan/article/details/41288619 http://blog.csdn.net/chosen0ne/article/details/7861048
Copyright (c) 2015 LittleHann All rights reserved