概述
上一篇介绍了HTTP会话建立流程,本篇介绍接收HTTP Header流程。由于Nginx是完全异步的,这对编写HTTP框架提出比较高的要求,因此Nginx在实现HTTP框架时定义出11个阶段。后续章节会详细介绍该11阶段。本篇介绍的接收HTTP Header请求在HTTP框架中是逻辑比较简单。
HTTP协议本身虽然比较简单,但是对于解析HTTP协议并不是很容易。体现之处就是HTTP协议header以及body都不是定长的。例如:上传文件可能是几个G大小,那么针对内存有限的前提下,如何做到高效呢?HTTP Header也是一样。接下来看一下Nginx是如何处理这种场景的,这是值得我们学习的。
一、HTTP报文处理简图
Nginx处理HTTP请求,大致分为以下几个流程(比较粗),我们做到心中有数即可,后续文章也是按照该图进行介绍。
二、第一次读取HTTP请求
在上一篇提到,当客户端HTTP请求事件发生后,处理该READ事件的回调函数是ngx_http_wait_request_handler。
2.1、流程图
2.2、源码
/**
* 处理请求
* @param rev 事件
* 进入此函数,要么是http请求未到定时器超时了,要么是有http请求到来
*/
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");
/* http请求未到 定时器超时 */
if (rev->timedout)
{
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;
}
这段代码是处理定时器超时和连接异常关闭事件。当我们创建HTTP连接后,客户端始终没有发送HTTP请求到服务端,则会引起超时事件。那么Nginx就会关闭当前连接connection。
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;
/* 为接收http header申请内存*/
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;
}
/* 接收http报文 实际指向ngx_unix_recv */
n = c->recv(c, b->last, size);
if (n == NGX_AGAIN)
{//需要重新设置定时器事件以及注册READ事件
if (!rev->timer_set)
{
ngx_add_timer(rev, c->listening->post_accept_timeout);
ngx_reusable_connection(c, 1);
}
if (ngx_handle_read_event(rev, 0) != 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;
}
这段代码主要做了两件事儿:
1、根据配置,申请buffer,接收客户端发送的HTTP请求。
2、调用recv接口,接收客户端数据,根据返回值进行异常处理。
这里需要特别说明一下,由于HTTP Header是不定长,Nginx无法确定其长度(所有的web server都不能确定),这里默认先申请1k空间(最大8k)。当然这个配置可以通过nginx.conf进行修改。
/* 表示接收到的字节数大于0 */
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_request对象 */
c->data = ngx_http_create_request(c);
if (c->data == NULL)
{
ngx_http_close_connection(c);
return;
}
/* 设置http request-line handler函数 */
rev->handler = ngx_http_process_request_line;
ngx_http_process_request_line(rev);
}
创建request对象,并且设置读事件回调函数为ngx_http_process_request_line。最后进入ngx_http_process_request_line函数进行处理。这里为什么要把回调函数设置ngx_http_process_request_line,然后又进入ngx_http_process_request_line函数呢?原因只有一个:HTTP是不定长的。具体分析如下:
1、虽然在ngx_http_wait_request_handler函数中调用recv函数用于接收客户端请求,但是我们无法保证,此次recv操作一定能够接收到完整的request line。所以将READ事件回调函数设置为ngx_http_process_request_line。
2、但是通常情况下,request line并不是很长,所以一般场景下能够接收到,所以函数末尾进入ngx_http_process_request_line函数。在该函数ngx_http_process_request_line中就能确定是否已经接受完整request line。
三、接收Request Line
3.1、流程图
首先看一下,Request Line处理函数的流程图:
3.2、代码
/**
* 处理请求行
* @param rev 事件对象
* 可能会多次进入此函数才能接收到完整的请求行
*/
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;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"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;
处理超时事件,直接关闭请求ngx_http_close_request。
rc = NGX_AGAIN;
for (;;)
{
if (rc == NGX_AGAIN)
{
n = ngx_http_read_request_header(r);//结构http request line 调用此函数
if (n == NGX_AGAIN || n == NGX_ERROR)
{
return;
}
}
/* 解析http 请求行 */
rc = ngx_http_parse_request_line(r, r->header_in);
尝试从网络中接收http报文,当成功接收报文进行HTTP请求行的解析ngx_http_parse_request_line。该函数就是纯粹的解析报文,内容比较枯燥此处不展开说明。
3.2.1、返回NGX_OK
if (rc == NGX_OK)
{
/**
* the request line has been parsed successfully
* 解析http request line成功 设置相关参数
*/
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);
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;
}
/* 解析uri */
if (ngx_http_process_request_uri(r) != NGX_OK)
{
return;
}
if (r->host_start && r->host_end)
{//处理host
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)
{//处理http版本小于1.0
if (r->headers_in.server.len == 0 && ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR)
{
return;
}
ngx_http_process_request(r);
return;
}
/* 为request header申请内存 */
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";
/* 处理request header */
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
此处需要知道,事件处理函数变成ngx_http_process_request_headers,这个是重点内容。其他内容的设置比较简单明了。
3.2.2、解析失败
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;
}
解析失败直接调用ngx_http_finalize_request函数
3.2.3、返回NGX_AGAIN
/* NGX_AGAIN: a request line parsing is still incomplete */
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;
}
}
这里需要指明一点,由于HTTP报文是不定长的,所以申请的缓冲(1k)可能无法满足客户端发送过来的报文,因此这里进行缓冲区扩展--ngx_http_alloc_large_header_buffer。
四、ngx_http_close_request和ngx_http_finalize_request区别
本篇只介绍接收并且处理HTTP请求行处理流程,下一篇介绍Nginx处理HTTP Header部分。
最后
以上就是孤独衬衫为你收集整理的菜鸟学习nginx之接收HTTP请求行的全部内容,希望文章能够帮你解决菜鸟学习nginx之接收HTTP请求行所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复