WSGI start_response object attributes

Having sent a header response to the WSGI.start_response method is there any way I can retrieve the values of what headers have been sent? It would be good to be able to read the headers for the purpose of logging but I can’t seem to find any documentation on what properties and methods (attributes?) the WSGI object has.

All I can get returned is: <bound method BaseHandler.start_response of <wsgiref.simple_server.ServerHandler object at …0>>

Are you talking about “Reading the Request Body”?
Without a concrete example, it’s not easy to help you!

A basic example:

from wsgiref import simple_server

class WSGIController:
    def __init__(self, request, response):
        self.request = request
        self.response = response
        self.response("404 Not Found", [("Content-Type", "text/html; charset=utf-8")])
    def __iter__(self):
        print(self.response) # <- this is where I want to read the headers sent
        yield b"MyHTML"

httpd = simple_server.make_server("", 8000, WSGIController)
httpd.serve_forever()

The below is not an answer per say to your question but this is what I would look at:

  • get_all().
  • The environ parameters.
    Lets hope that someone with more knowledge will be able to help you better than what I can do!
    Also, everything that you would like to do might not be available as this is not to be used in production.

Unfortunately can’t seem to get get_all() to work.

File “C:\Program Files\Python314\Lib\wsgiref\headers.py”, line 98, in get_all
name = self._convert_string_type(name.lower(), name=True)
^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: ‘function’ object has no attribute ‘_convert_string_type’

from wsgiref.simple_server import make_server, demo_app
from wsgiref.headers import Headers

h = Headers()

with make_server('', 8000, demo_app) as httpd:
    print(h.get_all('name'))

The method get_all() will show all values for a specific header matching name.

Thanks, that now runs but unfortunately doesn’t ever return anything other than an empty [dict].

from wsgiref.simple_server import make_server
from wsgiref.util import setup_testing_defaults
from wsgiref.headers import Headers

h = Headers()
h.add_header('Content-type', 'text/plain')

# A relatively simple WSGI application. It's going to print out the
# environment dictionary after being updated by setup_testing_defaults
def simple_app(environ, start_response):
    setup_testing_defaults(environ)

    status = '200 OK'
    headers = [('Content-type', 'text/plain; charset=utf-8')]

    start_response(status, headers)

    ret = [("%s: %s\n" % (key, value)).encode("utf-8") for key, value in environ.items()]
    return ret

with make_server('', 8000, simple_app) as httpd:
    print(httpd.get_request()) # Client request
    print(h.get_all('Content-type')) # Shows the header value(s) added by add_header()
    print("Serving on port 8000...")
    httpd.serve_forever()

If you point your web browser to http localhost 8000, you can see the content of environ. In the terminal, you can see some client request and the output of header Content-type value.
To clarify what get_all('header_name') does, it will show all values added by the method add_header('header_name', 'header_value') from the Headers class.
In short, the above code shows three things:

  • client request
  • environ params
  • Usage of the Headers class
    Even if, we try handlers, I’m not sure that will help you.

Right, I see the problem: get_all() shows headers added by the add_header() method but not those set by the start_response() object.

Still can’t see any way to get access to the headers which are set within start_response().

Should I be using the WSGI start_response() or the Header.add_header() method in a WSGI application?

If you look at the source code for simple_server.py, you can see that the headers are added in environ.

What I would do is to use a callback function, where you declare the desired headers. In that function you can log those headers.
This page should give you some inspiration.

Note that part of the issue here is that you are using a simple server, which does not help.
If you were using, a real httpd server, you would use the Headers class.
For local (simple server) testing, you do not have any other choice than to use start_response() passing the status and the response headers.

The environ.items() method lists (‘CONTENT_TYPE’, ‘text/plain’) even before I set it with start_response().

As far as my understanding goes, this is consistent with the source code!

So are there many other differences between simple_server and a proper install? I was under the impression that WSGIref was a reference implementation and the same WSGI Python server would run the same under both, sounds like that is not the case.

You can expect a test server to make things as simple as possible to be able to focus on development stuff.
If you expose a “wsgi server” to the internet, security is paramount.
Taking Apache and wsgi as an example, you can see that there is not much difference between simple_server.py and doing the same with Apache with regard to wsgi.!
The example is for linux but as you can see the Python code is the same as for simple_server.py.
Personally, I would directly test on the set up that I will use in production!

  • Develop my app
  • Secure my httpd server
  • Retest my app with security enabled

Have now tried stepping through this with debbuger but that doesn’t help much as start_response seems to have ‘Protected Attributes’ which can’t be accessed outside of the class definition itself.

I’m not sure why a debugger would help, the PEP is pretty clear.
Do not spend anymore time on this, this is a red herring.

But surely the response headers are stored somewhere within start_response() are they not? See paragraph from documentation below:

The start_response callable must not actually transmit the response headers. Instead, it must store them for the server or gateway to transmit only after the first iteration of the application return value that yields a non-empty string, or upon the application’s first invocation of the write() callable. In other words, response headers must not be sent until there is actual body data available, or until the application’s returned iterable is exhausted. (The only possible exception to this rule is if the response headers explicitly include a Content-Length of zero.)

While a server does need to store that information, the WSGI protocol does not provide any method to read it. If you want to capture what’s sent by another part of the application, you need to pass a different start_response function to that app, wrapping the start_response you were given. Your wrapper function will then receive the headers and can both log them and pass them on to the original start_response function. In other words, you can implement this using WSGI middleware: code that wraps an application in order to intercept WSGI operations either on the way in, the way out, or both..