对于活跃的 HTTP 连接,在执行连接建立回调函数 ngx_http_init_connection 的过程中会执行 ngx_http_wait_request_handler 回调函数,
负责 HTTP 请求的接收与 HTTP 请求描述结构的创建和初始化,并且第一次读取客户端数据到数据。
因此当客户端连接建立成功后,只有第一次读取客户端数据才会走该函数,如果在保活期内又收到客户端请求,则不会再走该函数,而是执行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; //默认1024
b = c->buffer;/\* 每次读取数据的buf大小 \*/
// 创建并初始化缓冲结构
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\_recv调用 recv 从 TCP 流上读取数据来的数据 执行ngx\_unix\_recv
/**----/* NGX_AGAIN* 这个是一个Http连接第一次等待读取数据,如果第一次接收的数据为空,则表示当前客户端连接上来了,但是数据还未上来,
则将当前连接上的读事件添加到定时器机制中,同时将读事件注册到epoll 事件机制中,return 从当前函数返回-----表示有错误数据了???????????????????????
if (n == NGX_AGAIN) {// 需要重新加入队列
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.
有读取到数据,则Nginx会将buf内存删除,然后继续等待read事件的到来,好处是防止大量非法请求上来,又占用内存不释放,导致Nginx内存暴涨。
*/
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); // 从连接池中删除连接
-----------/* 调用 ngx_http_create_request 方法构造ngx_http_request_t 请求结构体,并设置到当前连接的data 成员 ----k*/
//从新让c->data指向新开辟的ngx_http_request_t -----同时http-requets---->http-con===hc
c->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); // 解析 HTTP 请求行
}
首先创建并初始化了一段缓冲区,并调用 ngx_unix_recv 函数从 TCP 流上将数据读取到缓冲区中如果需要重新监听,
则把事件重新加入到定时器、把连接重新添加到连接池并退出否则在这以后,先后调用
ngx_reusable_connection -- 删除连接池中的该连接
ngx_http_create_request -- 创建并初始化 HTTP 请求描述结构
ngx_http_process_request_line -- 解析 HTTP 请求
/*
POST / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20200225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
name=Professional%20Ajax&publisher=Wisley
*/
/*
请求的所有信息(如方法、URI、协议版本号和头部等)都可以在传入的ngx_http_request_t类型参数r中取得。
ngx_http_request_t结构体的内容很多,本节不会探讨ngx_http_request_t中所有成员的意义
*/
/*
ngx_http_core_main_conf_t->variabels数组成员的结构式ngx_http_variable_s, ngx_http_request_s->variabels数组成员结构是
ngx_variable_value_t这两个结构的关系很密切,一个所谓变量,一个所谓变量值
*/
/*
对于每个ngx_http_request_t请求来说,只能访问一个上游服务器,但对于一个客户端请求来说,可以派生出许多子请求,任何一个子请求都
可以访问一个上游服务器,这些子请求的结果组合起来就可以使来自客户端的请求处理复杂的业务。
*/
//ngx_http_parse_request_line解析请求行, ngx_http_process_request_headers解析头部行(请求头部)
//请求的所有信息都可以都可以在ngx_http_request_s结构获取到
struct ngx_http_request_s { //当接收到客户端请求数据后,调用ngx_http_create_request中创建并赋值
uint32_t signature; /* "HTTP" */
/\*
在接收到客户端数据后,会创建一个ngx\_http\_request\_s,其connection成员指向对应的accept成功后获取到的连接信息
ngx\_connection\_t,见ngx\_http\_create\_request
这个请求对应的客户端连接 如果该r是子请求,其connection成员指向顶层root父请求的ngx\_connection\_t,因为它们都是对应的同一个客户端连接,见ngx\_http\_subrequest
\*/
ngx\_connection\_t \*connection;
/\*
ctx与ngx\_http\_conf\_ctxt结构的3个数组成员非常相似,它们都
表示指向void指针的数组。HTTP框架就是在ctx数组中保存所有HTTP模块上下文结构体的指针的,所有模块的请求上下文空间在
ngx\_http\_create\_request中创建。获取和设置分别在ngx\_http\_get\_module\_ctx和ngx\_http\_set\_ctx,为每个请求创建ngx\_http\_request\_s的时候
都会为该请求的ctx\[\]为所有的模块创建一个指针,也就是每个模块在ngx\_http\_request\_s中有一个ctx
\*/ //在应答完后,在ngx\_http\_filter\_finalize\_request会把ctx指向的空间全部清0 参考4.5节
void \*\*ctx; //指向存放所有HTTP模块的上下文结构体的指针数组,实际上发送给客户端的应答完成后,会把ctx全部置0
/\*
当客户端建立连接后,并发送请求数据过来后,在ngx\_http\_create\_request中从ngx\_http\_connection\_t->conf\_ctx获取这三个值,也就是根据客户端连接
本端所处IP:port所对应的默认server{}块上下文,如果是以下情况:ip:port相同,单在不同的server{}块中,那么有可能客户端请求过来的时候携带的host
头部项的server\_name不在默认的server{}中,而在另外的server{}中,所以需要通过ngx\_http\_set\_virtual\_server重新获取server{}和location{}上下文配置
例如:
server { #1
listen 1.1.1.1:80;
server\_name aaa
}
server { #2
listen 1.1.1.1:80;
server\_name bbb
}
这个配置在ngx\_http\_init\_connection中把ngx\_http\_connection\_t->conf\_ctx指向ngx\_http\_addr\_conf\_s->default\_server,也就是指向#1,然后
ngx\_http\_create\_request中把main\_conf srv\_conf loc\_conf 指向#1,
但如果请求行的头部的host:bbb,那么需要重新获取对应的server{} #2,见ngx\_http\_set\_virtual\_server
*/ //ngx_http_create_request和ngx_http_set_virtual_server 已经rewrite过程中(例如ngx_http_core_find_location
//ngx_http_core_post_rewrite_phase ngx_http_internal_redirect ngx_http_internal_redirect 子请求ngx_http_subrequest)都可能对他们赋值
void **main_conf; //指向请求对应的存放main级别配置结构体的指针数组
void **srv_conf; //指向请求对应的存放srv级别配置结构体的指针数组 赋值见ngx_http_set_virtual_server
void **loc_conf; //指向请求对应的存放loc级别配置结构体的指针数组 赋值见ngx_http_set_virtual_server
/\*
在接收完HTTP头部,第一次在业务上处理HTTP请求时,HTTP框架提供的处理方法是ngx\_http\_process\_request。但如果该方法无法一次处
理完该请求的全部业务,在归还控制权到epoll事件模块后,该请求再次被回调时,将通过ngx\_http\_request\_handler方法来处理,而这个
方法中对于可读事件的处理就是调用read\_event\_handler处理请求。也就是说,HTTP模块希望在底层处理请求的读事件时,重新实现read\_event\_handler方法
//在读取客户端来的包体时,赋值为ngx\_http\_read\_client\_request\_body\_handler
丢弃客户端的包体时,赋值为ngx\_http\_discarded\_request\_body\_handler
\*/ //注意ngx\_http\_upstream\_t和ngx\_http\_request\_t都有该成员 分别在ngx\_http\_request\_handler和ngx\_http\_upstream\_handler中执行
ngx\_http\_event\_handler\_pt read\_event\_handler;
/\* 与read\_event\_handler回调方法类似,如果ngx\_http\_request\_handler方法判断当前事件是可写事件,则调用write\_event\_handler处理请求 \*/
/\*请求行和请求头部解析完成后,会在ngx\_http\_handler中赋值为ngx\_http\_core\_run\_phases 子请求的的handler为ngx\_http\_handler
当发送响应的时候,如果一次没有发送完,则设在为ngx\_http\_writer
\*/ //注意ngx\_http\_upstream\_t和ngx\_http\_request\_t都有该成员 分别在ngx\_http\_request\_handler和ngx\_http\_upstream\_handler中执行
//如果采用buffer方式缓存后端包体,则在发送包体给客户端浏览器的时候,会把客户端连接的write\_e\_hand置为ngx\_http\_upstream\_process\_downstream
//在触发epoll\_in的同时也会触发epoll\_out,从而会执行该函数
ngx\_http\_event\_handler\_pt write\_event\_handler;//父请求重新激活后的回调方法
#if (NGX_HTTP_CACHE)
//通过ngx_http_upstream_cache_get获取
ngx_http_cache_t *cache;//在客户端请求过来后,在ngx_http_upstream_cache->ngx_http_file_cache_new中赋值r->caceh = ngx_http_cache_t
#endif
/\*
如果没有使用upstream机制,那么ngx\_http\_request\_t中的upstream成员是NULL空指针,在ngx\_http\_upstream\_create中创建空间
\*/
ngx\_http\_upstream\_t \*upstream; //upstream机制用到的结构体
ngx\_array\_t \*upstream\_states; //创建空间和赋值见ngx\_http\_upstream\_init\_request
/\* of ngx\_http\_upstream\_state\_t \*/
/\*
表示这个请求的内存池,在ngx\_http\_free\_request方法中销毁。它与ngx\_connection-t中的内存池意义不同,当请求释放时,TCP连接可能并
没有关闭,这时请求的内存池会销毁,但ngx\_connection\_t的内存池并不会销毁
\*/
ngx\_pool\_t \*pool;
//其中,header\_in指向Nginx收到的未经解析的HTTP头部,这里暂不关注它(header\_in就是接收HTTP头部的缓冲区)。 header\_in存放请求行,headers\_in存放头部行
//请求行和请求头部内容都在该buffer中
ngx\_buf\_t \*header\_in;//用于接收HTTP请求内容的缓冲区,主要用于接收HTTP头部,该指针指向ngx\_connection\_t->buffer
//类型的headers\_in则存储已经解析过的HTTP头部。
/\*常用的HTTP头部信息可以通过r->headers\_in获取,不常用的HTTP头部则需要遍历r->headers\_in.headers来遍历获取\*/
/*
ngx_http_process_request_headers方法在接收、解析完HTTP请求的头部后,会把解析完的每一个HTTP头部加入到headers_in的headers链表中,同时会构造headers_in中的其他成员
*/ //参考ngx_http_headers_in,通过该数组中的回调hander来存储解析到的请求行name:value中的value到headers_in的响应成员中,见ngx_http_process_request_headers
//注意:在需要把客户端请求头发送到后端的话,在请求头后面可能添加有HTTP_相关变量,例如fastcgi,见ngx_http_fastcgi_create_request
ngx_http_headers_in_t headers_in; //http头部行解析后的内容都由该成员存储 header_in存放请求行,headers_in存放头部行
//只要指定headers_out中的成员,就可以在调用ngx_http_send_header时正确地把HTTP头部发出
//HTTP模块会把想要发送的HTTP响应信息放到headers_out中,期望HTTP框架将headers_out中的成员序列化为HTTP响应包发送给用户
ngx_http_headers_out_t headers_out;
//如果是upstream赋值的来源是后端服务器会有的头部行中拷贝,参考ngx_http_upstream_headers_in中的copy_handler
/*
接收完请求的包体后,可以在r->request_body->temp_file->file中获取临时文件(假定将r->request_body_in_file_only标志位设为1,那就一定可以
在这个变量获取到包体。)。file是一个ngx_file_t类型。这里,我们可以从
r->request_body->temp_file->file.name中获取Nginx接收到的请求包体所在文件的名称(包括路径)。
*/ //在ngx_http_read_client_request_body中分配存储空间 读取的客户端包体存储在r->request_body->bufs链表和临时文件r->request_body->temp_file中 ngx_http_read_client_request_body
//读取客户包体即使是存入临时文件中,当所有包体读取完毕后(见ngx_http_do_read_client_request_body),还是会让r->request_body->bufs指向文件中的相关偏移内存地址
//向上游发送包体u->request_bufs(ngx_http_fastcgi_create_request),接收客户端的包体在r->request_body
ngx_http_request_body_t *request_body; //接收HTTP请求中包体的数据结构,为NULL表示还没有分配空间
//min(lingering_time,lingering_timeout)这段时间内可以继续读取数据,如果客户端有发送数据过来,见ngx_http_set_lingering_close
time_t lingering_time; //延迟关闭连接的时间
//ngx_http_request_t结构体中有两个成员表示这个请求的开始处理时间:start sec成员和start msec成员
/*
当前请求初始化时的时间。start sec是格林威治时间1970年1月1日凌晨0点0分0秒到当前时间的秒数。如果这个请求是子请求,则该时间
是子请求的生成时间;如果这个请求是用户发来的请求,则是在建立起TCP连接后,第一次接收到可读事件时的时间
*/
time_t start_sec;
ngx_msec_t start_msec;//与start_sec配合使用,表示相对于start_set秒的毫秒偏移量
//以下9个成员都是ngx_http_proces s_request_line方法在接收、解析HTTP请求行时解析出的信息
/*
注意 Nginx中对内存的控制相当严格,为了避免不必要的内存开销,许多需要用到的成员都不是重新分配内存后存储的,而是直接指向用户请求中的相应地址。
例如,method_name.data、request_start这两个指针实际指向的都是同一个地址。而且,因为它们是简单的内存指针,不是指向字符串的指针,所以,在大部分情况下,都不能将这些u_char*指针当做字符串使用。
*/ //NGX_HTTP_GET | NGX_HTTP_HEAD等,为NGX_HTTP_HEAD表示只需要发送HTTP头部字段
/* HTTP2的method赋值见ngx_http_v2_parse_method */
ngx_uint_t method; //对应客户端请求中请求行的请求方法GET、POS等,取值见NGX_HTTP_GET,也可以用下面的method_name进行字符串比较
/*
http_protocol指向用户请求中HTTP的起始地址。
http_version是Nginx解析过的协议版本,它的取值范围如下:
#define NGX_HTTP_VERSION_9 9
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
建议使用http_version分析HTTP的协议版本。
最后,使用request_start和request_end可以获取原始的用户请求行。
*/
ngx_uint_t http_version;//http_version是Nginx解析过的协议版本,它的取值范围如下:
/* 如果是HTTP2,则赋值见ngx_http_v2_construct_request_line */
ngx_str_t request_line; //请求行内容
/*
2016/01/07 12:38:01[ ngx_http_process_request_line, 1002] [debug] 200#20090: *14 http request line: "GET /download/nginx-1.9.2.rar?st=xhWL03HbtjrojpEAfiD6Mw&e=1452139931 HTTP/1.1"
2016/01/07 12:38:01[ ngx_http_process_request_uri, 1223] [debug] 20090#20090: *14 http uri: "/download/nginx-1.9.2.rar"
2016/01/07 12:38:01[ ngx_http_process_request_uri, 1226] [debug] 20090#20090: *14 http args: "st=xhWL03HbtjrojpEAfiD6Mw&e=1452139931"
2016/01/07 12:38:01[ ngx_http_process_request_uri, 1229] [debug] 20090#20090: *14 http exten: "rar"
*/
//ngx_str_t类型的uri成员指向用户请求中的URI。同理,u_char*类型的uri_start和uri_end也与request_start、method_end的用法相似,唯一不
//同的是,method_end指向方法名的最后一个字符,而uri_end指向URI结束后的下一个地址,也就是最后一个字符的下一个字符地址(HTTP框架的行为),
//这是大部分u_char*类型指针对“xxx_start”和“xxx_end”变量的用法。
//http://10.135.10.167/mytest中的/mytest http://10.135.10.167/mytest?abc?ttt中的/mytest
//同时"GET /mytest?abc?ttt HTTP/1.1"中的mytest和uri中的一样
ngx_str_t uri;
//arg指向用户请求中的URL参数。 http://10.135.10.167/mytest?abc?ttt中的abc?ttt
//同时"GET /mytest?abc?ttt HTTP/1.1"中的mytest?abc?ttt和uri中的一样
/*把请求中GET /download/nginx-1.9.2.rar?st=xhWL03HbtjrojpEAfiD6Mw&e=1452139931 HTTP/1.1的st和e形成变量$arg_st #arg_e,value分别
为xhWL03HbtjrojpEAfiD6Mw 1452139931即$arg_st=xhWL03HbtjrojpEAfiD6Mw,#arg_e=1452139931,见ngx_http_arg */
ngx_str_t args;
/*
ngx_str_t类型的extern成员指向用户请求的文件扩展名。例如,在访问“GET /a.txt HTTP/1.1”时,extern的值是{len = 3, data = "txt"},
而在访问“GET /a HTTP/1.1”时,extern的值为空,也就是{len = 0, data = 0x0}。
uri_ext指针指向的地址与extern.data相同。
*/
ngx_str_t exten; //http://10.135.10.167/mytest/ac.txt中的txt
/*
url参数中出现+、空格、=、%、&、#等字符的解决办法
url出现了有+,空格,/,?,%,#,&,=等特殊符号的时候,可能在服务器端无法获得正确的参数值,如何是好?
解决办法
将这些字符转化成服务器可以识别的字符,对应关系如下:
URL字符转义
用其它字符替代吧,或用全角的。
& URL 中指定的参数间的分隔符 %26
= URL 中指定参数的值 %3D
*/
//unparsed_uri表示没有进行URL解码的原始请求。例如,当uri为“/a b”时,unparsed_uri是“/a%20b”(空格字符做完编码后是%20)。
ngx_str_t unparsed_uri;//参考:为什么要对URI进行编码:
/* HTTP2的method赋值见ngx_http_v2_parse_method,在组新的HTTP2头部行后,赋值见ngx_http_v2_construct_request_line */
ngx_str_t method_name;//见method GET POST等
ngx_str_t http_protocol;//GET /sample.jsp HTTP/1.1 中的HTTP/1.1
/* 当ngx_http_header_filter方法无法一次性发送HTTP头部时,将会有以下两个现象同时发生:请求的out成员中将会保存剩余的响应头部,见ngx_http_header_filter */
/* 表示需要发送给客户端的HTTP响应。out中保存着由headers_out中序列化后的表示HTTP头部的TCP流。在调用ngx_http_output_filter方法后,
out中还会保存待发送的HTTP包体,它是实现异步发送HTTP响应的关键 */
ngx_chain_t *out;//ngx_http_write_filter把in中的数据拼接到out后面,然后调用writev发送,没有发送完
/\* 当前请求既可能是用户发来的请求,也可能是派生出的子请求,而main则标识一系列相关的派生子请
求的原始请求,我们一般可通过main和当前请求的地址是否相等来判断当前请求是否为用户发来的原始请求 \*/
//main成员始终指向一系列有亲缘关系的请求中的唯一的那个原始请求,初始赋值见ngx\_http\_create\_request
//客户端的建立连接的时候r->main =r(ngx\_http\_create\_request),如果是创建子请求,sr->main = r->main(ngx\_http\_subrequest)子请求->main=最上层的r
/\* 主请求保存在main字段中,这里其实就是最上层跟请求,例如当前是四层子请求,则main始终指向第一层父请求,
而不是第三次父请求,parent指向第三层父请求 \*/
ngx\_http\_request\_t \*main; //赋值见ngx\_http\_subrequest
ngx\_http\_request\_t \*parent;//当前请求的父请求。注意,父请求未必是原始请求 赋值见ngx\_http\_subrequest
//ngx\_http\_subrequest中赋值,表示对应的子请求r,该结构可以表示子请求信息
//postponed删除在ngx\_http\_finalize\_request
//当客户端请求需要通过多个subrequest访问后端的时候,就需要对这多个后端的应答进行合适的顺序整理才能发往客户端
ngx\_http\_postponed\_request\_t \*postponed; //与subrequest子请求相关的功能 postponed中数据依次发送参考ngx\_http\_postpone\_filter方法
ngx\_http\_post\_subrequest\_t \*post\_subrequest;/\* 保存回调handler及数据,在子请求执行完,将会调用 \*/
/* 所有的子请求都是通过posted_requests这个单链表来链接起来的,执行post子请求时调用的
ngx_http_run_posted_requests方法就是通过遍历该单链表来执行子请求的 */
//ngx_http_post_request中创建ngx_http_posted_request_t空间
//ngx_http_post_request将该子请求挂载在主请求的posted_requests链表队尾,在ngx_http_run_posted_requests中执行
ngx_http_posted_request_t *posted_requests; //通过posted_requests就把各个子请求以单向链表的数据结构形式组织起来
/*
全局的ngx_http_phase_engine_t结构体中定义了一个ngx_http_phase_handler_t回调方法组成的数组,而phase_handler成员则与该数组配合使用,
表示请求下次应当执行以phase_handler作为序号指定的数组中的回调方法。HTTP框架正是以这种方式把各个HTTP摸块集成起来处理请求的
*///phase_handler实际上是该阶段的处理方法函数在ngx_http_phase_engine_t->handlers数组中的位置
ngx_int_t phase_handler;
//表示NGX HTTP CONTENT PHASE阶段提供给HTTP模块处理请求的一种方式,content handler指向HTTP模块实现的请求处理方法,在ngx_http_core_content_phase中执行
//ngx_http_proxy_handler ngx_http_redis2_handler ngx_http_fastcgi_handler等
ngx_http_handler_pt content_handler; ////在ngx_http_update_location_config中赋值给r->content_handler = clcf->handler;
/*
在NGX_HTTP_ACCESS_PHASE阶段需要判断请求是否具有访问权限时,通过access_code来传递HTTP模块的handler回调方法的返回值,如果access_code为0,
则表示请求具备访问权限,反之则说明请求不具备访问权限
NGXHTTPPREACCESSPHASE、NGX\_HTTP\_ACCESS\_PHASE、NGX HTTPPOST\_ACCESS\_PHASE,很好理解,做访问权限检查的前期、中期、后期工作,
其中后期工作是固定的,判断前面访问权限检查的结果(状态码存故在字段r->access_code内),如果当前请求没有访问权限,那么直接返回状
态403错误,所以这个阶段也无法去挂载额外的回调函数。
*/
ngx_uint_t access_code; //赋值见ngx_http_core_access_phase
/*
ngx_http_core_main_conf_t->variables数组成员的结构式ngx_http_variable_s, ngx_http_request_s->variables数组成员结构是ngx_variable_value_t,
这两个结构的关系很密切,一个所谓变量,一个所谓变量值
r->variables这个变量和cmcf->variables是一一对应的,形成var\_ name与var\_value对,所以两个数组里的同一个下标位置元素刚好就是
相互对应的变量名和变量值,而我们在使用某个变量时总会先通过函数ngx_http_get_variable_index获得它在变量名数组里的index下标,也就是变
量名里的index字段值,然后利用这个index下标进而去变量值数组里取对应的值
*/ //分配的节点数见ngx_http_create_request,和ngx_http_core_main_conf_t->variables一一对应
//变量ngx_http_script_var_code_t->index表示Nginx变量$file在ngx_http_core_main_conf_t->variables数组内的下标,对应每个请求的变量值存储空间就为r->variables[code->index],参考ngx_http_script_set_var_code
ngx_http_variable_value_t *variables; //注意和ngx_http_core_main_conf_t->variables的区别
#if (NGX_PCRE)
/*
例如正则表达式语句re.name= ^(/download/.*)/media/(.*)/tt/(.*)$, s=/download/aa/media/bdb/tt/ad,则他们会匹配,同时匹配的
变量数有3个,则返回值为3+1=4,如果不匹配则返回-1
这里\*2是因为获取前面例子中的3个变量对应的值需要成对使用r->captures,参考ngx\_http\_script\_copy\_capture\_code等
\*/
ngx\_uint\_t ncaptures; //赋值见ngx\_http\_regex\_exec //最大的$n\*2
int \*captures; //每个不同的正则解析之后的结果,存放在这里。$1,$2等
u\_char \*captures\_data; //进行正则表达式匹配的原字符串,例如http://10.135.2.1/download/aaa/media/bbb.com中的/download/aaa/media/bbb.com
#endif
/* limit_rate成员表示发送响应的最大速率,当它大于0时,表示需要限速。limit rate表示每秒可以发送的字节数,超过这个数字就需要限速;
然而,限速这个动作必须是在发送了limit_rate_after字节的响应后才能生效(对于小响应包的优化设计) */
//实际最后通过ngx_writev_chain发送数据的时候,还会限制一次
size_t limit_rate; //限速的相关计算方法参考ngx_http_write_filter
size_t limit_rate_after;
/\* used to learn the Apache compatible response length without a header \*/
size\_t header\_size; //所有头部行内容之和,可以参考ngx\_http\_header\_filter
off\_t request\_length; //HTTP请求的全部长度,包括HTTP包体
ngx\_uint\_t err\_status; //错误码,取值为NGX\_HTTP\_BAD\_REQUEST等
//当连接建立成功后,当收到客户端的第一个请求的时候会通过ngx\_http\_wait\_request\_handler->ngx\_http\_create\_request创建ngx\_http\_request\_t
//同时把r->http\_connection指向accept客户端连接成功时候创建的ngx\_http\_connection\_t,这里面有存储server{}上下文ctx和server\_name等信息
//该ngx\_http\_request\_t会一直有效,除非关闭连接。因此该函数只会调用一次,也就是第一个客户端请求报文过来的时候创建,一直持续到连接关闭
//该结构存储了服务器端接收客户端连接时,服务器端所在的server{\]上下文ctx server\_name等配置信息
ngx\_http\_connection\_t \*http\_connection; //存储ngx\_connection\_t->data指向的ngx\_http\_connection\_t,见ngx\_http\_create\_request
#if (NGX_HTTP_SPDY)
ngx_http_spdy_stream_t *spdy_stream;
#endif
#if (NGX\_HTTP\_V2)
/\* 赋值见ngx\_http\_v2\_create\_stream \*/
ngx\_http\_v2\_stream\_t \*stream;
#endif
ngx\_http\_log\_handler\_pt log\_handler;
//在这个请求中如果打开了某些资源,并需要在请求结束时释放,那么都需要在把定义的释放资源方法添加到cleanup成员中
/\*
如果没有需要清理的资源,则cleanup为空指针,否则HTTP模块可以向cleanup中以单链表的形式无限制地添加ngx\_http\_cleanup\_t结构体,
用以在请求结束时释放资源 \*/
ngx\_http\_cleanup\_t \*cleanup;
//默认值r->subrequests = NGX\_HTTP\_MAX\_SUBREQUESTS + 1;见ngx\_http\_create\_request
unsigned subrequests:8; //该r最多还可以处理多少个子请求
/*
在阅读HTTP反向代理模块(ngx_http_proxy_module)源代码时,会发现它并没有调用r->main->count++,其中proxy模块是这样启动upstream机制的:
ngx_http_read_client_request_body(r,ngx_http_upstream_init);,这表示读取完用户请求的HTTP包体后才会调用ngx_http_upstream_init方法
启动upstream机制。由于ngx_http_read_client_request_body的第一行有效语句是r->maln->count++,所以HTTP反向代理模块不能
再次在其代码中执行r->main->count++。
这个过程看起来似乎让人困惑。为什么有时需要把引用计数加1,有时却不需要呢?因为ngx_http_read- client_request_body读取请求包体是
一个异步操作(需要epoll多次调度方能完成的可称其为异步操作),ngx_http_upstream_init方法启用upstream机制也是一个异步操作,因此,
从理论上来说,每执行一次异步操作应该把引用计数加1,而异步操作结束时应该调用ngx_http_finalize_request方法把引用计数减1。另外,
ngx_http_read_client_request_body方法内是加过引用计数的,而ngx_http_upstream_init方法内却没有加过引用计数(或许Nginx将来会修改
这个问题)。在HTTP反向代理模块中,它的ngx_http_proxy_handler方法中用“ngx_http_read- client_request_body(r,ngx_http_upstream_init);”
语句同时启动了两个异步操作,注意,这行语句中只加了一次引用计数。执行这行语句的ngx_http_proxy_handler方法返回时只调用
ngx_http_finalize_request方法一次,这是正确的。对于mytest模块也一样,务必要保证对引用计数的增加和减少是配对进行的。
*/
/*
表示当前请求的引用次数。例如,在使用subrequest功能时,依附在这个请求上的子请求数目会返回到count上,每增加一个子请求,count数就要加1。
其中任何一个子请求派生出新的子请求时,对应的原始请求(main指针指向的请求)的count值都要加1。又如,当我们接收HTTP包体时,由于这也是
一个异步调用,所以count上也需要加1,这样在结束请求时,就不会在count引用计数未清零时销毁请求
*/
unsigned count:8; //应用计数 ngx_http_close_request中-1
/* 如果AIO上下文中还在处理这个请求,blocked必然是大于0的,这时ngx_http_close_request方法不能结束请求
ngx_http_copy_aio_handler会自增,当内核把数据发送出去后会在ngx_http_copy_aio_event_handler自剪
*/
unsigned blocked:8; //阻塞标志位,目前仅由aio使用 为0,表示没有HTTP模块还需要处理请求
//ngx_http_copy_aio_handler handler ngx_http_copy_aio_event_handler执行后,会置回到0
//ngx_http_copy_thread_handler ngx_http_copy_thread_event_handler置0
//ngx_http_cache_thread_handler置1, ngx_http_cache_thread_event_handler置0
//ngx_http_file_cache_aio_read中置1,
unsigned aio:1; //标志位,为1时表示当前请求正在使用异步文件IO
unsigned http\_state:4; //赋值见ngx\_http\_state\_e中的成员
/\* URI with "/." and on Win32 with "//" \*/
unsigned complex\_uri:1;
/\* URI with "%" \*/
unsigned quoted\_uri:1;
/\* URI with "+" \*/
unsigned plus\_in\_uri:1;
/\* URI with " " \*/
unsigned space\_in\_uri:1; //uri中是否带有空格
//头部帧内容部分header合法性检查,见ngx\_http\_v2\_validate\_header
unsigned invalid\_header:1; //头部行解析不正确,见ngx\_http\_parse\_header\_line
unsigned add\_uri\_to\_alias:1;
unsigned valid\_location:1; //ngx\_http\_handler中置1
//如果有rewrite 内部重定向 uri带有args等会直接置0,否则如果uri中有空格会置1
unsigned valid\_unparsed\_uri:1;//r->valid\_unparsed\_uri = r->space\_in\_uri ? 0 : 1;
/\*
将uri\_changed设置为0后,也就标志说URL没有变化,那么,在ngx\_http\_core\_post\_rewrite\_phase中就不会执行里面的if语句,也就不会
再次走到find config的过程了,而是继续处理后面的。不然正常情况,rewrite成功后是会重新来一次的,相当于一个全新的请求。
\*/ // 例如rewrite ^.\*$ www.galaxywind.com last;就会多次执行rewrite ngx\_http\_script\_regex\_start\_code中置1
unsigned uri\_changed:1; //标志位,为1时表示URL发生过rewrite重写 只要不是rewrite xxx bbb sss;aaa不是break结束都会置1
//表示使用rewrite重写URL的次数。因为目前最多可以更改10次,所以uri\_changes初始化为11,而每重写URL -次就把uri\_changes减1,
//一旦uri\_changes等于0,则向用户返回失败
unsigned uri\_changes:4; //NGX\_HTTP\_MAX\_URI\_CHANGES + 1;
unsigned request\_body\_in\_single\_buf:1;//client\_body\_in\_single\_buffer on | off;设置
//置1包体需要存入临时文件中 如果request\_body\_no\_buffering为1表示不用缓存包体,那么request\_body\_in\_file\_only也为0,因为不用缓存包体,那么就不用写到临时文件中
/\*注意:如果每次开辟的client\_body\_buffer\_size空间都存储满了还没有读取到完整的包体,则还是会把之前读满了的buf中的内容拷贝到临时文件,参考
ngx\_http\_do\_read\_client\_request\_body -> ngx\_http\_request\_body\_filter和ngx\_http\_read\_client\_request\_body -> ngx\_http\_request\_body\_filter
\*/
unsigned request\_body\_in\_file\_only:1; //"client\_body\_in\_file\_only on |clean"设置 和request\_body\_no\_buffering是互斥的
unsigned request\_body\_in\_persistent\_file:1; //"client\_body\_in\_file\_only on"设置
unsigned request\_body\_in\_clean\_file:1;//"client\_body\_in\_file\_only clean"设置
unsigned request\_body\_file\_group\_access:1; //是否有组权限,如果有一般为0600
unsigned request\_body\_file\_log\_level:3;
//默认是为0的表示需要缓存客户端包体,决定是否需要转发客户端包体到后端,如果request\_body\_no\_buffering为1表示不用缓存包体,那么request\_body\_in\_file\_only也为0,因为不用缓存包体,那么就不用写到临时文件中
unsigned request\_body\_no\_buffering:1; //是否缓存HTTP包体,如果不缓存包体,和request\_body\_in\_file\_only是互斥的,见ngx\_http\_read\_client\_request\_body
/\*
upstream有3种处理上游响应包体的方式,但HTTP模块如何告诉upstream使用哪一种方式处理上游的响应包体呢?
当请求的ngx\_http\_request\_t结构体中subrequest\_in\_memory标志位为1时,将采用第1种方式,即upstream不转发响应包体
到下游,由HTTP模块实现的input\_filter方法处理包体;当subrequest\_in\_memory为0时,upstream会转发响应包体。当ngx\_http\_upstream\_conf\_t
配置结构体中的buffering标志位为1时,将开启更多的内存和磁盘文件用于缓存上游的响应包体,这意味上游网速更快;当buffering
为0时,将使用固定大小的缓冲区(就是上面介绍的buffer缓冲区)来转发响应包体。
\*/
unsigned subrequest\_in\_memory:1; //ngx\_http\_subrequest中赋值 NGX\_HTTP\_SUBREQUEST\_IN\_MEMORY
unsigned waited:1; //ngx\_http\_subrequest中赋值 NGX\_HTTP\_SUBREQUEST\_WAITED
#if (NGX_HTTP_CACHE)
unsigned cached:1;//如果客户端请求过来有读到缓存文件,则置1,见ngx_http_file_cache_read ngx_http_upstream_cache_send
#endif
#if (NGX_HTTP_GZIP)
unsigned gzip_tested:1;
unsigned gzip_ok:1;
unsigned gzip_vary:1;
#endif
unsigned proxy:1;
unsigned bypass\_cache:1;
unsigned no\_cache:1;
/\*
\* instead of using the request context data in
\* ngx\_http\_limit\_conn\_module and ngx\_http\_limit\_req\_module
\* we use the single bits in the request structure
\*/
unsigned limit\_conn\_set:1;
unsigned limit\_req\_set:1;
#if 0
unsigned cacheable:1;
#endif
unsigned pipeline:1;
//如果后端发送过来的头部行中不带有Content-length:xxx 这种情况1.1版本HTTP直接设置chunked为1, 见ngx\_http\_chunked\_header\_filter
//如果后端带有Transfer-Encoding: chunked会置1
unsigned chunked:1; //chunk编码方式组包实际组包过程参考ngx\_http\_chunked\_body\_filter
//当下游的r->method == NGX\_HTTP\_HEAD请求方法只请求头部行,则会在ngx\_http\_header\_filter中置1
//HTTP2头部帧发送在ngx\_http\_v2\_header\_filter中置1
unsigned header\_only:1; //表示是否只有行、头部,没有包体 ngx\_http\_header\_filter中置1
//在1.0以上版本默认是长连接,1.0以上版本默认置1,如果在请求头里面没有设置连接方式,见ngx\_http\_handler
//标志位,为1时表示当前请求是keepalive请求 1长连接 0短连接 长连接时间通过请求头部的Keep-Alive:设置,参考ngx\_http\_headers\_in\_t
unsigned keepalive:1; //赋值见ngx\_http\_handler
//延迟关闭标志位,为1时表示需要延迟关闭。例如,在接收完HTTP头部时如果发现包体存在,该标志位会设为1,而放弃接收包体时则会设为o
unsigned lingering_close:1;
//如果discard_body为1,则证明曾经执行过丢弃包体的方法,现在包体正在被丢弃中,见ngx_http_read_client_request_body
unsigned discard_body:1;//标志住,为1时表示正在丢弃HTTP请求中的包体
unsigned reading_body:1; //标记包体还没有读完,需要继续读取包体,见ngx_http_read_client_request_body
/\* 在这一步骤中,把phase\_handler序号设为server\_rewrite\_index,这意味着无论之前执行到哪一个阶段,马上都要重新从NGX\_HTTP\_SERVER\_REWRITE\_PHASE
阶段开始再次执行,这是Nginx的请求可以反复rewrite重定向的基础。见ngx_http_handler */
//ngx_http_internal_redirect置1 创建子请求的时候,子请求也要置1,见ngx_http_subrequest,所有子请求需要做重定向
//内部重定向是从NGX_HTTP_SERVER_REWRITE_PHASE处继续执行(ngx_http_internal_redirect),而重新rewrite是从NGX_HTTP_FIND_CONFIG_PHASE处执行(ngx_http_core_post_rewrite_phase)
unsigned internal:1;//t标志位,为1时表示请求的当前状态是在做内部跳转,
unsigned error_page:1; //默认0,在ngx_http_special_response_handler中可能置1
unsigned filter_finalize:1;
unsigned post_action:1;//ngx_http_post_action中置1 默认为0,除非post_action XXX配置
unsigned request_complete:1;
unsigned request_output:1;//表示有数据需要往客户端发送,ngx_http_copy_filter中置1
//为I时表示发送给客户端的HTTP响应头部已经发送。在调用ngx_http_send_header方法后,若已经成功地启动响应头部发送流程,
//该标志位就会置为1,用来防止反复地发送头部
unsigned header_sent:1;
unsigned expect_tested:1;
unsigned root_tested:1;
unsigned done:1;
unsigned logged:1;
/* ngx_http_copy_filter中赋值 */
unsigned buffered:4;//表示缓冲中是否有待发送内容的标志位,参考ngx_http_copy_filter
unsigned main\_filter\_need\_in\_memory:1;
unsigned filter\_need\_in\_memory:1;
unsigned filter\_need\_temporary:1;
unsigned allow\_ranges:1; //支持断点续传 参考3.8.3节
unsigned single\_range:1;
//
unsigned disable\_not\_modified:1; //r->disable\_not\_modified = !u->cacheable;因此默认为0
#if (NGX_STAT_STUB)
unsigned stat_reading:1;
unsigned stat_writing:1;
#endif
/\* used to parse HTTP headers \*/ //状态机解析HTTP时使用state来表示当前的解析状态
ngx\_uint\_t state; //解析状态,见ngx\_http\_parse\_header\_line
//header\_hash为Accept-Language:zh-cn中Accept-Language所有字符串做hash运算的结果
ngx\_uint\_t header\_hash; //头部行中一行所有内容计算ngx\_hash的结构,参考ngx\_http\_parse\_header\_line
//lowcase\_index为Accept-Language:zh-cn中Accept-Language字符数,也就是15个字节
ngx\_uint\_t lowcase\_index; // 参考ngx\_http\_parse\_header\_line
//存储Accept-Language:zh-cn中的Accept-Language字符串到lowcase\_header。如果是AAA\_BBB:CCC,则该数组存储的是\_BBB
u\_char lowcase\_header\[NGX\_HTTP\_LC\_HEADER\_LEN\]; //http头部内容,不包括应答行或者请求行,参考ngx\_http\_parse\_header\_line
/*
例如:Accept:image/gif.image/jpeg,**
Accept对应于key,header_name_start header_name_end分别指向这个Accept字符串的头和尾
image/gif.image/jpeg,** 为value部分,header_start header_end分别对应value的头和尾,可以参考mytest_upstream_process_header
*/
//header_name_start指向Accept-Language:zh-cn中的A处
u_char *header_name_start; //解析到的一行http头部行中的一行的name开始处 //赋值见ngx_http_parse_header_line
//header_name_start指向Accept-Language:zh-cn中的:处
u_char *header_name_end; //解析到的一行http头部行中的一行的name的尾部 //赋值见ngx_http_parse_header_line
u_char *header_start;//header_start指向Accept-Language:zh-cn中的z字符处
u_char *header_end;//header_end指向Accept-Language:zh-cn中的末尾换行处
/\*
\* a memory that can be reused after parsing a request line
\* via ngx\_http\_ephemeral\_t
\*/
//ngx_str_t类型的uri成员指向用户请求中的URI。同理,u_char*类型的uri_start和uri_end也与request_start、request_end的用法相似,唯一不
//同的是,method_end指向方法名的最后一个字符,而uri_end指向URI结束后的下一个地址,也就是最后一个字符的下一个字符地址(HTTP框架的行为),
//这是大部分u_char*类型指针对“xxx_start”和“xxx_end”变量的用法。
u_char *uri_start;//HTTP2的赋值见ngx_http_v2_parse_path
u_char *uri_end;//HTTP2的赋值见ngx_http_v2_parse_path
/*
ngx_str_t类型的extern成员指向用户请求的文件扩展名。例如,在访问“GET /a.txt HTTP/1.1”时,extern的值是{len = 3, data = "txt"},
而在访问“GET /a HTTP/1.1”时,extern的值为空,也就是{len = 0, data = 0x0}。
uri_ext指针指向的地址与extern.data相同。
*/ //GET /sample.jsp HTTP/1.1 后面的文件如果有.字符,则指向该.后面的jsp字符串,表示文件扩展名
u_char *uri_ext;
//"GET /aaaaaaaa?bbbb.txt HTTP/1.1"中的bbb.txt字符串头位置处
u_char *args_start;//args_start指向URL参数的起始地址,配合uri_end使用也可以获得URL参数。
/\* 通过request\_start和request\_end可以获得用户完整的请求行 \*/
u\_char \*request\_start; //请求行开始处
u\_char \*request\_end; //请求行结尾处
u\_char \*method\_end; //GET POST字符串结尾处
//HTTP2的赋值见ngx\_http\_v2\_parse\_scheme
u\_char \*schema\_start;
u\_char \*schema\_end;
u\_char \*host\_start;
u\_char \*host\_end;
u\_char \*port\_start;
u\_char \*port\_end;
// HTTP/1.1前面的1代表major,后面的1代表minor
unsigned http\_minor:16;
unsigned http\_major:16;
};
//在连接建立并接受到客户端第一次请求的时候才会创建ngx_connection_t,该结构一直持续到连接关闭才释放
ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c)
{
ngx_pool_t *pool;
ngx_time_t *tp;
ngx_http_request_t *r;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_main_conf_t *cmcf;
c->requests++;
hc = c->data;
//在ngx\_http\_wait\_request\_handler的时候,data指向ngx\_http\_connection\_t,该函数返回后 c->data重新指向新开盘的ngx\_http\_request\_t
//之前c->data指向的ngx\_http\_connection\_t用r->http\_connection保存
cscf = ngx\_http\_get\_module\_srv\_conf(hc->conf\_ctx, ngx\_http\_core\_module);
pool = ngx\_create\_pool(cscf->request\_pool\_size, c->log);
if (pool == NULL) {
return NULL;
}
r = ngx\_pcalloc(pool, sizeof(ngx\_http\_request\_t));
if (r == NULL) {
ngx\_destroy\_pool(pool);
return NULL;
}
r->pool = pool;
//当连接建立成功后,当收到客户端的第一个请求的时候会通过ngx\_http\_wait\_request\_handler->ngx\_http\_create\_request创建ngx\_http\_request\_t
//同时把r->http\_connection指向accept客户端连接成功时候创建的ngx\_http\_connection\_t,这里面有存储server{}上下文ctx和server\_name等信息
//该ngx\_http\_request\_t会一直有效,除非关闭连接。因此该函数只会调用一次,也就是第一个客户端请求报文过来的时候创建,一直持续到连接关闭
r->http\_connection = hc; //重新把c->data赋值给r->http\_connection,这样r->http\_connection就保存了ngx\_http\_connection\_t信息
r->signature = NGX\_HTTP\_MODULE;
r->connection = c;
r->main\_conf = hc->conf\_ctx->main\_conf;
r->srv\_conf = hc->conf\_ctx->srv\_conf;
r->loc\_conf = hc->conf\_ctx->loc\_conf;
r->read\_event\_handler = ngx\_http\_block\_reading;
clcf = ngx\_http\_get\_module\_loc\_conf(r, ngx\_http\_core\_module);
ngx\_set\_connection\_log(r->connection, clcf->error\_log);
r->header\_in = hc->nbusy ? hc->busy\[0\] : c->buffer;
if (ngx\_list\_init(&r->headers\_out.headers, r->pool, 20,
sizeof(ngx\_table\_elt\_t))
!= NGX\_OK)
{
ngx\_destroy\_pool(r->pool);
return NULL;
}
r->ctx = ngx\_pcalloc(r->pool, sizeof(void \*) \* ngx\_http\_max\_module);
if (r->ctx == NULL) {
ngx\_destroy\_pool(r->pool);
return NULL;
}
cmcf = ngx\_http\_get\_module\_main\_conf(r, ngx\_http\_core\_module);
r->variables = ngx\_pcalloc(r->pool, cmcf->variables.nelts
\* sizeof(ngx\_http\_variable\_value\_t));
if (r->variables == NULL) {
ngx\_destroy\_pool(r->pool);
return NULL;
}
#if (NGX_HTTP_SSL)
if (c->ssl) {
r->main_filter_need_in_memory = 1;
}
#endif
r->main = r;
r->count = 1;
tp = ngx\_timeofday();
//ngx\_http\_request\_t结构体中有两个成员表示这个请求的开始处理时间:start\_sec成员和start\_msec成员
r->start\_sec = tp->sec;
r->start\_msec = tp->msec;
r->method = NGX\_HTTP\_UNKNOWN;
r->http\_version = NGX\_HTTP\_VERSION\_10;
r->headers\_in.content\_length\_n = -1;
r->headers\_in.keep\_alive\_n = -1;
r->headers\_out.content\_length\_n = -1;
r->headers\_out.last\_modified\_time = -1;
r->uri\_changes = NGX\_HTTP\_MAX\_URI\_CHANGES + 1;
r->subrequests = NGX\_HTTP\_MAX\_SUBREQUESTS + 1;
r->http\_state = NGX\_HTTP\_READING\_REQUEST\_STATE;
ctx = c->log->data;
ctx->request = r;
ctx->current\_request = r;
r->log\_handler = ngx\_http\_log\_error\_handler;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
r->stat_reading = 1;
(void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
#endif
return r;
}
ssize_t
ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
ssize_t n;
ngx_err_t err;
ngx_event_t *rev;
int ready = 0;
rev = c->read;
do {
/\*
针对非阻塞I/O执行的系统调用则总是立即返回,而不管事件足否已经发生。如果事件没有眭即发生,这些系统调用就
返回—1.和出错的情况一样。此时我们必须根据errno来区分这两种情况。对accept、send和recv而言,事件未发牛时errno
通常被设置成EAGAIN(意为“再来一次”)或者EWOULDBLOCK(意为“期待阻塞”):对conncct而言,errno则被
设置成EINPROGRESS(意为“在处理中")。
\*/
//n = recv(c->fd, buf, size, 0); yang test
//These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer has performed an orderly shutdown.
n = recv(c->fd, buf, size, 0);//表示TCP错误,见ngx\_http\_read\_request\_header recv返回0表示对方已经关闭连接
//读取成功,直接返回
//recv返回0,本端不应该去关闭连接,如果是因为对端使用了shutdown来关闭半连接,本端还是可以发送数据的,知识不能读数据,所以这里置ready=0
//如果不是对端shutdown,那么说明是因为读缓冲区数据读完了,没数据了,读不到数据,所以返回0。返回0不能本端不能关闭套接字
//recv返回0,表示对端使用shutdown来实现半关闭或者异步读写的情况下,缓冲区没有数据可读,也会返回0。send返回0当作正常情况处理
if (n == 0) { //表示TCP错误,见ngx\_http\_read\_request\_header recv返回0表示对方已经关闭连接 The return value will be 0 when the peer has performed an orderly shutdown.
rev->ready = 0;//数据读取完毕ready置0
rev->eof = 1;
goto end;
} else if (n > 0) {
//期待发送800字节,实际上返回100字节,说明内核缓冲区接收到这100字节后已经满了,不能在写, read为0,只有等epoll写事件触发 read
//但是,接收如果期待接收800字节,返回400字节则说明我内核缓冲区中只有400字节,因此可以继续recv,ready还是为1
if ((size\_t) n < size
&& !(ngx\_event\_flags & NGX\_USE\_GREEDY\_EVENT)) //数据读取完毕ready置0,需要重新添加add epoll event
{
rev->ready = 0; //说明内核缓冲区数据已经读取完毕
}
goto end;
}
//如果内核数据接收完毕,则走到这里n为-1,err为NGX\_EAGAIN
err = ngx\_socket\_errno;
/\*
EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。
在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这表明你在非阻塞模式下调用了阻塞操作,
在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。对非阻塞socket而言,EAGAIN不是一种错误。
在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。 另外,如果出现EINTR即errno为4,错误描述Interrupted system call,操作也应该继续。
在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。
从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。
例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,
read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。
*/
if (err == NGX_EAGAIN || err == NGX_EINTR) { //这两种情况 ,需要继续读
ngx\_log\_debug0(NGX\_LOG\_DEBUG\_EVENT, c->log, err,
"recv() not ready"); //recv() not ready (11: Resource temporarily unavailable)
n = NGX\_AGAIN; //返回这个表示内核数据已有的数据已经读取完,需要重新add epoll event来触发新数据epoll返回
} else {//TCP连接出错了
n = ngx\_connection\_error(c, err, "recv() failed");
break;
}
} while (err == NGX\_EINTR); //如果读过程中被中断切换,则继续读
rev->ready = 0;
if (n == NGX\_ERROR) {
rev->error = 1;
}
end:
ready = rev->ready;
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
"recv: fd:%d read-size:%d of %d, ready:%d", c->fd, n, size, ready);
return n;
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章