为什么environ['wsgi.input'].read()会阻塞,尽管PEP-3333允许?

6 投票
1 回答
2753 浏览
提问于 2025-04-18 01:35

问题

这里有一个简单的WSGI应用程序,它应该在头部打印内容的长度和请求体。

def application(environ, start_response):
    start_response('200 OK', [('Content-Type','text/plain')])
    content_length = int(environ['CONTENT_LENGTH'])
    print('---- Begin ----')
    print('CONTENT_LENGTH:', content_length)
    print('wsgi.input:', environ['wsgi.input'].read())
    print('---- End ----')
    return [b'Foo\n']

if __name__ == '__main__':
    from wsgiref import simple_server
    server = simple_server.make_server('0.0.0.0', 8080, application)
    server.serve_forever()

当我运行这个应用程序时,它在以下调用处被阻塞:environ['wsgi.input'].read()

我使用Python 3解释器运行这个应用程序,并通过curl提交HTTP POST请求。

lone@debian:~$ curl --data "a=1&b=2" http://localhost:8080/

curl命令在等待输出时被阻塞。Python解释器在environ['wsgi.input'].read()这个调用处被阻塞。

lone@debian:~$ python3 foo.py
---- Begin ----
CONTENT_LENGTH: 7

从上面的输出可以看到,application()函数在打印CONTENT_LENGTH后被阻塞了。

解决方法

我知道如何解决这个问题:通过将Content-Length头的值传递给read()调用。

修改后的代码来解决这个问题:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type','text/plain')])
    content_length = int(environ['CONTENT_LENGTH'])
    print('---- Begin ----')
    print('CONTENT_LENGTH:', content_length)
    print('wsgi.input:', environ['wsgi.input'].read(content_length))
    print('---- End ----')
    return [b'Foo\n']

if __name__ == '__main__':
    from wsgiref import simple_server
    server = simple_server.make_server('0.0.0.0', 8080, application)
    server.serve_forever()

现在curl命令得到了有效的HTTP响应。

lone@debian:~$ curl --data "a=1&b=2" http://localhost:8080/
Foo
lone@debian:~$

application()函数也完成了它的执行。

lone@debian:~$ python3 foo.py
---- Begin ----
CONTENT_LENGTH: 7
wsgi.input: b'a=1&b=2'
---- End ----
127.0.0.1 - - [06/Apr/2014 17:53:21] "POST / HTTP/1.1" 200 4

疑问

为什么在没有任何参数的情况下调用environ['wsgi.input'].read()会被阻塞呢?

PEP-3333文档似乎暗示它应该可以工作。这里是相关的内容。

服务器不需要读取超过客户端指定的Content-Length,并且应该模拟文件结束的状态,如果应用程序尝试读取超过这个点。应用程序不应该尝试读取超过CONTENT_LENGTH变量指定的数据。

服务器应该允许在没有参数的情况下调用read(),并返回客户端输入流的剩余部分。

我明白应用程序不应该尝试读取超过CONTENT_LENGTH变量指定的数据。我没有遵守这个指令。但是服务器应该允许在没有参数的情况下调用read()并返回整个输入流,为什么它没有这样做呢?

1 个回答

8

因为它只实现了PEP 333,而没有实现PEP 3333。

PEP 333没有规定通过返回空字符串来模拟流的结束。

在PEP 333中,如果WSGI服务器支持HTTP 1.1,并且使用了请求管道(保持连接),那么如果你尝试读取超过CONTENT_LENGTH的内容,就会遇到问题。

我建议你阅读一下PEP 333,并把它和PEP 3333进行对比。

另外,建议你看看:

在这里我详细描述了整个问题,并为PEP在更新到Python 3时的变化做出了贡献。

撰写回答