nginx源代码分析--读请求主体(1)

首先,读取请求体已进入HTTP要求11相,我们需要做的请求正文部分处理一些模块,所以这个模块需要注册功能在这个阶段,在阅读功能要求的身体ngx_http_read_client_request_body()是存在的。仅仅只是不同的模块可能对请求体做不同的处理。读取请全体的函数是在某个模块的conent_handler函数中包括的。比方比方proxy模块,fastcgi模块,uwsgi模块等这些模块对请求体感兴趣,那么读取请求体的函数在这些模块的content_handler中注冊。

上节说到nginx核心本身不会主动读取请求体,这个工作是交给请求处理阶段的模块来做,可是nginx核心提供了ngx_http_read_client_request_body()接口来读取请求体,另外还提供了一个丢弃请求体的接口-ngx_http_discard_request_body(),在请求运行的各个阶段中。不论什么一个阶段的模块假设对请求体感兴趣或者希望丢掉客户端发过来的请求体。可以分别调用这两个接口来完毕。

这两个接口是nginx核心提供的处理请求体的标准接口。假设希望配置文件里一些请求体相关的指令(比方client_body_in_file_only,client_body_buffer_size等)可以预期工作,

以及可以正常使用nginx内置的一些和请求体相关的变量(比方$request_body和$request_body_file)。一般来说全部模块都必须调用这些接口来完毕对应操作,假设须要自己定义接口来处理请求体,也应尽量兼容nginx默认的行为。

1,读取请求体

请求体的读取一般发生在nginx的content handler中。一些nginx内置的模块。比方proxy模块,fastcgi模块。uwsgi模块等。这些模块的行为必须将client过来的请求体(假设有的话)以对应协议完整的转发到后端服务进程,全部的这些模块都是调用了ngx_http_read_client_request_body()接口来完毕请求体读取。

值得注意的是这些模块会把client的请求体完整的读取后才開始往后端转发数据。

因为内存的限制,ngx_http_read_client_request_body()接口读取的请求体会部分或者所有写入一个暂时文件里,依据请求体的大小以及相关的指令配置,请求体可能完整放置在一块连续内存中。也可能分别放置在两块不同内存中,还可能所有存在一个暂时文件里,最后还可能一部分在内存,剩余部分在暂时文件里。以下先介绍一下和这些不同存储行为相关的指令:

client_body_buffer_size:设置缓存请求体的buffer大小。默觉得系统页大小的2倍,当请求体的大小超过此大小时,nginx会把请求体写入到暂时文件里。能够依据业务需求设置合适的大小。尽量避免磁盘io操作;

client_body_in_single_buffer:指示是否将请求体完整的存储在一块连续的内存中,默觉得off,假设此指令被设置为on。则nginx会保证请求体在不大于client_body_buffer_size设置的值时,被存放在一块连续的内存中,但超过大小时会被整个写入一个暂时文件;

client_body_in_file_only:设置是否总是将请求体保存在暂时文件里,默觉得off,当此指定被设置为on时,即使客户端显示指示了请求体长度为0时。nginx还是会为请求创建一个暂时文件。

接着介绍ngx_http_read_client_request_body()接口的实现,它的定义例如以下:

[cpp] view plaincopy

  1. <span style="font-family:SimSun;font-size:18px;">ngx_int_t
  2. ngx_http_read_client_request_body(ngx_http_request_t *r,
  3. ngx_http_client_body_handler_pt post_handler)</span>

该接口有2个參数,第1个为指向请求结构的指针。第2个为一个函数指针。当请求体读完时。它会被调用。之前也说到依据nginx现有行为,模块逻辑会在请求体读完后运行。这个回调函数一般就是模块的逻辑处理函数。ngx_http_read_client_request_body()函数首先将參数r相应的主请求的引用加1。这样做的目的和该接口被调用的上下文有关。一般而言。模块是在content handler中调用此接口,一个典型的调用例如以下:

[cpp] view plaincopy

  1. <span style="font-family:SimSun;font-size:18px;">static ngx_int_t
  2. ngx_http_proxy_handler(ngx_http_request_t *r)
  3. {
  4. ...
  5. rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
  6. if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
  7. return rc;
  8. }
  9. return NGX_DONE;
  10. }</span>

上面的代码是在porxy模块的content handler,ngx_http_proxy_handler()中调用了ngx_http_read_client_request_body()函数,当中ngx_http_upstream_init()被作为回调函数传入进接口中。另外nginx中模块的content
handler调用的上下文例如以下:

[cpp] view plaincopy

  1. <span style="font-family:SimSun;font-size:18px;">ngx_int_t
  2. ngx_http_core_content_phase(ngx_http_request_t *r,
  3. ngx_http_phase_handler_t *ph)
  4. {
  5. ...
  6. if (r->content_handler) {
  7. r->write_event_handler = ngx_http_request_empty_handler;
  8. ngx_http_finalize_request(r, r->content_handler(r));
  9. return NGX_OK;
  10. }
  11. ...
  12. }</span>

上面的代码中,content handler调用之后,它的返回值作为參数调用了ngx_http_finalize_request()函数。在请求体没有被接收全然时,ngx_http_read_client_request_body()函数返回值为NGX_AGAIN。此时content
handler,比方ngx_http_proxy_handler()会返回NGX_DONE,而NGX_DONE作为參数传给ngx_http_finalize_request()函数会导致主请求的引用计数减1,所以正好抵消了ngx_http_read_client_request_body()函数开头对主请求计数的加1。

接下来回到ngx_http_read_client_request_body()函数。它会检查该请求的请求体是否已经被读取或者被丢弃了,假设是的话。则直接调用回调函数并返回NGX_OK,这里实际上是为子请求检查,子请求是nginx中的一个概念,nginx中能够在当前请求中发起另外一个或多个全新的子请求来訪问其它的location,关于子请求的具体介绍会在后面的章节作具体分析,一般而言子请求不须要自己去读取请求体。

函数接着调用ngx_http_test_expect()检查client是否发送了Expect: 100-continue头,是的话则给client回复"HTTP/1.1
100 Continue"。依据http 1.1协议,client能够发送一个Expect头来向server表明期望发送请求体,server假设同意client发送请求体。则会回复"HTTP/1.1 100 Continue",client收到时。才会開始发送请求体。

接着继续为接收请求体做准备工作。分配一个ngx_http_request_body_t结构,并保存在r->request_body,这个结构用来保存请求体读取过程用到的缓存引用,暂时文件引用,剩余请求体大小等信息,它的定义例如以下。

[cpp] view plaincopy

  1. <span style="font-family:SimSun;font-size:18px;">typedef struct {
  2. ngx_temp_file_t                  *temp_file;
  3. ngx_chain_t                      *bufs;
  4. ngx_buf_t                        *buf;
  5. off_t                             rest;
  6. ngx_chain_t                      *to_write;
  7. ngx_http_client_body_handler_pt   post_handler;
  8. } ngx_http_request_body_t;</span>

temp_file: 指向储存请求体的暂时文件的指针;

bufs: 指向保存请求体的链表头;

buf: 指向当前用于保存请求体的内存缓存。

rest: 当前剩余的请求体大小;

post_handler:保存传给ngx_http_read_client_request_body()函数的回调函数。

做好准备工作之后,函数開始检查请求是否带有content_length头。假设没有该头或者客户端发送了一个值为0的content_length头,表明没有请求体,这时直接调用回调函数并返回NGX_OK就可以。当然假设client_body_in_file_only指令被设置为on,且content_length为0时。该函数在调用回调函数之前。会创建一个空的暂时文件。

进入到函数下半部分,表明client请求确实表明了要发送请求体,该函数会先检查是否在读取请求头时预读了请求体。这里的检查是通过推断保存请求头的缓存(r->header_in)中是否还有未处理的数据。假设有预读数据。则分配一个ngx_buf_t结构,并将r->header_in中的预读数据保存在当中,而且假设r->header_in中还有剩余空间,而且可以容下剩余未读取的请求体,这些空间将被继续使用。而不用分配新的缓存,当然甚至假设请求体已经被整个预读了,则不须要继续处理了,此时调用回调函数后返回。

假设没有预读数据或者预读不完整,该函数会分配一块新的内存(除非r->header_in还有足够的剩余空间)。另外假设request_body_in_single_buf指令被设置为no。则预读的数据会被拷贝进新开辟的内存块中,真正读取请求体的操作是在ngx_http_do_read_client_request_body()函数,该函数循环的读取请求体并保存在缓存中。假设缓存被写满了。当中的数据会被清空并写回到暂时文件里。当然这里有可能不能一次将数据读到。该函数会挂载读事件并设置读事件handler为ngx_http_read_client_request_body_handler。另外nginx核心对两次请求体的读事件之间也做了超时设置,client_body_timeout指令能够设置这个超时时间,默觉得60s,假设下次读事件超时了,nginx会返回408给客户端。

终于读完请求体后。ngx_http_do_read_client_request_body()会依据配置,将请求体调整到预期的位置(内存或者文件)。所有情况下从主体的请求能够r->request_body的bufs获取名单,列表可以是向上2节点,每个节点是一个buffer,但是,这buffer所述内容可以被存储在存储器。它可被存储在一个磁盘文件。

另$request_body变量只有当请求体已被读出并存储在存储器中用于所有,能力,以获得相应的数据。

时间: 2024-10-08 09:04:43

nginx源代码分析--读请求主体(1)的相关文章

Raid1源代码分析--读流程

我阅读的代码的linux内核版本是2.6.32.61.刚进实验室什么都不懂,处于摸索阶段,近期的任务就是阅读raid1的源码.第一次接触raid相关的东西,网上分析源码的资料又比较少,不详细.逐行阅读代码,做了笔记.如果要对raid1的读流程有个整体上的把握,需要将笔记中的主线提炼出来,这里不写了.理解不足或者有误之处,希望批评指正. 读流程主要涉及以下函数: 请求函数make_request 读均衡read_balance 回调函数raid1_end_read_request 读出错处理rai

nginx源代码分析之内存池实现原理

建议看本文档时结合nginx源代码. 1.1   什么是内存池?为什么要引入内存池? 内存池实质上是接替OS进行内存管理.应用程序申请内存时不再与OS打交道.而是从内存池中申请内存或者释放内存到内存池.因此.内存池在实现的过程中,必定有一部分操作时从OS中申请内存.或者释放内存到OS.例如以下图所看到的: 图1 内存池的引入可有效解决两个问题: (1) 减少应用程序与OS之间进行频繁内存和释放的系统调用,进而减少程序执行期间在两个空间的切换,提升了程序执行效率: (2)内存池可依据应用特性组织内

nginx源代码分析--从源代码看nginx框架总结

nginx源代码总结: 1)代码中没有特别绕特别别扭的编码实现.从变量的定义调用函数的实现封装,都非常恰当.比方从函数命名或者变量命名就能够看出来定义的大体意义,函数的基本功能,再好的架构实如今编码习惯差的人实现也会黯然失色,假设透彻理解代码的实现,领悟架构的设计初衷,认为每块代码就想经过耐心雕琢一样,不只实现了主要的功能给你,为其它人阅读也会提供非常好的支持.仔细恰当的命名规则就能够看出作者的功力. 2)更好更高的软件性能体如今架构设计上,好的架构会让软件更加稳定.easy维护.便于扩展.从核

nginx源代码分析--监听套接字的创建 套接字的监听 HTTP请求创建连接

作为一个webserver,那么肯定是有监听套接字的,这个监听套接字是用于接收HTTP请求的,这个监听套接字的创建是依据配置文件的内容来创建的,在nginx.conf文件里有多少个地址就须要创建多少个监听套接字.这里不说各个结构体的构造 仅仅说大体情况! 1).首先在main函数中调用了ngx_init_cycle()函数,在这个函数的最后调用了ngx_open_listening_sockets函数,这个函数负责将创建的监听套接字进行套接字选项的设置(比方非堵塞.接受发送的缓冲区.绑定.监听处

nginx源代码分析--ngx_http_optimize_servers()函数

这个函数做了连部分工作:1)以port为入口点 将实用的信息存放到hash表内 2)调用ngx_http_init_listening()函数 对port进行监听 1. 在ngx_http_core_main_conf_t结构体中有一个字段为ports,是一个数组,数组内存放的全是ngx_http_conf_port_t:对于每个端口信息(ngx_http_conf_port_t),调用 ngx_http_server_names函数,同一时候也调用ngx_http_init_listening

nginx源代码分析--event事件驱动初始化

1.在nginx.c中设置每一个核心模块的index ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; } 2.进入函数ngx_init_cycle,调用每一个核心模块的create_conf for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_CORE_MODULE)

nginx源代码分析--GDB调试

利用gdb[i]调试nginx[ii]和利用gdb调试其他程序没有两样,只是nginx能够是daemon程序,也能够以多进程执行,因此利用gdb调试和寻常会有些许不一样. 当然,我们能够选择将nginx设置为非daemon模式并以单进程执行.而这需做例如以下设置就可以: daemon off; master_process off; 这是第一种情况: 这样的设置下的nginx在gdb下调试非常普通,过程能够[iii]是这样: 运行命令: [email protected]:/usr/local/

struts2请求过程源代码分析

struts2请求过程源代码分析 Struts2是Struts社区和WebWork社区的共同成果.我们甚至能够说,Struts2是WebWork的升级版.他採用的正是WebWork的核心,所以.Struts2并非一个不成熟的产品,相反.构建在WebWork基础之上的Struts2是一个执行稳定.性能优异.设计成熟的WEB框架. 我这里的struts2源代码是从官网下载的一个最新的struts-2.3.15.1-src.zip.将其解压就可以. 里面的文件夹页文件很的多,我们仅仅须要定位到stru

CSAPP Tiny web server源代码分析及搭建执行

1. Web基础 webclient和server之间的交互使用的是一个基于文本的应用级协议HTTP(超文本传输协议). 一个webclient(即浏览器)打开一个到server的因特网连接,而且请求某些内容.server响应所请求的内容,然后关闭连接.浏览器读取这些内容.并把它显示在屏幕上. 对于webclient和server而言.内容是与一个MIME类型相关的字节序列. 常见的MIME类型: MIME类型 描写叙述 text/html HTML页面 text/plain 无格式文本 ima