1. 程式人生 > 實用技巧 >http 結構初始化

http 結構初始化

  簡要而說:accept 到連線後 根據fd 構建一個connection 由於是 http ; 重新封裝為http-connection;同時設定fd的讀回撥;

回撥函式根據是否是https/http 進行區別

ngx_http_init_connection(ngx_connection_t *c) 
//當建立連線後開闢ngx_http_connection_t結構,這裡面儲存該伺服器端ip:port所在server{}上下文配置資訊,和server_name資訊等,然後讓
//ngx_connection_t->data指向該結構,這樣就可以通過ngx_connection_t->data獲取到伺服器端的serv loc 等配置資訊以及該server{}中的server_name資訊
{ ngx_uint_t i; ngx_event_t *rev; struct sockaddr_in *sin; ngx_http_port_t *port; ngx_http_in_addr_t *addr; ngx_http_log_ctx_t *ctx; ngx_http_connection_t *hc; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; ngx_http_in6_addr_t
*addr6; #endif //注意ngx_connection_t和ngx_http_connection_t的區別,前者是建立連線accept前使用的結構,後者是連線成功後使用的結構 // 使用分層的思想看待問題 ev_connect &&& http_connect hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); if (hc == NULL) { ngx_http_close_connection(c); return; }
//在伺服器端accept客戶端連線成功(ngx_event_accept)後,通過ngx_get_connection從連線池獲取一個ngx_connection_t結構, //每個客戶端連線對於一個ngx_connection_t結構,並且為其分配一個ngx_http_connection_t結構,ngx_connection_t->data = ngx_http_connection_t, c->data = hc; /* find the server configuration for the address:port */ port = c->listening->servers; if (port->naddrs > 1) { /* * there are several addresses on this port and one of them * is an "*:port" wildcard so getsockname() in ngx_http_server_addr() * is required to determine a server address */ //說明listen ip:port存在幾條沒有bind選項,並且存在萬用字元配置,如listen *:port,那麼就需要通過ngx_connection_local_sockaddr來確定 //究竟客戶端是和那個本地ip地址建立的連線 if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { // ngx_http_close_connection(c); return; } switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) c->local_sockaddr; addr6 = port->addrs; /* the last address is "*" */ for (i = 0; i < port->naddrs - 1; i++) { if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) { break; } } hc->addr_conf = &addr6[i].conf; break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *) c->local_sockaddr; addr = port->addrs; /* the last address is "*" */ //根據上面的ngx_connection_local_sockaddr函式獲取到客戶端連線到本地,本地IP地址獲取到後,遍歷ngx_http_port_t找到對應 //的IP地址和埠,然後賦值給ngx_http_connection_t->addr_conf,這裡面儲存有server_name配置資訊以及該ip:port對應的上下文資訊 for (i = 0; i < port->naddrs - 1; i++) { if (addr[i].addr == sin->sin_addr.s_addr) { break; } } /* 這裡也體現了在ngx_http_init_connection中獲取http{}上下文ctx,如果客戶端請求中帶有host引數,則會繼續在ngx_http_set_virtual_server 中重新獲取對應的server{}和location{},如果客戶端請求不帶host頭部行,則使用預設的server{},見 ngx_http_init_connection */ hc->addr_conf = &addr[i].conf; break; } } else { switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: addr6 = port->addrs; hc->addr_conf = &addr6[0].conf; break; #endif default: /* AF_INET */ addr = port->addrs; hc->addr_conf = &addr[0].conf; break; } } /* the default server configuration for the address:port */ //listen add:port對於的 server{}配置塊的上下文ctx hc->conf_ctx = hc->addr_conf->default_server->ctx; ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { ngx_http_close_connection(c); return; } ctx->connection = c; ctx->request = NULL; ctx->current_request = NULL; c->log->connection = c->number; c->log->handler = ngx_http_log_error; c->log->data = ctx; c->log->action = "waiting for request"; c->log_error = NGX_ERROR_INFO; rev = c->read; // 設定read-ev 的回撥 rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; #if (NGX_HTTP_V2) /* 這裡放在SSL的前面是,如果沒有配置SSL,則直接不用進行SSL協商而進行HTTP2處理ngx_http_v2_init */ if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; } #endif #if (NGX_HTTP_SSL) { ngx_http_ssl_srv_conf_t *sscf; sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); if (sscf->enable || hc->addr_conf->ssl) { c->log->action = "SSL handshaking"; if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "no \"ssl_certificate\" is defined " "in server listening on SSL port"); ngx_http_close_connection(c); return; } hc->ssl = 1;//如果是 ssl/tls 後面 會進入 tls 的握手 並且關聯sock-fd和 ssl 同時設定read-ev的回撥解析tls協商 rev->handler = ngx_http_ssl_handshake; } } #endif if (hc->addr_conf->proxy_protocol) { hc->proxy_protocol = 1; c->log->action = "reading PROXY protocol"; } /* 如果新連線的讀事件ngx_event_t結構體中的標誌位ready為1,實際上表示這個連線對應的套接字快取上已經有使用者發來的資料, 這時就可呼叫上面說過的ngx_http_init_request方法處理請求。 */ //這裡只可能是當listen的時候添加了defered引數並且核心支援,在ngx_event_accept的時候才會置1,才可能執行下面的if裡面的內容,否則不會只需if裡面的內容 if (rev->ready) { /* the deferred accept(), iocp */ if (ngx_use_accept_mutex) { //如果是配置了accept_mutex,則把該rev->handler延後處理, //實際上執行的地方為ngx_process_events_and_timers中的ngx_event_process_posted ngx_post_event(rev, &ngx_posted_events); return; } rev->handler(rev); //ngx_http_wait_request_handler return; } /* 在有些情況下,當TCP連線建立成功時同時也出現了可讀事件(例如,在套接字listen配置時設定了deferred選項時,核心僅在套接字上確實收到請求時才會通知epoll 排程事件的回撥方法。當然,在大部分情況下,ngx_http_init_request方法和 ngx_http_init_connection方法都是由兩個事件(TCP連線建立成功事件和連線上的可讀事件)觸發呼叫的 */ /* 呼叫ngx_add_timer方法把讀事件新增到定時器中,設定的超時時間則是nginx.conf中client_header_timeout配置項指定的引數。 也就是說,如果經過client_header_timeout時間後這個連線上還沒有使用者資料到達,則會由定時器觸發呼叫讀事件的ngx_http_init_request處理方法。 *///把接收事件新增到定時器中,當post_accept_timeout秒還沒有客戶端資料到來,就關閉連線-----幹掉 idle 連線 ngx_add_timer(rev, c->listening->post_accept_timeout, NGX_FUNC_LINE); ngx_reusable_connection(c, 1); //將fd epoll add 到 ep 監聽;當下次有資料從客戶端傳送過來的時候,會在ngx_epoll_process_events把對應的ready置1。 if (ngx_handle_read_event(rev, 0, NGX_FUNC_LINE) != NGX_OK) { // ngx_http_close_connection(c); return; } }