您现在的位置是:首页 > 文章详情

nginx请求行读取流程

日期:2020-03-19点击:395

        在前面的文章中我们讲解了当一个请求到达时,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()方法中,如下是该方法的源码:

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; } } } } 

        对于这里的主流程,我们可以看到,其首先是检查读事件是否过期,如果过期了,则直接返回。如果没有过期,则进入一个无限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()方法的源码:

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; } 

        这里读取请求行的数据流程整体也比较简单,其主要流程如下:

  • 检查当前读取缓冲区中是否存在还未处理的数据,如果存在,则直接返回,以让后面的ngx_http_parse_request_line()方法解析这些数据;
  • 检查当前的事件是否处于可执行状态,是则调用c->recv()方法从连接的句柄上读取数据,返回值n表示读取到的数据大小;
  • 如果当前事件处于不可执行状态,则将当前事件添加到事件框架中,并且在epoll句柄上为当前连接注册读事件;
  • 在第二步中,如果读取数据的返回值是NGX_ERROR或者0(0表示客户端断开了连接),则给客户端返回400响应码。

        可以看到,这里对于请求行数据的处理过程,简单的说就是如果存在数据就读取,如果不存在则注册当前的读取事件。

3. 小结

        本文主要讲解了nginx是如何读取请求行数据,并且对请求行数据进行解析的,并且着重说明了在读取请求行数据时,如果读取到的数据不完整时,nginx是如何通过事件模型进行处理的。

原文链接:https://my.oschina.net/zhangxufeng/blog/3198036
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章