之前已经介绍过nginx的事件框架。那么,对于client发出的一个http的请求,nginx的http框架是如何一步步解析这个http请求?http框架又是如何和之前介绍过得epoll事件模块结合起来的,下面来简要介绍下。

注:我手头上的nginx工程是nginx-1.9.14的,与《深入理解nginx》的版本不一致,在http框架这块的代码上也有着较大的区别。

一.ngx_http_init_connection

在http框架初始化的时候(参见《深入理解nginx》第10章),会将每个ngx_listening_t结构体的handler方法设为ngx_http_init_connection,这是框架初始化的时候完成的工作。在整个系统正常工作起来之后,client每发出一个新的http连接请求,nginx的事件模块会对这个请求进行处理,最后在ngx_event_accept函数里面会调用accept系统调用来接收这个请求。而在ngx_event_accept函数的最后会调用ls->handler即ngx_http_init_connection函数。这样一来,新的http连接就会来到http框架中的函数来进行后续的解析和处理。

void
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前使用的结构,后者是连接成功后使用的结构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,见ngx_http_init_connectionc->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;
#endifdefault: /* 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;
#endifdefault: /* AF_INET */addr = port->addrs;hc->addr_conf = &addr[0].conf;break;}}/* the default server configuration for the address:port *///listen add:port对于的 server{}配置块的上下文ctxhc->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;rev->handler = ngx_http_wait_request_handler;c->write->handler = ngx_http_empty_handler;#if (NGX_HTTP_SPDY)if (hc->addr_conf->spdy) {rev->handler = ngx_http_spdy_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;rev->handler = ngx_http_ssl_handshake;}}
#endifif (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_postedngx_post_event(rev, &ngx_posted_events);return;}rev->handler(rev); //ngx_http_wait_request_handlerreturn;}

这个函数最核心的地方是设置c->read->handler和c->write->handler。因为此时TCP连接已经建立了,后续当epoll_wait返回事件的时候,需要完成的就不是TCP连接操作而是数据接收处理操作。所以这里把handler设置成了ngx_http_wait_request_handler二.ngx_http_wait_request_handler

//客户端建立连接后,只有第一次读取客户端数据到数据的时候,执行的handler指向该函数,因此当客户端连接建立成功后,只有第一次读取
//客户端数据才会走该函数,如果在保活期内又收到客户端请求,则不会再走该函数,而是执行ngx_http_process_request_line,因为该函数
//把handler指向了ngx_http_process_request_line
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{u_char                    *p;size_t                     size;ssize_t                    n;ngx_buf_t                 *b;ngx_connection_t          *c;ngx_http_connection_t     *hc;ngx_http_core_srv_conf_t  *cscf;c = rev->data;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");if (rev->timedout) { //如果tcp连接建立后,等了client_header_timeout秒一直没有收到客户端的数据包过来,则关闭连接ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");ngx_http_close_connection(c);return;}if (c->close) {ngx_http_close_connection(c);return;}hc = c->data;cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);size = cscf->client_header_buffer_size; //默认1024b = c->buffer;if (b == NULL) {b = ngx_create_temp_buf(c->pool, size);if (b == NULL) {ngx_http_close_connection(c);return;}c->buffer = b;} else if (b->start == NULL) {b->start = ngx_palloc(c->pool, size);if (b->start == NULL) {ngx_http_close_connection(c);return;}b->pos = b->start;b->last = b->start;b->end = b->last + size;}//这里如果一次没有把所有客户端的数据读取完,则在ngx_http_process_request_line中会继续读取//与ngx_http_read_request_header配合读n = c->recv(c, b->last, size);  //读取客户端来的数据    执行ngx_unix_recvif (n == NGX_AGAIN) {       //nginx里面采用的都是非阻塞的recv,因此当执行recv时候可能会出现还没传送完的情形,这时候recv实际上就会返回EAGAIN错误if (!rev->timer_set) {ngx_add_timer(rev, c->listening->post_accept_timeout, NGX_FUNC_LINE);ngx_reusable_connection(c, 1);}if (ngx_handle_read_event(rev, 0, NGX_FUNC_LINE) != NGX_OK) {ngx_http_close_connection(c);return;}/** We are trying to not hold c->buffer's memory for an idle connection.*/if (ngx_pfree(c->pool, b->start) == NGX_OK) {b->start = NULL;}return;}if (n == NGX_ERROR) {ngx_http_close_connection(c);return;}if (n == 0) {ngx_log_error(NGX_LOG_INFO, c->log, 0,"client closed connection");ngx_http_close_connection(c);return;}b->last += n;if (hc->proxy_protocol) {hc->proxy_protocol = 0;p = ngx_proxy_protocol_read(c, b->pos, b->last);if (p == NULL) {ngx_http_close_connection(c);return;}b->pos = p;if (b->pos == b->last) {c->log->action = "waiting for request";b->pos = b->start;b->last = b->start;ngx_post_event(rev, &ngx_posted_events);return;}}c->log->action = "reading client request line";ngx_reusable_connection(c, 0);//从新让c->data指向新开辟的ngx_http_request_tc->data = ngx_http_create_request(c);if (c->data == NULL) {ngx_http_close_connection(c);return;}rev->handler = ngx_http_process_request_line;ngx_http_process_request_line(rev);
}

当进入这个函数的时候,一定是客户端开始往client发实际的数据了(像HTTP头,请求行等等)。那么在这个函数里面会先调用recv来接收下。由于nginx里面的recv都是非阻塞的,因此当前的recv可能会没接收到数据(比如出现client数据还没发送完这样的情况,此时recv会返回EAGAIN,这个并不是出错,而是让程序过一会再来recv看看。在非阻塞程序里面比较常见。)当出现EAGAIN的时候,需要再次把该事件注册到epoll里面去。这是因为nginx里面的epoll采用的是ET触发模式,epoll_wait模式将无法再次获取该事件,所以需要重新进行注册。然后函数会直接return,将控制权交换给HTTP框架。

ps. 有一个疑惑:如果epoll提示监听的读fd上有数据来了,但是取出该读fd, 使用recv系统调用的返回值是EAGAIN。这具体是什么原因导致的?epoll既然提示,那么该fd的接收缓存中应该存有一定的可读数据才对?

如果recv返回的结果是n>0。说明此时接收到client传来的数据了,但是只recv一次可能没法读取到所有的数据,而且TCP发送端的缓存区也很可能存不下整个HTTP请求行。所以需要采取额外的措施来继续接收数据,并且判断是否接收到了完成的HTTP请求行。nginx是专门实现了一个函数ngx_http_process_request_line来完成这个事,本函数后来把handler指向了ngx_http_process_request_line。

三.ngx_http_process_request_line

static void
ngx_http_process_request_line(ngx_event_t *rev) //gx_http_process_request_line方法来接收HTTP请求行
{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");/*检查这个读事件是否已经超时,超时时间仍然是nginx.conf配置文件中指定的client_header_timeout。如果ngx_event_t事件的timeout标志为1,则认为接收HTTP请求已经超时,调用ngx_http_close_request方法关闭请求,同时由ngx_http_process_request_line方法中返回。*/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;//读取一行数据,分析出请求行中包含的method、uri、http_version信息。然后再一行一行处理请求头,并根据请求method与请求头的信息来决定
//是否有请求体以及请求体的长度,然后再去读取请求体for ( ;; ) {if (rc == NGX_AGAIN) {n = ngx_http_read_request_header(r);if (n == NGX_AGAIN || n == NGX_ERROR) { //如果内核中的数据已经读完,但这时候头部字段还没有解析完毕,则把控制器交还给HTTP,当数据到来的时候触发//ngx_http_process_request_line,因为该函数外面rev->handler = ngx_http_process_request_line;return;}}rc = ngx_http_parse_request_line(r, r->header_in);if (rc == NGX_OK) { //请求行解析成功/* the request line has been parsed successfully *///请求行内容及长度    //GET /sample.jsp HTTP/1.1整行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);//请求方法 GET  POST等    //GET /sample.jsp HTTP/1.1  中的GETr->method_name.len = r->method_end - r->request_start + 1;r->method_name.data = r->request_line.data;//GET /sample.jsp HTTP/1.1  中的HTTP/1.1if (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;}r->headers_in.server = host;}if (r->http_version < NGX_HTTP_VERSION_10) { //1.0以下版本没有请求头部字段,/*用户请求的HTTP版本小于1.0(如HTTP 0.9版本),其处理过程将与HTTP l.0和HTTP l.1的完全不同,它不会有接收HTTP头部这一步骤。这时将会调用ngx_http_find_virtual_server方法寻找到相应的虚拟主机?                    */if (r->headers_in.server.len == 0&& ngx_http_set_virtual_server(r, &r->headers_in.server) //http0.9应该是从请求行获取虚拟主机?== NGX_ERROR){return;}ngx_http_process_request(r);return;}//初始化用于存放http头部行的空间,用来存放http头部行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);//开始解析http头部行return;}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;}//表示该行内容不够,例如recv读取的时候,没有把整行数据读取出来,返回后继续recv,然后接着上次解析的位置继续解析直到请求行解析完毕/* NGX_AGAIN: a request line parsing is still incomplete *//*如果ngx_http_parse_request_line方法返回NGX_AGAIN,则表示需要接收更多的字符流,这时需要对header_in缓冲区做判断,检查是否还有空闲的内存,如果还有未使用的内存可以继续接收字符流,则跳转到第2步,检查缓冲区是否有未解析的字符流,否则调用ngx_http_alloc_large_header_buffer方法分配更大的接收缓冲区。到底分配多大呢?这由nginx.conf文件中的large_client_header_buffers配置项指定。*/if (r->header_in->pos == r->header_in->end) {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;}}//表示头部行没有解析完成,继续读数据解析}
}

在ngx_http_process_request_line这个函数里面,首先是判断是否超时。如果不超时的话,那么接下来首先是进入 n = ngx_http_read_request_header(r);函数。 在这个函数里面,先是判断当前用户态缓存区里面是否有一些还没解析的数据。如果存在一些未解析的数据,那么会继续下去调用ngx_http_parse_request_line来进行解析请求行。如果解析成功,则后面会继续解析请求头。但是也可能解析失败,因为TCP是字节流的服务,当前收到的字节可能还没有涵盖整个请求行。所以rc的状态会再变成EAGAIN,然后再次进入 ngx_http_read_request_header(r),在这个函数里面会尝试调用recv来接收数据。直至接收到足够的数据,以成功解析请求行。

nginx学习笔记七(nginx HTTP框架的执行流程)相关推荐

  1. nginx 学习笔记(2) nginx新手入门

    这篇手册简单介绍了nginx,并提供了一些可以操作的简单的工作.前提是nginx已经被安装到你的服务器上.如果没有安装,请阅读上篇:nginx 学习笔记(1) nginx安装.这篇手册主要内容:1. ...

  2. nginx 学习笔记(1) nginx安装

    1.nginx安装 根据操作系统的不同,nginx的安装方式也不相同. 1.1 对linux系统来说,nginx.org提供了nginx安装包.http://nginx.org/en/linux_pa ...

  3. nginx学习笔记(7)Nginx如何处理一个请求---转载

    如何防止处理未定义主机名的请求 基于域名和IP混合的虚拟主机 一个简单PHP站点配置 基于名字的虚拟主机 Nginx首先选定由哪一个虚拟主机来处理请求.让我们从一个简单的配置(其中全部3个虚拟主机都在 ...

  4. nginx 学习笔记(5) nginx调试日志

    为启动一个调试日志,nginx需要在构建时配置城支持调试模式. ./configure --with-debug ... 而且调试级别应该使用err_log指令来设置: err_log /path/t ...

  5. nginx 学习笔记(3) nginx管理

    nginx可以通过向其发送信号来进行管理.默认情况下主进程的进程ID写到文件/usr/local/nginx/logs/nginx.pid中.当然也可以在配置文件中自定义该pid文件,自定义使用pid ...

  6. nginx 学习笔记(6) nginx配置文件中的度量单位

    容量大小可以用比特(byte),千比特(kilobyte,后缀k或者K)或者兆(megabytes,后缀m或者M),例如:"1024","8k","1 ...

  7. Nginx学习笔记09——URLRewrite伪静态

    URLRewrite 伪静态配置 因为jsp获取url参数很方便,所以需要将tomcat服务器上的index.html转为jsp格式的,方式是在html文件开头加上<%@ page conten ...

  8. Nginx 学习笔记

    Nginx 学习笔记 文章目录 Nginx 学习笔记 1.Nginx 的简介 1.1正向代理 1.2反向代理 1.3负载均衡 1.4动静分离 1.5Nginx的安装 1.6Nginx的常用命令 1.7 ...

  9. 全是精髓!也许是最完美的“Nginx学习笔记”了,阿里云高工“365”天手写!

    前言 "Nginx",一个高性能的HTTP和反向代理web服务器,Nginx因为内存少.并发能力强的特性,深受虚拟机主机提供商的欢迎,可以支持高达50000个并发连接数的响应.Ng ...

最新文章

  1. GitHub被中国人霸榜!国外开发者不开心了
  2. JSON.stringify() 方法
  3. AngularJs angular.element
  4. nmap 扫描工具 使用方法
  5. Spring Cloud学习系列第三篇【服务容错保护】
  6. CFA大起底:三百六十度无死角详解CFA到底是个啥?
  7. linux-arm下如何开启tftp传输,linux-arm间tftp命令的安装、使用
  8. Numerical Analysis(by Timothy Sauer) Notes
  9. flushia系统_IA 系统和应用 第七章 环境组态.pdf
  10. 【Python】列表 - 集大成篇
  11. 视频批量添加滚动字幕,我1分钟就搞定了
  12. Scrach基本概念与操作
  13. COLA异步任务重复执行?
  14. PHP 函数、类声明和调用
  15. c#位数不够0补充完_C# 如何对数字不足位数时前面(左边)补零呢?
  16. MySQL 中的 CURDATE 函数与相关方法
  17. markdwon常用格式
  18. Unity打IOS版本遇到的问题(总)
  19. 蓝牙遥控器与小米盒子增强版配对指南
  20. Java单元测试工具:JUnit4(一)——概述及简单例子

热门文章

  1. 一次哔哩哔哩面试经历,吐血整理
  2. 考研 数学1 2 3 区别
  3. linux rtc与时钟
  4. python爬虫可视化题目北京空气质量监测数据获取与分析
  5. 唐山乐高机器人_唐山青少年乐高机器人编程学校
  6. 关于使用Kmeans函数RuntimeWarning: Explicit initial center position passed: performing only one init in KMe
  7. IE浏览器打不开网页
  8. python中随机生成数字方法
  9. 股价上涨,资金流出以及内外盘的关系
  10. Ubuntu虚拟机使用桥接网络设置静态IP