Python asynchronous programming|ASGI and Django (with source code)

image

Asynchronous Server Gateway Interface (ASGI) adheres to the WSGI unified gateway interface principle, provides a standard interface between asynchronous services, frameworks and applications, and is compatible with WSGI.

01, ASGI

ASGI is a new standard redesigned based on the idea of a unified interface. You may have doubts, why not directly upgrade WSGI and create a new standard?

WSGI is a gateway interface based on HTTP short connections. A call request must be processed and returned as soon as possible. This mode is not suitable for long connections, such as the technology SSE (Server-Sent Events) and WebSocket in the new HTML 5 standard, WSGI And the traditional blocking IO programming model is not good at handling such requests. Even if WSGI is forcibly upgraded to support asynchronous IO, it is meaningless if the supporting technology (such as the Apache server) does not provide corresponding support. Now that the Python asynchronous IO programming model has gone ahead, it is time to formulate a new standard ASGI to support and use the latest technology in the most elegant way.

The ASGI interface is an asynchronous function that requires three parameters to be passed in, namely scope, receive and send. The sample code is as follows:

async def app(scope, receive, send):
    pass

The scope is a dictionary (dict), including connection-related information. Figure 1 shows a breakpoint debugging screenshot of the information included in the scope in a request.

receive is an asynchronous function used to read the information sent by the front end. The structure of a read information is as follows:

{
    'type': 'http.request',
    'body': b"",
    'more_body': False
}

The information includes 3 fields, which are type (type), content (body) and whether there is more content (more_body). The type can be used to determine the type of the information, such as HTTP request, life cycle, For WebSocket requests, etc., body is the data included in the information. This data is in binary format. more_body indicates whether the current data has been sent. If it is sent, the value of more_body is False, so that it can be used to transfer large files in segments.

image

Figure 1 screenshot of breakpoint

send is also an asynchronous function, which is used to send information to the front end, and the structure of the information sent is similar to that received from the front end. A sample code for sending simple information to the front end is as follows:

async def app(scope, receive, send):
#Send HTTP protocol headers to the front end, including HTTP status and protocol headers
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/html'],
        ]
  })
#Send data to the front end, if the data is huge, it can also be sent in segments
    await send({
        'type': 'http.response.body',
        'body': b"Hello World",
        'more_body': False
    })

In addition to regular data communication, ASGI also specifies a lifecycle management interface that can be used to listen for server startup and shutdown. In actual development work, this is very useful, and can be used to perform initialization and finishing work. The application code for life cycle management is as follows:

async def app(scope, receive, send):
    request_type = scope['type']
    if request_type == 'lifespan':
        while True:
            message = await receive()
            if message['type'] == 'lifespan.startup':
                await send({'type':'lifespan.startup.complete'})
            elif message['type'] == 'lifespan. shutdown':
                await send({'type':'lifespan.shutdown.complete'})
                break

When the type of scpoe[‘type’] is lifespan, it means that the type of the request is a lifespan. This request will occur at the beginning of the server startup, and then the management of the lifespan should be implemented.

Continuously monitor the change of the request status through an infinite loop, execute the initialization operation when reading message[t’ ype’] is lifespan.startup, and send lifespan.startup.complete information to the front end (protocol layer) after the operation is completed, The protocol layer can be understood as the server has been started and can accept browser requests normally.

When you read that message[‘type’] is lifespan.shutdown, it means that the service is going to be shut down, probably because the server administrator has executed the shutdown command, so you need to perform finishing work here, such as releasing the corresponding resources. Send the lifespan.shutdown.complete message to the protocol layer after finishing, indicating that the protocol layer can safely shut down the server at this time.

A complete ASGI-based Hello World sample code is as follows:

async def app(scope, receive, send):
    request_type = scope['type']
    if request_type == 'http':
        await send({
            'type': 'http.response.start',
            'status': 200,
            'headers': [
                [b'content-type', b'text/html'],
            ]
        })
        await send({
            'type': 'http.response.body',
            'body': b"Hello World",
            'more_body': False
        })
    elif request_type == 'lifespan':
        while True:
            message = await receive()
            if message['type'] == 'lifespan.startup':
                await send({'type':'lifespan.startup.complete'})
            elif message['type'] == 'lifespan. shutdown':
                await send({'type':'lifespan.shutdown.complete'})
                break
    else:
        raise NotImplementedError()

02, Uvicorn

Uvicorn is a protocol layer implementation of ASGI, a lightweight ASGI server, based on uvloop and httptools, and runs extremely fast.

uvloop is an efficient asynchronous IO-based event loop framework, and the underlying implementation is carried by libuv. libuv is a high-concurrency asynchronous IO library developed in C language. It was developed by the author of Node.js and implemented as the underlying IO library of Node.js. Now it has developed quite maturely and stably.

To use Uvicorn, you need to install the dependency through the command pip install uvicorn first. The project structure is shown in Figure 2.

Next, enter uvicorn asgi:app in the terminal to start the server, the effect is shown in Figure 3

image

Figure 2 ASGI project file structure

image

Figure 3 Start the ASGI server

After the server starts, you can use a browser to visit the site through http://127.0.0.1:8000, and the result is shown in Figure 4.

In order to provide users with more secure services, modern websites need to support HTTPS. Uvicorn also provides support for HTTPS, which is quite convenient to use.

First prepare the HTTPS certificate file, as shown in Figure 5.

image

Figure 4 page access results

image

Figure 5 The directory where the certificate file is located

Next, start the server by command uvicorn–ssl-certfile ./ssl/cert.pem–ssl-keyfile ./ssl/cert.key asgi:app, as shown in Figure 6.

image

Figure 6 Start the server with HTTPS

The content of the corresponding Dockerfile for containerization is as follows:

 FROM python:3-slim

RUN pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple uvicorn

The content of docker-compose.yml corresponding to containerization is as follows:

services:
  web:
    build: .
    restart: always
    tty: true
    ports:
      - "8000:8000"
    volumes:
      - ".:/opt/"
    working_dir: "/opt/"
    command: uvicorn --host 0.0.0.0 asgi:app

03. Source code

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledgePython entry skill treeWeb application development Django327005 is studying systematically