nginx:Expect 的行为:100-continue with HTTP 重定向

问题描述

我在 NginxPUT 重定向方面遇到了一些问题: 假设我有一个位于 Nginx 服务器后面的 HTTP 服务(假设 HTTP 1.1)

客户端使用 PUT /my/api 执行 Expect: 100-continue。 我的服务没有发送 100-continue,而是发送 307 重定向到另一个端点(在本例中为 S3)。 但是,Nginx 出于某种未知原因在提供重定向服务之前发送了 100-continue - 客户端在提供重定向服务之前继续将整个正文上传Nginx。这会导致客户端两次有效地传输正文 - 这对于多 GB 的上传来说不是很好

我想知道是否有办法:

  • 防止 Nginx 发送 100-continue,除非服务确实发送。
  • 允许具有任意大 Content-Length 的请求,而不必将 client_max_body_size 设置为大值(以避免 413 Entity too large)。

由于我的服务仅发送重定向而不发送 100-Continue,因此请求正文永远不会到达 Nginx。必须设置 client_max_body_size 并等待 Nginx 缓冲整个主体以提供重定向服务是非常不理想的。

我已经能够用 Apache 做到这一点,但不能用 Nginx。 Apache 在修复之前曾经有相同的行为:https://bz.apache.org/bugzilla/show_bug.cgi?id=60330 - 想知道 Nginx 是否有同样的问题

任何指针表示赞赏:)

编辑 1:这是重现问题的示例设置:

  • Nginx 监听 80 端口,转发到 localhost 的 9999 端口
  • 一个简单的 HTTP 服务器侦听端口 9999,它总是在 PUT 上返回重定向
  1. Nginx.conf
worker_rlimit_nofile 261120;
worker_shutdown_timeout 10s ;

events {
    multi_accept        on;
    worker_connections  16384;
    use                 epoll;
}

http {
 server { 
  listen 80;
  server_name frontend;
  keepalive_timeout  75s;
  keepalive_requests 100;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:9999/;
  }
 }
}

我在上面运行

docker run --rm --name Nginx --net=host -v /path/to/Nginx.conf:/etc/Nginx/Nginx.conf:ro Nginx:1.21.1
  1. 简单的 python3 HTTP 服务器。
#!/usr/bin/env python3

import sys
from http.server import HTTPServer,BaseHTTPRequestHandler

class Redirect(BaseHTTPRequestHandler):
   def do_PUT(self):
       self.send_response(307)
       self.send_header('Location','https://s3.amazonaws.com/test')
       self.end_headers()

HTTPServer(("",9999),Redirect).serve_forever()

测试结果:

  • 直接上传到 python 服务器按预期工作。 python 服务器不会在 PUT 上发送 100-continue - 它会在看到正文之前直接发送 307 重定向
$ curl -sv -L -X PUT -T /some/very/large/file 127.0.0.1:9999/test
> PUT /test HTTP/1.1
> Host: 127.0.0.1:9999
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Length: 531202949
> Expect: 100-continue
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0,assume close after body
< HTTP/1.0 307 Temporary Redirect
< Server: BaseHTTP/0.6 Python/3.9.2
< Date: Thu,15 Jul 2021 10:16:44 GMT
< Location: https://s3.amazonaws.com/test
< 
* Closing connection 0
* Issue another request to this URL: 'https://s3.amazonaws.com/test'
*   Trying 52.216.129.157:443...
* Connected to s3.amazonaws.com (52.216.129.157) port 443 (#1)
> PUT /test HTTP/1.0
> Host: s3.amazonaws.com
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Length: 531202949
> 
  • 通过 Nginx 执行相同的操作失败并显示 413 Entity too large - 即使主体不应通过 Nginx
  • 在配置中添加client_max_body_size 1G;后,结果不同,除了Nginx尝试缓冲整个主体:
$ curl -sv -L -X PUT -T /some/very/large/file 127.0.0.1:80/test
*   Trying 127.0.0.1:80...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> PUT /test HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Length: 531202949
> Expect: 100-continue
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 100 Continue
} [65536 bytes data]
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
< HTTP/1.1 502 Bad Gateway
< Server: Nginx/1.21.1
< Date: Thu,15 Jul 2021 10:22:08 GMT
< Content-Type: text/html
< Content-Length: 157
< Connection: keep-alive
< 
{ [157 bytes data]
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>Nginx/1.21.1</center>
</body>
</html>

注意 Nginx 如何发送 HTTP/1.1 100 Continue 使用这个简单的 python 服务器,请求随后失败,因为 python 服务器在服务重定向后立即关闭连接,这导致 Nginx 由于管道损坏而服务 502:

127.0.0.1 - - [15/Jul/2021:10:22:08 +0000] "PUT /test HTTP/1.1" 502 182 "-" "curl/7.74.0"
2021/07/15 10:22:08 [error] 31#31: *1 writev() Failed (32: broken pipe) while sending request to upstream,client: 127.0.0.1,server: frontend,request: "PUT /test HTTP/1.1",upstream: "http://127.0.0.1:9999/test",host: "127.0.0.1"

据我所知,这似乎与下面的 Apache 问题 https://bz.apache.org/bugzilla/show_bug.cgi?id=60330 完全一样(现在在较新的版本中已解决)。我不知道如何用 Nginx 来规避这个

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)