Leapcell: The Best of Serverless Web Hosting
Implementing a FastAPI-like Routing Scheme from WSGI
In the realm of Python web development, FastAPI is widely favored by developers for its efficient and concise routing design, as well as its powerful functionality. FastAPI is built based on the ASGI (Asynchronous Server Gateway Interface) protocol, which is different from the traditional WSGI (Web Server Gateway Interface). This article will explore how to start from WSGI to implement a routing scheme similar to FastAPI, while deeply analyzing key concepts such as WSGI and Uvicorn and their interrelationships.
I. Conceptual Analysis of WSGI, ASGI, and Uvicorn
1.1 WSGI: Web Server Gateway Interface
WSGI is a standard interface in Python web development that defines the communication specification between a web server and a Python web application. Its emergence solves the compatibility problem between different web servers and web frameworks, allowing developers to freely choose web servers and frameworks without worrying about their inability to work together.
WSGI abstracts a web application as a callable object that accepts two parameters: a dictionary containing request information (environ
) and a callback function for sending response status codes and header information (start_response
). For example, a simple WSGI application can be written as:
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [b'Hello, World!']
In this example, environ
contains request-related information such as the request method, URL, and HTTP headers; the start_response
function is used to set the response status code and header information; and the return value of the function is the response body.
1.2 ASGI: Asynchronous Server Gateway Interface
With the rise of asynchronous programming in Python, especially the widespread use of the asyncio
library, the traditional WSGI has been unable to cope with asynchronous I/O operations. ASGI came into being. As an extension of WSGI, it not only supports synchronous applications but also can efficiently handle asynchronous requests, meeting the needs of modern web applications for high performance and high concurrency.
ASGI also defines the communication protocol between the server and the application, but its application callable object supports asynchronous methods. A simple ASGI application example is as follows:
async def simple_asgi_app(scope, receive, send):
assert scope['type'] == 'http'
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
(b'content-type', b'text/plain')
]
})
await send({
'type': 'http.response.body',
'body': b'Hello, ASGI!'
})
Here, scope
contains information such as the request type (e.g., http
); receive
is used to receive data sent by the client; and send
is used to send response data to the client, with the entire process supporting asynchronous operations.
1.3 Uvicorn: ASGI Server
Uvicorn is a high-performance ASGI server based on Python that can run ASGI applications and is also compatible with WSGI applications. Uvicorn uses libraries such as uvloop
and httptools
to implement efficient event loops and HTTP parsing, which can fully leverage the asynchronous advantages of ASGI and provide excellent performance.
In actual development, we can use Uvicorn to start ASGI or WSGI applications. For example, to start the above ASGI application:
uvicorn main:app --reload
Here, main
is the name of the Python module containing the application code, app
is the ASGI application object defined in the module, and the --reload
parameter is used to automatically reload the application when the code changes.
II. Implementing a FastAPI-like Routing Scheme from WSGI
2.1 Basic Principles of the Routing System
FastAPI's routing system binds functions to specific URL paths through decorators. When a request matching the path is received, the corresponding processing function is called and a response is returned. The core idea of implementing a similar routing system in WSGI is to find the corresponding processing function based on the request's URL path and call it to generate a response.
We can define a routing table to store the mapping between URL paths and processing functions. When a request arrives, the matching path is found in the routing table, and then the corresponding processing function is called to generate a response.
2.2 Implementing a Simple WSGI Routing System
Below, we will gradually implement a simple WSGI routing system to simulate FastAPI's routing functions.
First, define an empty routing table:
route_table = {}
Next, create a decorator to bind functions to URL paths and add them to the routing table:
def route(path):
def decorator(func):
route_table[path] = func
return func
return decorator
Then, implement a WSGI application to find and call the corresponding processing function based on the request path:
def wsgi_app(environ, start_response):
path = environ.get('PATH_INFO', '/')
if path in route_table:
response_body = route_table[path]()
status = '200 OK'
else:
response_body = [b'404 Not Found']
status = '404 Not Found'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return response_body
Now, we can use the defined routing decorator to define processing functions:
@route('/')
def index():
return [b'Welcome to the index page!']
@route('/about')
def about():
return [b'This is the about page.']
Finally, use Uvicorn to run this WSGI application:
uvicorn main:wsgi_app --reload
Through the above steps, we have implemented a simple WSGI routing system that can return corresponding content according to different URL paths, initially simulating FastAPI's routing functions.
2.3 Improving the Routing System
The above routing system is just a basic version and has many shortcomings. For example, it does not support dynamic routing (such as parameterized paths) or request method handling. Below, we will improve it.
Supporting Dynamic Routing
To support dynamic routing, we can use regular expressions to match paths and extract parameters from paths. Import the re
module and modify the routing table and routing decorator:
import re
route_table = {}
def route(path):
def decorator(func):
route_table[path] = func
return func
return decorator
def dynamic_route(path_pattern):
def decorator(func):
route_table[path_pattern] = func
return func
return decorator
At the same time, modify the WSGI application to handle dynamic routing:
def wsgi_app(environ, start_response):
path = environ.get('PATH_INFO', '/')
for pattern, handler in route_table.items():
if isinstance(pattern, str):
if path == pattern:
response_body = handler()
status = '200 OK'
break
else:
match = re.match(pattern, path)
if match:
args = match.groups()
response_body = handler(*args)
status = '200 OK'
break
else:
response_body = [b'404 Not Found']
status = '404 Not Found'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return response_body
Now, we can define dynamic routing processing functions:
@dynamic_route(r'/user/(\d+)')
def user_detail(user_id):
return [f'User {user_id} detail page.'.encode()]
Supporting Request Methods
To support different request methods (such as GET and POST), we can store the processing functions for different request methods corresponding to each path in the routing table. Modify the routing table and routing decorator as follows:
route_table = {}
def route(path, methods=['GET']):
def decorator(func):
if path not in route_table:
route_table[path] = {}
for method in methods:
route_table[path][method] = func
return func
return decorator
At the same time, modify the WSGI application to call the corresponding processing function based on the request method:
def wsgi_app(environ, start_response):
path = environ.get('PATH_INFO', '/')
method = environ.get('REQUEST_METHOD', 'GET')
if path in route_table and method in route_table[path]:
response_body = route_table[path][method]()
status = '200 OK'
else:
response_body = [b'404 Not Found']
status = '404 Not Found'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return response_body
Now, we can define processing functions that support different request methods:
@route('/login', methods=['GET', 'POST'])
def login():
return [b'Login page.']
Through the above improvements, our WSGI routing system has become more complete, able to support dynamic routing and different request methods, further approaching FastAPI's routing functions.
III. Summary and Outlook
This article starts from the basic concepts of WSGI, introduces relevant knowledge about ASGI and Uvicorn, and simulates FastAPI's routing functions by gradually implementing a simple WSGI routing system. Through this process, we have gained an in-depth understanding of the communication protocol between web servers and applications, as well as the core principles of routing systems.
Although the WSGI routing system we implemented is still far from FastAPI, it provides a clear idea for us to understand the implementation of routing systems. In the future, we can further improve this routing system, such as adding functions for parsing request bodies and serializing response bodies to make it closer to web frameworks used in actual production environments. At the same time, we can also explore how to extend this routing system to the ASGI environment to fully leverage the advantages of asynchronous programming and improve the performance and concurrency processing capabilities of applications.
The above content details the process of implementing a routing scheme using WSGI. If you think certain parts need to be expanded in more depth or want to add new functions, feel free to let me know.
Leapcell: The Best of Serverless Web Hosting
Recommended as the best platform for deploying Python services: Leapcell
🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
🔹 Follow us on Twitter: @LeapcellHQ
Top comments (0)