-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Closed
Labels
docsDocumentation in the Doc dirDocumentation in the Doc dir
Description
Bug report
Bug description:
The example WSGI server in the documentation sets the headers as a list in the simple_app function.
from wsgiref.simple_server import make_server
def hello_world_app(environ, start_response):
status = "200 OK" # HTTP Status
headers = [("Content-type", "text/plain; charset=utf-8")] # HTTP Headers
start_response(status, headers)
# The returned object is going to be printed
return [b"Hello World"]
with make_server("", 8000, hello_world_app) as httpd:
print("Serving on port 8000...")
# Serve until process is killed
httpd.serve_forever()
When moving the headers from the function to a global (as these headers might not change), this global array of tuples will be modified by the calls to start_response
. This is because this function creates a wsgiref.headers.Headers
with the passed reference of the global variable, instead of using a copy.
from wsgiref.simple_server import make_server
count = 10
increase = True
headers = [('Content-type', 'text/plain; charset=utf-8')]
def bug_reproducer_app(environ, start_response):
status = '200 OK'
start_response(status, headers)
# Something that will change its Content-Length on every request
global count
count -= 1
if count == 0:
count = 10
return [b"Hello " + (b"x" * count)]
with make_server('', 8000, bug_reproducer_app) as httpd:
print("Serving on port 8000...")
httpd.serve_forever()
This results the Content-Length
value being set once but never updated, resulting in too-short or too-long answers as shown by curl:
$ # First request will set the Content-Length just fine
$ curl localhost:8000 -v
* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8000...
* connect to ::1 port 8000 from ::1 port 60888 failed: Connection refused
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.5.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Date: Mon, 02 Dec 2024 16:34:16 GMT
< Server: WSGIServer/0.2 CPython/3.12.3
< Content-type: text/plain; charset=utf-8
< Content-Length: 15
<
* Closing connection
Hello xxxxxxxxx%
$ # Second request will reuse the previous Content-Length set in the global variable
$ curl localhost:8000 -v
* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8000...
* connect to ::1 port 8000 from ::1 port 60462 failed: Connection refused
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.5.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Date: Mon, 02 Dec 2024 16:34:18 GMT
< Server: WSGIServer/0.2 CPython/3.12.3
< Content-type: text/plain; charset=utf-8
< Content-Length: 15
<
* transfer closed with 1 bytes remaining to read
* Closing connection
curl: (18) transfer closed with 1 bytes remaining to read
Hello xxxxxxxx%
The solution for this problem would be to peform a shallow copy of the headers list, ideally in the wsgi.headers.Headers
class so that it does not modify the passed reference but its own copy.
CPython versions tested on:
3.12
Operating systems tested on:
Linux
Linked PRs
- gh-127522: wsgiref: indicate that
start_response
objects should follow a specific protocol #127525 - [3.13] gh-127522: wsgiref: indicate that
start_response
objects should follow a specific protocol (GH-127525) #130504 - [3.12] gh-127522: wsgiref: indicate that
start_response
objects should follow a specific protocol (GH-127525) #130505
Metadata
Metadata
Assignees
Labels
docsDocumentation in the Doc dirDocumentation in the Doc dir
Projects
Status
Todo