在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
本篇文章主要会介绍nginx中请求的接收流程,包括请求头的解析和请求体的读取流程。
Request = Request-Line *(( general-header | request-header | entity-header ) CRLF) CRLF [ message-body ] </span>
第一行是请求行(request line),用来说明请求方法,要访问的资源以及所使用的HTTP版本: 请求方法(Method)的定义如下,其中最常用的是GET,POST方法: Method = "OPTIONS" | "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE" | "CONNECT" | extension-method extension-method = token 要访问的资源由统一资源地位符URI(Uniform Resource Identifier)确定,它的一个比较通用的组成格式(rfc2396)如下: <scheme>://<authority><path>?<query> 一般来说根据请求方法(Method)的不同,请求URI的格式会有所不同,通常只需写出path和query部分。 http版本(version)定义如下,现在用的一般为1.0和1.1版本: HTTP/<major>.<minor>
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ... /* 初始化用来管理所有定时器的红黑树 */ 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; } ... /* 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_WIN32) /* windows环境下不做分析,但原理类似 */ #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; } 当一个工作进程在某个时刻将监听事件挂载上事件处理模型之后,nginx就可以正式的接收并处理客户端过来的请求了。这时如果有一个用户在浏览器的地址栏内输入一个域名,并且域名解析服务器将该域名解析到一台由nginx监听的服务器上,nginx的事件处理模型接收到这个读事件之后,会速度交由之前注册好的事件处理函数ngx_event_accept来处理。
ngx_http_init_request函数主要工作即是初始化请求,由于它是一个事件处理函数,它只有唯一一个ngx_event_t *类型的参数,ngx_event_t 结构在nginx中表示一个事件,事件处理的上下文类似于一个中断处理的上下文,为了在这个上下文得到相关的信息,nginx中一般会将连接结构的引用保存在事件结构的data字段,请求结构的引用则保存在连接结构的data字段,这样在事件处理函数中可以方便的得到对应的连接结构和请求结构。进入函数内部看一下,首先判断该事件是否是超时事件,如果是的话直接关闭连接并返回;反之则是指之前accept的连接上有请求过来需要处理,ngx_http_init_request函数首先在连接的内存池中为该请求分配一个ngx_http_request_t结构,这个结构将用来保存该请求所有的信息。分配完之后,这个结构的引用会被包存在连接的hc成员的request字段,以便于在长连接或pipelined请求中复用该请求结构。在这个函数中,nginx根据该请求的接收端口和地址找到一个默认虚拟服务器配置(listen指令的default_server属性用来标识一个默认虚拟服务器,否则监听在相同端口和地址的多个虚拟服务器,其中第一个定义的则为默认),因为在nginx配置文件中可以设置多个监听在不同端口和地址的虚拟服务器(每个server块对应一个虚拟服务器),另外还根据域名(server_name指令可以配置该虚拟服务器对应的域名)来区分监听在相同端口和地址的虚拟服务器,每个虚拟服务器可以拥有不同的配置内容,而这些配置内容决定了nginx在接收到一个请求之后如何处理该请求。找到之后,相应的配置被保存在该请求对应的ngx_http_request_t结构中。注意这里根据端口和地址找到的默认配置只是临时使用一下,最终nginx会根据域名找到真正的虚拟服务器配置,随后的初始化工作还包括: 将连接的读事件的处理函数设置为ngx_http_process_request_line函数,这个函数用来解析请求行,将请求的read_event_handler设置为ngx_http_block_reading函数,这个函数实际上什么都不做(当然在事件模型设置为水平触发时,唯一做的事情就是将事件从事件模型监听列表中删除,防止该事件一直被触发),后面会说到这里为什么会将read_event_handler设置为此函数;
丢弃请求体 一个模块想要主动的丢弃客户端发过的请求体,可以调用nginx核心提供的ngx_http_discard_request_body()接口,主动丢弃的原因可能有很多种,如模块的业务逻辑压根不需要请求体 ,客户端发送了过大的请求体,另外为了兼容http1.1协议的pipeline请求,模块有义务主动丢弃不需要的请求体。总之为了保持良好的客户端兼容性,nginx必须主动丢弃无用的请求体。下面开始分析ngx_http_discard_request_body()函数: ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r) { ssize_t size; ngx_event_t *rev; if (r != r->main || r->discard_body) { return NGX_OK; } 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->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; if (ngx_handle_read_event(rev, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_read_discarded_request_body(r) == NGX_OK) { r->lingering_close = 0; } else { r->count++; r->discard_body = 1; } return NGX_OK; } 由于函数不长,这里把它完整的列出来了,函数的开始同样先判断了不需要再做处理的情况:子请求不需要处理,已经调用过此函数的也不需要再处理。接着调用ngx_http_test_expect() 处理http1.1 expect的情况,根据http1.1的expect机制,如果客户端发送了expect头,而服务端不希望接收请求体时,必须返回417(Expectation Failed)错误。nginx并没有这样做,它只是简单的让客户端把请求体发送过来,然后丢弃掉。接下来,函数删掉了读事件上的定时器,因为这时本身就不需要请求体,所以也无所谓客户端发送的快还是慢了,当然后面还会将到,当nginx已经处理完该请求但客户端还没有发送完无用的请求体时,nginx会在读事件上再挂上定时器。 接下来,如果还有剩余的请求体未处理,该函数调用ngx_handle_read_event()在事件处理机制中挂载好读事件,并把读事件的处理函数设置为ngx_http_discarded_request_body_handler。做好这些准备之后,该函数最后调用ngx_http_read_discarded_request_body()接口读取客户端过来的请求体并丢弃。如果客户端并没有一次将请求体发过来,函数会返回,剩余的数据等到下一次读事件过来时,交给ngx_http_discarded_request_body_handler()来处理,这时,请求的discard_body将被设置为1用来标识这种情况。另外请求的引用数(count)也被加1,这样做的目的是客户端可能在nginx处理完请求之后仍未完整发送待发送的请求体,增加引用是防止nginx核心在处理完请求后直接释放了请求的相关资源。 ngx_http_read_discarded_request_body()函数非常简单,它循环的从链接中读取数据并丢弃,直到读完接收缓冲区的所有数据,如果请求体已经被读完了,该函数会设置读事件的处理函数为ngx_http_block_reading,这个函数仅仅删除水平触发的读事件,防止同一事件不断被触发。 void ngx_http_discarded_request_body_handler(ngx_http_request_t *r) { ... 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_time()); if (timer <= 0) { r->discard_body = 0; r->lingering_close = 0; ngx_http_finalize_request(r, NGX_ERROR); return; } } else { timer = 0; } 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; } /* 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); } } 函数一开始就处理了读事件超时的情况,之前说到在ngx_http_discard_request_body()函数中已经删除了读事件的定时器,那么什么时候会设置定时器呢?答案就是在nginx已经处理完该请求,但是又没有完全将该请求的请求体丢弃的时候(客户端可能还没有发送过来),在ngx_http_finalize_connection()函数中,如果检查到还有未丢弃的请求体时,nginx会添加一个读事件定时器,它的时长为lingering_timeout指令所指定,默认为5秒,不过这个时间仅仅两次读事件之间的超时时间,等待请求体的总时长为lingering_time指令所指定,默认为30秒。这种情况中,该函数如果检测到超时事件则直接返回并断开连接。同样,还需要控制整个丢弃请求体的时长不能超过lingering_time设置的时间,如果超过了最大时长,也会直接返回并断开连接。 |
请发表评论