I’ve been writing Python Web for a few years, but I still don’t know what WSGI is. That’s fine, because as a developer you rarely need to know what WSGi is to build a website.

But if you want to write your own Web framework for fun, you have to get to know WSGi.

As a reminder, when we do Web development in Python, we tend to do it based on a Web framework, django or flask or something else. After services are developed, they must be deployed to a server to provide external access.

If you look it up on the Internet, they’ll tell you you need gunicorn or UWSGI. What about Gunicorn and UWSGI?

Look at this picture. I got it from the Internet

The role of uWSGi or Gunicorn in this case is the role of the Web server, which is the software-level server that processes HTTP requests from the browser and returns the results to the front end. The main task of the Web framework is to process the business logic and generate the results to the Web server, which then returns them to the browser.

Communication between a Web framework and a Web server needs to follow a set of specifications, and that specification is WSGI.

Why do we need to make such a set of norms? Specification is to unify standards, convenient for everyone to use

Imagine that the charging interfaces of our mobile phones are all Type-C. Type-c is a kind of specification. Mobile phone manufacturers produce mobile phones according to this specification, and charger manufacturers produce chargers according to Type-C, so mobile phones of different manufacturers can be used with chargers of different manufacturers. Apple created its own set of specifications, which ultimately led to Android chargers not being able to charge apple.

! [

] (p9-juejin.byteimg.com/tos-cn-i-k3…).

So how do you write a WSGI compliant application (framework) and server?

As shown above, the Web server is on the left and the Web framework, or application, is on the right.

The application

WSGI states that an application must be a callable object (a callable can be a function, a class, or an instance object that implements __call__), and that it must take two arguments, and that the object’s return value must be an iterable.

We can write an example of the simplest application

HELLO_WORLD = b"Hello world! \n"

def application(environ, start_response) :
    status = '200 OK'
    response_headers = [('Content-type'.'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]
Copy the code

Application is a function, which must be a callable object, and receives two parameters: environ and start_Response

  • Environ is a dictionary that stores all HTTP request-related content, such as headers, request parameters, and more
  • Start_response is a function passed by the WSGI Server to pass the Response header, status code, to the Server.

The start_response function is called to pass the response header and status code to the server, and the response body is returned to the server by the application function. A complete HTTP response is provided by these two functions.

But any Web framework that implements WSGi will have such a callable object

The server

All the WSGI server side does is receive an HTTP request each time, build an environ object, call an Application object, and return the HTTP Response to the browser.

Here is a complete wsGI Server code

import socket
import sys
from io import StringIO


class WSGIServer(object) :
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    request_queue_size = 1

    def __init__(self, server_address) :
        # Create a listening socket
        self.listen_socket = listen_socket = socket.socket(
            self.address_family,
            self.socket_type
        )
        # Allow to reuse the same address
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # Bind
        listen_socket.bind(server_address)
        # Activate
        listen_socket.listen(self.request_queue_size)
        # Get server host name and port
        host, port = self.listen_socket.getsockname()[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port
        # Return headers set by Web framework/Web application
        self.headers_set = []

    def set_app(self, application) :
        self.application = application

    def serve_forever(self) :
        listen_socket = self.listen_socket
        while True:
            # New client connection
            self.client_connection, client_address = listen_socket.accept()
            # Handle one request and close the client connection. Then
            # loop over to wait for another client connection
            self.handle_one_request()

    def handle_one_request(self) :
        self.request_data = request_data = self.client_connection.recv(1024)
        # Print formatted request data a la 'curl -v'
        print(' '.join(
            '< {line}\n'.format(line=line)
            for line in request_data.splitlines()
        ))
        self.parse_request(request_data)
        # Construct environment dictionary using request data
        env = self.get_environ()
        # It's time to call our application callable and get
        # back a result that will become HTTP response body
        result = self.application(env, self.start_response)
        # Construct a response and send it back to the client
        self.finish_response(result)

    def parse_request(self, text) :
        request_line = text.splitlines()[0]
        request_line = request_line.rstrip('\r\n')
        # Break down the request line into components
        (self.request_method,  # GET
         self.path,  # /hello
         self.request_version  # HTTP / 1.1
         ) = request_line.split()

    def get_environ(self) :
        env = {}
        # The following code snippet does not follow PEP8 conventions
        # but it's formatted the way it is for demonstration purposes
        # to emphasize the required variables and their values
        #
        # Required WSGI variables
        env['wsgi.version'] = (1.0)
        env['wsgi.url_scheme'] = 'http'
        env['wsgi.input'] = StringIO.StringIO(self.request_data)
        env['wsgi.errors'] = sys.stderr
        env['wsgi.multithread'] = False
        env['wsgi.multiprocess'] = False
        env['wsgi.run_once'] = False
        # Required CGI variables
        env['REQUEST_METHOD'] = self.request_method  # GET
        env['PATH_INFO'] = self.path  # /hello
        env['SERVER_NAME'] = self.server_name  # localhost
        env['SERVER_PORT'] = str(self.server_port)  # 8888
        return env

    def start_response(self, status, response_headers, exc_info=None) :
        # Add necessary server headers
        server_headers = [
            ('Date'.'Tue, 31 Mar 2015 12:54:48 GMT'),
            ('Server'.'WSGIServer 0.2'),
        ]
        self.headers_set = [status, response_headers + server_headers]
        # To adhere to WSGI specification the start_response must return
        # a 'write' callable. We simplicity's sake we'll ignore that detail
        # for now.
        # return self.finish_response

    def finish_response(self, result) :
        try:
            status, response_headers = self.headers_set
            response = 'HTTP / 1.1 {status} \ r \ n'.format(status=status)
            for header in response_headers:
                response += '{0}: {1}\r\n'.format(*header)
            response += '\r\n'
            for data in result:
                response += data
            # Print formatted response data a la 'curl -v'
            print(' '.join(
                '> {line}\n'.format(line=line)
                for line in response.splitlines()
            ))
            self.client_connection.sendall(response)
        finally:
            self.client_connection.close()


SERVER_ADDRESS = (HOST, PORT) = 'localhost'.8080


def make_server(server_address, application) :
    server = WSGIServer(server_address)
    server.set_app(application)
    return server


if __name__ == '__main__':
    httpd = make_server(SERVER_ADDRESS, application)
    print('WSGIServer: Serving HTTP on port {port} ... \n'.format(port=PORT))
    httpd.serve_forever()

Copy the code

Of course, if you’re just writing a server for your development environment, you don’t need to bother building your own wheels, because Python’s built-in modules provide wsGi Server functionality.

from wsgiref.simple_server import make_server
srv = make_server('localhost'.8080, application)
srv.serve_forever()
Copy the code

With just three lines of code to provide the WSGi server, isn’t it super convenient? Finally, let’s test the effect of a browser making a request

That’s wsGI in general, and you can familiarize yourself with PEP333

This article is published on the public account: Zen of Python, welcome to follow