nginx请求体读取(二)

2,丢弃请求体

一个模块想要主动的丢弃客户端发过的请求体,可以调用nginx核心提供的ngx_http_discard_request_body()接口,主动丢弃的原因可能有很多种,如模块的业务逻辑压根不需要请求体 ,客户端发送了过大的请求体,另外为了兼容http1.1协议的pipeline请求,模块有义务主动丢弃不需要的请求体。总之为了保持良好的客户端兼容性,nginx必须主动丢弃无用的请求体。下面开始分析ngx_http_discard_request_body()函数:

[cpp] view plaincopy

  1. <span style="font-size:18px;">ngx_int_t
  2. ngx_http_discard_request_body(ngx_http_request_t *r)
  3. {
  4. ssize_t       size;
  5. ngx_event_t  *rev;
  6. if (r != r->main || r->discard_body) {
  7. return NGX_OK;
  8. }
  9. if (ngx_http_test_expect(r) != NGX_OK) {
  10. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  11. }
  12. rev = r->connection->read;
  13. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
  14. if (rev->timer_set) {
  15. ngx_del_timer(rev);
  16. }
  17. if (r->headers_in.content_length_n <= 0 || r->request_body) {
  18. return NGX_OK;
  19. }
  20. size = r->header_in->last - r->header_in->pos;
  21. if (size) {
  22. if (r->headers_in.content_length_n > size) {
  23. r->header_in->pos += size;
  24. r->headers_in.content_length_n -= size;
  25. } else {
  26. r->header_in->pos += (size_t) r->headers_in.content_length_n;
  27. r->headers_in.content_length_n = 0;
  28. return NGX_OK;
  29. }
  30. }
  31. r->read_event_handler = ngx_http_discarded_request_body_handler;
  32. if (ngx_handle_read_event(rev, 0) != NGX_OK) {
  33. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  34. }
  35. if (ngx_http_read_discarded_request_body(r) == NGX_OK) {
  36. r->lingering_close = 0;
  37. } else {
  38. r->count++;
  39. r->discard_body = 1;
  40. }
  41. return NGX_OK;
  42. }
  43. </span>

由于函数不长,这里把它完整的列出来了,函数的开始同样先判断了不需要再做处理的情况:子请求不需要处理,已经调用过此函数的也不需要再处理。接着调用ngx_http_test_expect() 处理http1.1 expect的情况,根据http1.1的expect机制,如果客户端发送了expect头,而服务端不希望接收请求体时,必须返回417(Expectation Failed)错误。nginx并没有这样做,它只是简单的让客户端把请求体发送过来,然后丢弃掉。接下来,函数删掉了读事件上的定时器,因为这时本身就不需要请求体,所以也无所谓客户端发送的快还是慢了,当然后面还会将到,当nginx已经处理完该请求但客户端还没有发送完无用的请求体时,nginx会在读事件上再挂上定时器。

函数同样还会检查请求头中的content-length头,客户端如果打算发送请求体,就必须发送content-length头,同时还会查看其他地方是不是已经读取了请求体。如果确实有待处理的请求体,函数接着检查请求头buffer中预读的数据,预读的数据会直接被丢掉,当然如果请求体已经被全部预读,函数就直接返回了。

接下来,如果还有剩余的请求体未处理,该函数调用ngx_handle_read_event()在事件处理机制中挂载好读事件,并把读事件的处理函数设置为ngx_http_discarded_request_body_handler。做好这些准备之后,该函数最后调用ngx_http_read_discarded_request_body()接口读取客户端过来的请求体并丢弃。如果客户端并没有一次将请求体发过来,函数会返回,剩余的数据等到下一次读事件过来时,交给ngx_http_discarded_request_body_handler()来处理,这时,请求的discard_body将被设置为1用来标识这种情况。另外请求的引用数(count)也被加1,这样做的目的是客户端可能在nginx处理完请求之后仍未完整发送待发送的请求体,增加引用是防止nginx核心在处理完请求后直接释放了请求的相关资源。

ngx_http_read_discarded_request_body()函数非常简单,它循环的从链接中读取数据并丢弃,直到读完接收缓冲区的所有数据,如果请求体已经被读完了,该函数会设置读事件的处理函数为ngx_http_block_reading,这个函数仅仅删除水平触发的读事件,防止同一事件不断被触发。

再来看一下读事件的处理函数ngx_http_discarded_request_body_handler,这个函数每次读事件来时会被调用,先看一下它的源码:

[cpp] view plaincopy

  1. <span style="font-size:18px;">void
  2. ngx_http_discarded_request_body_handler(ngx_http_request_t *r)
  3. {
  4. ...
  5. c = r->connection;
  6. rev = c->read;
  7. if (rev->timedout) {
  8. c->timedout = 1;
  9. c->error = 1;
  10. ngx_http_finalize_request(r, NGX_ERROR);
  11. return;
  12. }
  13. if (r->lingering_time) {
  14. timer = (ngx_msec_t) (r->lingering_time - ngx_time());
  15. if (timer <= 0) {
  16. r->discard_body = 0;
  17. r->lingering_close = 0;
  18. ngx_http_finalize_request(r, NGX_ERROR);
  19. return;
  20. }
  21. } else {
  22. timer = 0;
  23. }
  24. rc = ngx_http_read_discarded_request_body(r);
  25. if (rc == NGX_OK) {
  26. r->discard_body = 0;
  27. r->lingering_close = 0;
  28. ngx_http_finalize_request(r, NGX_DONE);
  29. return;
  30. }
  31. /* rc == NGX_AGAIN */
  32. if (ngx_handle_read_event(rev, 0) != NGX_OK) {
  33. c->error = 1;
  34. ngx_http_finalize_request(r, NGX_ERROR);
  35. return;
  36. }
  37. if (timer) {
  38. clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
  39. timer *= 1000;
  40. if (timer > clcf->lingering_timeout) {
  41. timer = clcf->lingering_timeout;
  42. }
  43. ngx_add_timer(rev, timer);
  44. }
  45. }
  46. </span>

函数一开始就处理了读事件超时的情况,之前说到在ngx_http_discard_request_body()函数中已经删除了读事件的定时器,那么什么时候会设置定时器呢?答案就是在nginx已经处理完该请求,但是又没有完全将该请求的请求体丢弃的时候(客户端可能还没有发送过来),在ngx_http_finalize_connection()函数中,如果检查到还有未丢弃的请求体时,nginx会添加一个读事件定时器,它的时长为lingering_timeout指令所指定,默认为5秒,不过这个时间仅仅两次读事件之间的超时时间,等待请求体的总时长为lingering_time指令所指定,默认为30秒。这种情况中,该函数如果检测到超时事件则直接返回并断开连接。同样,还需要控制整个丢弃请求体的时长不能超过lingering_time设置的时间,如果超过了最大时长,也会直接返回并断开连接。

如果读事件发生在请求处理完之前,则不用处理超时事件,也不用设置定时器,函数只是简单的调用ngx_http_read_discarded_request_body()来读取并丢弃数据。

时间: 2024-12-17 07:01:33

nginx请求体读取(二)的相关文章

nginx请求体读取

上节说到nginx核心本身不会主动读取请求体,这个工作是交给请求处理阶段的模块来做,但是nginx核心提供了ngx_http_read_client_request_body()接口来读取请求体,另外还提供了一个丢弃请求体的接口-ngx_http_discard_request_body(),在请求执行的各个阶段中,任何一个阶段的模块如果对请求体感兴趣或者希望丢掉客户端发过来的请求体,可以分别调用这两个接口来完成.这两个接口是nginx核心提供的处理请求体的标准接口,如果希望配置文件中一些请求体

nginx源码分析--请求体的读取(1)

首先请求体的读取已经进入了HTTP请求的11个阶段,针对有些模块需要对请求体做一些处理,那么这个模块就需要在这个阶段注册函数,其中读取请求体的函数ngx_http_read_client_request_body()是存在的,只不过不同的模块可能对请求体做不同的处理,读取请全体的函数是在某个模块的conent_handler函数中包含的,比如比如proxy模块,fastcgi模块,uwsgi模块等这些模块对请求体感兴趣,那么读取请求体的函数在这些模块的content_handler中注册. 上节

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

场景交代 在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证.当参数在url后面时(queryString)获取参数进行验证之后程序正常运行.但是,当请求参数在请求体中的时候,通过流的方式将请求体取出参数进行验证之后,发现后续流程抛出错误: Required request body is missing ... 经过排查,发现ServletInputStream的流只能读取一次(参考:httpServletRequest中的流只能读取一次的原因). 这就是为什么在拦截器

nginx学习笔记之二:nginx作为web server

一.nginx的配置文件:nginx.conf 1.nginx配置文件的结构: main(全局配置段) events {...} http { ... server { location ... {...} location ... {...} ... } server { ... } } 2.配置参数需要以分号结尾,语法格式: 参数名  值1 [值2 ...]; 3.配置文件中还可使用变量: 模块内置变量 用户自定义变量:set var_name value 4.配置文件检查:nginx -t

Nginx深度优化(二)

Nginx作为现在最流行的Web应用程序,对其优化十分重要.通过Nginx初步优化.深度优化Nginx(一)已经可以对Nginx进行大量的优化来满足基本的需要,但是作为一名合格的运维工程师来说,仅仅掌握以上对Nginx进行优化的方法显然是远远不足的.所以就需要本篇博文进一步对Nginx进行优化. 博文大纲:一.安装nginx服务器二.Nginx配置优化(1)Nginx 运行工作进程个数(2)Nginx 事件处理模型 (3)开启高效传输模式(4)连接超时时间(5)fastcgi调优(6)expir

Nginx详解(二)操作

一.前言二.Nginx安装三.Nginx的配置文件详解四.nginx配置之http段五.其他模块六.实验 一.前言http://www.nginx.cn/doc/  nginx安装等各个操作界面介绍Nginx主要实现两个功能:Web服务器和反向代理Nginx的模块类型:    核心模块:core module    标准模块:        Stanard HTTP modules        Optional HTTP modules        Mail modules    第三方模块

SpringCloud Gateway获取post请求体(request body)

获取spring cloud gateway POST请求体的时候,会有很多坑,网上大多数解决方案是 /** 这种方法在spring-boot-starter-parent 2.0.6.RELEASE + Spring Cloud Finchley.SR2 body 中生效, 但是在spring-boot-starter-parent 2.1.0.RELEASE + Spring Cloud Greenwich.M3 body 中不生效,总是为空 */ private String resolv

Nginx基本用法篇二

一.nginx安装 1. yum 安装 yum install nginx 2.编译安装 useradd nginx -r -s /sbin/nologin wget http://nginx.org/download/nginx-1.12.2.tar.gz tar xf nginx-1.12.2.tar.gz cd nginx-1.12.2 ./configure –prefix=/usr/local/nginx –conf-path=/etc/nginx/nginx.conf –error-

python写http post请求的四种请求体

Web自动化测试(25) HTTP 协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式.常见的四种编码方式如下: 1.application/x-www-form-urlencoded 这应该是最常见的 POST 提交数据的方式了.浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据.请求类似于下面这样(无关的请求头在本文中