上节说到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模块等,这些模块的行为必须将客户端过来的请求体(如果有的话)以相应协议完整的转发到后端服务进程,所有的这些模块都是调用了ngx_http_read_client_request_body()接口来完成请求体读取。值得注意的是这些模块会把客户端的请求体完整的读取后才开始往后端转发数据。

由于内存的限制,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()接口的实现,它的定义如下:

  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中调用此接口,一个典型的调用如下:

  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调用的上下文如下:

  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()检查客户端是否发送了Expect: 100-continue头,是的话则给客户端回复"HTTP/1.1 100 Continue",根据http 1.1协议,客户端可以发送一个Expect头来向服务器表明期望发送请求体,服务器如果允许客户端发送请求体,则会回复"HTTP/1.1 100 Continue",客户端收到时,才会开始发送请求体。

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

  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时,该函数在调用回调函数之前,会创建一个空的临时文件。

进入到函数下半部分,表明客户端请求确实表明了要发送请求体,该函数会先检查是否在读取请求头时预读了请求体,这里的检查是通过判断保存请求头的缓存(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变量只在当请求体已经被读取并且是全部保存在内存中,才能取得相应的数据。

nginx请求体读取的更多相关文章

  1. nginx请求体读取(二)

    2,丢弃请求体 一个模块想要主动的丢弃客户端发过的请求体,可以调用nginx核心提供的ngx_http_discard_request_body()接口,主动丢弃的原因可能有很多种,如模块的业务逻辑压 ...

  2. 使用Enablebuffering多次读取Asp Net Core 3.0 请求体 读取Request.Body流

    原文:使用Enablebuffering多次读取Asp Net Core 请求体 使用Enablebuffering多次读取Asp Net Core 请求体 1 .Net Core 2.X时代 使用E ...

  3. nginx读取请求体

    请求体的读取一般发生在nginx的content handler中,一些nginx内置的模块,比如proxy模块,fastcgi模块,uwsgi模块等,这些模块的行为必须将客户端过来的请求体(如果有的 ...

  4. http 请求体数据--ngx

    HTTP包体的长度有可能非常大,不同业务可能对包体读取 处理不相同, 比如waf, 也许会读取body内容或者只是读取很少的前几十字节.所以根据不同业务特性,对http body 数据包处理方式不同, ...

  5. springboot请求体中的流只能读取一次的问题

    场景交代 在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证.当参数在url后面时(queryString)获取参数进行验证之后程序正常运行.但是,当请求参数在请求体中的时候, ...

  6. 使用Enablebuffering多次读取Asp Net Core 请求体

    使用Enablebuffering多次读取Asp Net Core 请求体 1 .Net Core 2.X时代 使用EnableRewind倒带 public IActionResult Index( ...

  7. post请求体过大导致ngx.req.get_post_args()取不到参数体的问题

    http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size 该地址对于client_body_buf ...

  8. 获取【请求体】数据的3种方式(精)(文末代码) request.getInputStream() request.getInputStream() request.getReader()

    application/x- www-form-urlencoded是Post请求默认的请求体内容类型,也是form表单默认的类型.Servlet API规范中对该类型的请求内容提供了request. ...

  9. 从Excel获取请求体

    Excel文件 .py文件---------------------- import xlrdimport re def fetch_body(path,sheet,name,adict):     ...

随机推荐

  1. 【转】Apache配置中ProxyPassReverse指令的含义

    此文章来源:http://blog.csdn.net/yl_wh/article/details/8697501 apache中的mod_proxy模块主要作用就是进行url的转发,即具有代理的功能. ...

  2. mysql的client和sever之间通信password的传输方式

    本文想要说明的是,当我们用mysql -uroot -p1234567 -h127.0.0.1 -P3306 去连接mysql server时密码是通过什么样的形式传过去的呢? 首先密码这种东西明文传 ...

  3. text-overflow 文字溢出时的设置

    text-overflow : clip | ellipsis clip: 不显示省略标记(...),而是简单的裁切. ellipsis: 当对象内文本溢出时显示省略标记(...) 设置或检索是否使用 ...

  4. ini_set()注意要点和解决方法

    在php编程中,很多情况下,我们不希望通过修改配置文件(php.ini)来实现相关功能,这样不利于代码的可移植性. 大多数情况下,我们会使用ini_set()来实现配置文件的占时修改.但需要注意的是, ...

  5. 从一道面试题谈linux下fork的运行机制

    http://www.cnblogs.com/leoo2sk/archive/2009/12/11/talk-about-fork-in-linux.html

  6. python list列表 方法总结

    深入链表(most on lists) The list data type has some more methods. Here are all of the methods of list ob ...

  7. Android 匿名共享内存Java接口分析

    在Android 匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体 ...

  8. bzoj1623 [Usaco2008 Open]Cow Cars 奶牛飞车

    Description   编号为1到N的N只奶牛正各自驾着车打算在牛德比亚的高速公路上飞驰.高速公路有M(1≤M≤N)条车道.奶牛i有一个自己的车速上限Si(l≤Si≤1,000,000).     ...

  9. 百度地图LV1.5实践项目开发工具类bmap.util.jsV1.1

    /** * 百度地图使用工具类-v1.5 * * @author boonya * @date 2013-7-7 * @address Chengdu,Sichuan,China * @email b ...

  10. Remove Element 解答

    Question Given an array and a value, remove all instances of that value in place and return the new ...