WSGI is a term often referred to in Python Web development, and is defined in Wikipedia as follows.

The Python Web Server Gateway Interface (abbreviated WSGI) is a simple and generic interface between a web server and a web application or framework defined for the Python language. Since WSGI was developed, similar interfaces have appeared in many other languages.

As defined, WSGI is not a server, not an API, not a Python module, but an interface specification that specifies the interaction between a server and a client.

The goal of WSGI is to provide a common API standard between the Web server and Web framework layers, reducing interoperability and forming a uniform invocation. According to this definition, a Web server that satisfies WSGI will pass two fixed parameters into the WSGI APP: a dictionary of environment variables and a callable object that initializes the Response. WSGI APP will process the request and return an iterable object.

WSGI APP

By definition, we can implement a very simple App that satisfies WSGI.

1
2
3
4
5
def demo_wsgi_app(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/plain')]
    start_response(status, headers)
    yield "Hello World!"

As you can see, the App initializes the request with start_response and returns the body via yield. In addition to yield, it is possible to return an iterable object directly.

A simple WSGI APP is already included in the standard library wsgiref, which can be found in wsgiref.simple_server, and as you can see, this does the same thing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def demo_app(environ,start_response):
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k,v in h:
        print(k,'=',repr(v), file=stdout)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]

Run the app as follows.

wsgi app

In Django, you can find get_wsgi_application in wsgi.py under the default app, and Django creates and returns a WSGIHandle through this method, which is, in essence, still a WSGI APP, see its __call__ method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = list(response.items())
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', c.output(header='')))
        start_response(status, response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response

WSGI Server

You can basically guess what the WSGI server does from the way the WSGI APP is written, so you can try to implement a rudimentary WSGI server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def run_wsgi_app(app, environ):
    from io import StringIO
    body = StringIO()

    def start_response(status, headers):
        body.write('Status: {}\r\n'.format(status))
        for header in headers:
            body.write("{}: {}".format(*header))
        return body.write

    iterable = app(environ, start_response)
    try:
        if not body.getvalue():
            raise RuntimeError("No exec start_response")
        body.write("\r\n{}\r\n".format('\r\n'.join(line for line in iterable)))
    finally:
        if hasattr(iterable, "close") and callable(iterable.close):
            iterable.close()
    # θΏ™ι‡ŒηžŽζ‰―
    return body.getvalue()

For real (usable) WSGI servers, such as Gunicorn, there is a class method called handle_request implemented in different workers (in the gunicorn.worker module), which is the method that calls the WSGI APP and completes the Response assembly. worker implementations differ slightly, but the code is relatively common.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
respiter = self.wsgi(environ, resp.start_response)
try:
    if isinstance(respiter, environ['wsgi.file_wrapper']):
        resp.write_file(respiter)
    else:
        for item in respiter:
            resp.write(item)
    resp.close()
    request_time = datetime.now() - request_start
    self.log.access(resp, req, environ, request_time)
finally:
    if hasattr(respiter, "close"):
        respiter.close()

This is the code that calls the WSGI APP and writes the Body to the resp through a loop.

Middleware

Because of the way WSGI is defined, multiple WSGI APPs can be written to nest and handle different logic, e.g.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def first_wsgi_app(environ, start_response):
    import logging
    logging.info("new request")
    rsp = second_wsgi_app(environ, start_response)
    logging.info("finish request")
    return rsp


def second_wsgi_app(environ, start_response):
    if environ.get("HTTP_ROLE") == "ADMIN":
        return third_wsgi_app(environ, start_response)
    status = '200 OK'
    headers = [('Content-type', 'text/plain')]
    start_response(status, headers)
    yield "Hello User!"


def third_wsgi_app(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/plain')]
    start_response(status, headers)
    yield "Hello Admin!"

At this point we pass the first WSGI APP first_wsgi_app to the Server. during execution, first_wsgi_app does the logging, second_wsgi_app does the authentication, and third_wsgi_app does the actual processing of the request.

This onion structure of the App is affectionately known as Russian nesting doll by Maslen.