详解nginx请求头数据读取流程
本文主要详细介绍了nginx请求头数据的读取过程,通过示例代码非常详细,对大家的学习或者工作都有一定的参考价值。有需要的朋友下面和边肖一起学习。
在上一篇文章中,我们解释了nginx如何读取请求行的数据并解析请求行。本文主要解释nginx如何读取和解析客户端发送的请求头的数据。本质上,请求行和请求头的数据读取过程基本相同,因为两者都面临着如何从间歇数据流中读取数据,以及如何处理数据的问题。
1。读取主进程的请求头
在介绍请求头的读取过程之前,先来展示一个http请求消息的例子:
POST/web/book/readHTTP/1.1
Host:localhost
Connection:keep-alive
Content-Length:365
Accept:application/json,text/plain,*/*
示例中的第一行数据是请求行,后面几行是请求头。每个请求头以name:value的格式组装,每个请求头占一行。在上一篇介绍读取请求行过程的文章中,我们说过一旦读取了请求行,nginx会将当前读取事件的回调函数修改为ngx_http_process_request_headers()方法,直接调用这个方法尝试读取请求头数据。这个方法是读取请求行数据的主要过程。以下是该方法的源代码:
/**
*解析客户端发送来的header数据
*/
staticvoidngx_http_process_request_headers(ngx_event_t*rev){
u_char*p;
size_tlen;
ssize_tn;
ngx_int_trc,rv;
ngx_table_elt_t*h;
ngx_connection_t*c;
ngx_http_header_t*hh;
ngx_http_request_t*r;
ngx_http_core_srv_conf_t*cscf;
ngx_http_core_main_conf_t*cmcf;
c=rev->data;
r=c->data;
if(rev->timedout){
ngx_log_error(NGX_LOG_INFO,c->log,NGX_ETIMEDOUT,"clienttimedout");
c->timedout=1;
ngx_http_close_request(r,NGX_HTTP_REQUEST_TIME_OUT);
return;
}
cmcf=ngx_http_get_module_main_conf(r,ngx_http_core_module);
rc=NGX_AGAIN;
for(;;){
if(rc==NGX_AGAIN){
//如果当前header缓冲区中没有剩余空间,则申请新的空间
if(r->header_in->pos==r->header_in->end){
//申请新的空间
rv=ngx_http_alloc_large_header_buffer(r,0);
if(rv==NGX_ERROR){
ngx_http_close_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
//客户端发送的header太长,超出了large_client_header_buffers指定的最大大小
if(rv==NGX_DECLINED){
p=r->header_name_start;
r->lingering_close=1;
if(p==NULL){
ngx_log_error(NGX_LOG_INFO,c->log,0,"clientsenttoolargerequest");
ngx_http_finalize_request(r,NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return;
}
len=r->header_in->end-p;
if(len>NGX_MAX_ERROR_STR-300){
len=NGX_MAX_ERROR_STR-300;
}
ngx_http_finalize_request(r,NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return;
}
}
//尝试读取连接上客户端新发送来的数据
n=ngx_http_read_request_header(r);
if(n==NGX_AGAIN||n==NGX_ERROR){
return;
}
}
cscf=ngx_http_get_module_srv_conf(r,ngx_http_core_module);
//这里主要是对读取到的数据进行转换
rc=ngx_http_parse_header_line(r,r->header_in,cscf->underscores_in_headers);
//NGX_OK表示成功解析得到了一个header数据
if(rc==NGX_OK){
r->request_length+=r->header_in->pos-r->header_name_start;
//过滤无效的header
if(r->invalid_header&&cscf->ignore_invalid_headers){
continue;
}
//创建一个存储header的结构体
h=ngx_list_push(&r->headers_in.headers);
if(h==NULL){
ngx_http_close_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
h->hash=r->header_hash;
//把header的name作为hash表的key
h->key.len=r->header_name_end-r->header_name_start;
h->key.data=r->header_name_start;
h->key.data[h->key.len]='\0';
//把header的value作为hash表的value
h->value.len=r->header_end-r->header_start;
h->value.data=r->header_start;
h->value.data[h->value.len]='\0';
h->lowcase_key=ngx_pnalloc(r->pool,h->key.len);
if(h->lowcase_key==NULL){
ngx_http_close_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if(h->key.len==r->lowcase_index){
ngx_memcpy(h->lowcase_key,r->lowcase_header,h->key.len);
}else{
ngx_strlow(h->lowcase_key,h->key.data,h->key.len);
}
//headers_in_hash中存储了所有的header,这里是查找当前客户端传的header是否为有效的header
hh=ngx_hash_find(&cmcf->headers_in_hash,h->hash,h->lowcase_key,h->key.len);
//这里的handler是在ngx_http_headers_in中为每一个header定义的处理方法,经过各个header的
//handler()方法处理后,客户端传来的header就都转换到r->headers_in结构体中的各个属性中了
if(hh&&hh->handler(r,h,hh->offset)!=NGX_OK){
return;
}
continue;
}
//NGX_HTTP_PARSE_HEADER_DONE表示已经将所有的header都处理完成了
if(rc==NGX_HTTP_PARSE_HEADER_DONE){
r->request_length+=r->header_in->pos-r->header_name_start;
r->http_state=NGX_HTTP_PROCESS_REQUEST_STATE;
//检查客户端发送来的header数据的合法性
rc=ngx_http_process_request_header(r);
if(rc!=NGX_OK){
return;
}
ngx_http_process_request(r);
return;
}
//NGX_AGAIN表示读取到的header行数据不完全,还需要继续读取
if(rc==NGX_AGAIN){
continue;
}
ngx_log_error(NGX_LOG_INFO,c->log,0,"clientsentinvalidheaderline");
ngx_http_finalize_request(r,NGX_HTTP_BAD_REQUEST);
return;
}
}
这里,请求报头的读取主要分为以下步骤:
首先,检查当前读取事件是否超时,如果超时,直接关闭当前连接;
判断r->:header_in->;pos==r->;header_in->;End,这主要是检查当前读缓冲区中是否有内存空空间可以存放新读取的数据。如果没有,从内存池申请新的内存空房间;
调用ngx_http_read_request_header()方法读取当前连接句柄上的数据。如果其返回值大于0,则表示读取数据的长度。如果等于0,则意味着客户端已断开连接。如果是NGX_ERROR,说明读数异常。如果又是NGX_的话,这次没有读取数据,需要继续读取新的数据。可以看出,这里首先判断返回值是否为NGX_AGAIN,如果是,则直接返回,不做任何其他处理。这主要是因为当前read事件的回调函数仍然是NGX_http_process_request_headers(),当触发新的read事件时,仍然会调用ngx_http_read_request_header()再次读取数据。另一方面,在ngx_http_read_request_header()方法中,如果发现返回值为NGX_AGAIN,则再次将当前的read事件添加到事件队列中,并将read事件注册到当前连接的epoll句柄上;
调用ngx_http_parse_header_line()方法来解析读请求头数据。应该注意的是,每次调用这个方法时,只会解析一个请求头。但是,所有的请求头数据最终都要通过无限for循环和不停的事件触发机制来读取。
根据ngx_http_parse_header_line()方法的返回值,如果是NGX_OK,新读取的头将存储在r->:Headers_in.headers链表中;
如果ngx_http_parse_header_line()方法的返回值是NGX_HTTP_PARSE_HEADER_DONE,则说明已经成功读取了所有的头。此时将调用NGX_http_process_request_header()方法检查读取头的合法性。然后调用ngx_http_process_request()方法启动nginx中http模块的11个阶段。这种方法的实现原理将在下面的文章中解释。
2。请求读取标题数据
如您所见,读取请求头有两种主要方法:ngx_http_read_request_header()和ngx_http_parse_header_line()。这里的第二个方法很长,但是它的逻辑非常简单。主要分析读取的数据能否形成完整的请求头(以name:value的形式,占一行)。如果是,它返回NGX_OK,否则,它再次返回NGX_以继续读取数据。对于这种方法,这里就不解释了。读者可以自行阅读源代码。我们主要说明ngx_http_read_request_header()方法如何读取客户端发送的请求头数据:
staticssize_tngx_http_read_request_header(ngx_http_request_t*r){
ssize_tn;
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){
returnn;
}
//走到这里,说明当前读取到的数据都已经处理完了,因而这里会进行判断,如果当前事件的ready参数为1,
//则表示当前连接的句柄上存储还未读取的数据,因而调用c->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);
returnNGX_ERROR;
}
returnNGX_AGAIN;
}
//如果n为0,说明客户端关闭了连接
if(n==0){
ngx_log_error(NGX_LOG_INFO,c->log,0,"clientprematurelyclosedconnection");
}
//如果客户端关闭了连接或者读取异常,则回收当前的request结构体
if(n==0||n==NGX_ERROR){
c->error=1;
c->log->action="readingclientrequestheaders";
ngx_http_finalize_request(r,NGX_HTTP_BAD_REQUEST);
returnNGX_ERROR;
}
//更新当前读取到的数据指针
r->header_in->last+=n;
returnn;
}
这里,请求标题数据的读取主要分为以下步骤:
判断当前缓冲区是否有未处理的数据,如果有,直接返回。未读数据的原因是在之前读取请求行数据的过程中可能会读取部分或全部请求头数据,因此将在此处进行检查;
判断当前的read事件是否就绪,如果是,调用C->:Recv()方法读取当前连接句柄上的数据;
如果当前读事件未准备好,则当前读事件被再次添加到事件队列中,并且该读事件被注册在用于当前连接的epoll句柄上;
判断第二步的返回值。如果为0,则表示客户端已断开连接。如果是NGX_ERROR,说明读取的数据异常。在这两种情况下,当前连接都将被关闭,并向客户端返回一个400状态代码。如果返回值NGX_AGAIN,则继续第三步中的步骤,继续监听读取事件。如果返回值大于0,则读取成功,这个大于0的值表示读取数据的长度;
更新存储读取数据的缓冲器的指针数据。
3。摘要
本文主要讲解nginx如何读取和解析请求头,重点介绍读取数据的主要流程代码和读取的详细步骤。
关于nginx请求头数据的详细读取过程,本文到此为止。有关读取nginx请求头数据的更多信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望大家以后能多多支持我们!
评论列表(0条)