Skip to content

Document start_response parameters in wsgiref functions #127522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
gagath opened this issue Dec 2, 2024 · 2 comments
Closed

Document start_response parameters in wsgiref functions #127522

gagath opened this issue Dec 2, 2024 · 2 comments
Labels
docs Documentation in the Doc dir

Comments

@gagath
Copy link

gagath commented Dec 2, 2024

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

@gagath gagath added the type-bug An unexpected behavior, bug, or error label Dec 2, 2024
@gagath
Copy link
Author

gagath commented Dec 2, 2024

After looking into PEP 3333, this is expected behavior:

The response_headers argument is a list of (header_name, header_value) tuples. It must be a Python list; i.e. type(response_headers) is ListType, and the server may change its contents in any way it desires. Each header_name must be a valid HTTP header field-name (as defined by RFC 2616, Section 4.2), without a trailing colon or other punctuation.

However it is not that clear in the documentation of the module itself. Instead of trying to fix this and contradict the PEP, I think a documentation update will be the best solution to this issue ("modify" word is not listed in the wsgiref documentation page).

gagath added a commit to gagath/prometheus-hdparm-exporter that referenced this issue Dec 2, 2024
@picnixz picnixz added stdlib Python modules in the Lib dir docs Documentation in the Doc dir and removed type-bug An unexpected behavior, bug, or error labels Dec 2, 2024
@picnixz picnixz removed the stdlib Python modules in the Lib dir label Dec 2, 2024
@picnixz
Copy link
Member

picnixz commented Dec 2, 2024

I'm not sure it requires a doc update. start_response, in the example, is a parameter whose type is (albeit not explicitly stated) StartResponse which itself is linked to the PEP-3333.

Well.. strictly speaking, it does not say that the start_response parameter is of this specific type so we could add this note in the docs.

@picnixz picnixz changed the title wsgiref.handlers.BaseHandler does not copy passed headers; results in wrong, cached Content-Length Document start_response parameters in wsgiref functions Dec 2, 2024
encukou pushed a commit that referenced this issue Feb 24, 2025
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Feb 24, 2025
…ld follow a specific protocol (pythonGH-127525)

(cherry picked from commit 39ba4b6)

Co-authored-by: Bénédikt Tran <[email protected]>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Feb 24, 2025
…ld follow a specific protocol (pythonGH-127525)

(cherry picked from commit 39ba4b6)

Co-authored-by: Bénédikt Tran <[email protected]>
encukou pushed a commit that referenced this issue Feb 24, 2025
…uld follow a specific protocol (GH-127525) (GH-130504)

(cherry picked from commit 39ba4b6)

Co-authored-by: Bénédikt Tran <[email protected]>
encukou pushed a commit that referenced this issue Feb 24, 2025
…uld follow a specific protocol (GH-127525) (GH-130505)

(cherry picked from commit 39ba4b6)

Co-authored-by: Bénédikt Tran <[email protected]>
@encukou encukou closed this as completed Feb 24, 2025
seehwan pushed a commit to seehwan/cpython that referenced this issue Apr 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir
Projects
Status: Todo
Development

No branches or pull requests

3 participants