当WSGI应用在消费environ['wsgi.input']之前响应时发生TCP连接重置

4 投票
1 回答
1849 浏览
提问于 2025-04-15 18:14

为了我们的网络服务,我写了一些逻辑来防止发送超过4MB的multipart/form-data POST请求。

简单来说,就是下面这些代码(我去掉了所有WebOb的使用,只保留了最基本的WSGI代码):

import paste.httpserver

form = """\
<html>
<body>
  <form method="post" enctype="multipart/form-data" action="/">
    <input type="file" name="photopicker" />
    <input type="submit" />
  </form>
</body>
</html>
"""

limit = 4 * 1024 * 1024

def upload_app(environ, start_response):
    if environ['REQUEST_METHOD'] == 'POST':
        if int(environ.get('CONTENT_LENGTH', '0')) > limit:
            start_response('400 Ouch', [('content-type', 'text/plain')])
            return ["Upload is too big!"]
    # elided: consume the file appropriately
    start_response('200 OK', [('content-type', 'text/html')])
    return [form]

paste.httpserver.serve(upload_app, port=7007)

这段逻辑在单元测试时运行得很好。但是,当我尝试向这个接口发送实际大于4MB的文件时,客户端却出现了这样的错误:

  • Error 101 (net::ERR_CONNECTION_RESET): Unknown error. 这是来自谷歌浏览器的错误信息。
  • The connection to the server was reset while the page was loading. 这是来自火狐浏览器的错误信息。

使用Python内置的wsgiref HTTP服务器时也会出现同样的错误。

事实是:当我在返回HTTP 400之前加上environ['wsgi.input'].read()时,连接重置的问题就消失了。当然,这并不是一个好的解决办法。它只是表明当你完全读取输入时会发生什么。

我查阅了《HTTP: The Definitive Guide》,发现了一些有趣的指导原则,强调在实现HTTP服务器和客户端时,管理TCP连接是非常重要的。书中提到,与其直接close套接字,更推荐使用shutdown,这样客户端就有机会反应并停止向服务器发送更多数据。

也许我遗漏了一些关键的实现细节,导致了这种连接重置的问题。有没有人能提供一些见解?

可以查看这个链接。

1 个回答

2

这个问题发生的原因是你没有读取输入流就把它丢弃了,这样就导致它被强制关闭了。浏览器已经把文件的一部分放入了发送队列,但因为服务器强行关闭了连接,所以就出现了写入错误。

我所知道的,解决这个问题的方法就是必须读取所有的输入。

我建议你使用一些JavaScript来在发送文件之前检查文件的大小。这样,只有那些忽视客户端检查的人会遇到错误,比如他们没有开启JavaScript,或者故意想搞事情的人。

撰写回答