零基础小白必看篇:从0到1构建Python Web框架
- 格式:docx
- 大小:26.21 KB
- 文档页数:14
造轮子是最好的一种学习方式,本文尝试从0开始造个Python Web框架的轮子,我称它为ToyWebF。
本文操作环境为:MacOS,文中涉及的命令,请根据自己的系统进行替换。
ToyWebF的简单特性:1.支持多种不同形式的路由注册方式2.支持静态HTML、CSS、JavaScript3.支持自定义错误4.支持中间件下面我们来实现这些特性。
最简单的web服务首先,我们需要安装gunicorn,回忆一下Flask框架,该框架有内置的Web服务器,但不稳定,所以上线时通常会替换成uWSGI或gunicorn,这里不搞这个内置Web服务,直接使用gunicorn。
这里多说一句,小编是一名python开发工程师,这里有我自己整理的一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。
想要这些资料的可以关注小编,并在后台私信小编即可领取。
我们创建新的目录与Python虚拟环境,在该虚拟环境中安装gunicornmkdir ToyWebFpython3 -m venv venv # 创建虚拟环境source venv/bin/activate #激活虚拟环境pip install gunicorn复制代码在啥都没有的情况下,构建最简单的Web服务,在ToyWebF目录下,创建app.py与api.py文件,写入下面代码。
api.py 文件class API:def call(self, environ, start_response):response_body = b"Hello, World!"status = "200 OK"start_response(status, headers=[])return iter([response_body])app.py 文件from api import APIapp = API()复制代码运行gunicorn app:app访问http://127.0.0.1:8000,可以看见Hello, World!,但现在请求体中的参数在environ变量中,难以解析,我们返回的response也是bytes形式。
我们可以使用webob库,将environ中的数据转为Request对象,将需要返回的数据转为Response对象,处理起来更加直观方便,直接通过pip安装一下。
pip install webob复制代码然后修改一下API类的__call__方法,代码如下。
from webob import Request, Responseclass API(object):def wsgi_app(self, environ, start_response):"""通过webob 将请求的环境信息转为request对象"""request = Request(environ)response = self.handle_request(request)return response(environ, start_response)def call(self, environ, start_response):self.wsgi_app(environ, start_response)复制代码上述代码中,通过webob库的Request类将environ对象(请求的环境信息)转为容易处理的request,随后调用handle_request方法对request进行处理,处理的结果,通过response对象返回。
handle_request方法在ToyWebF中非常重要,它会匹配出某个路由对应的处理方法,然后调用该方法处理请求并将处理的结果返回,在解析handle_request前,需要先讨论路由注册实现,代码如下。
class API(object):def init(self):# url路由self.routes = {}def route(self, path):# 添加路由的装饰器def wrapper(handler):self.add_route(path, handler)return handlerreturn wrapperdef add_route(self, path, handler):# 相同路径不可重复添加assert path not in self.routes, "Such route already exists"self.routes[path] = handler复制代码其实就是将路由和方法存到self.routes字典中,可以通过route装饰器的形式将路由和方法关联,也可以通过add_route方法关联,在app.py中使用一下。
app = API()通过装饰器关联路由和方法@app.route("/home")def home(request, response):response.text = "This is Home"路由中可以有变量,对应的方法也需要有对应的参数@app.route("/hello/{name}")def hello(requst, response, name):response.text = f"Hello, {name}"可以装饰类@app.route("/book")class BooksResource(object):def get(self, req, resp):resp.text = "Books Page"def handler1(req, resp):resp.text = "handler1"可以直接通过add_route方法添加app.add_route("/handler1", handler1)复制代码因为url中可以存在变量,如@app.route("/hello/{name}"),所以在匹配时,需要进行解析,可以使用正则匹配的方式进行匹配,parse这个第三方库已经帮我们实现了相应的正则匹配逻辑,pip安装使用一下则可。
pip install parseIn [1]: from parse import parse匹配In [2]: res = parse("/hello/{name}", "/hello/二两")In [3]: dOut[3]: {'name': '二两'}复制代码这里定义find_handler方法来实现对self.routes的遍历。
class API(object):def find_handler(self, request_path):# 遍历路由for path, handler in self.routes.items():# 正则匹配路由parse_result = parse(path, request_path)if parse_result is not None:# 返回路由对应的方法和路由本身return handler, parse_dreturn None, None复制代码了解了路由与方法关联的原理后,就可以实现handle_request方法,该方法主要的路径就是根据路由调度对应的方法,代码如下。
import inspectclass API(object):def handle_request(self, request):"""请求调度"""response = Response()handler, kwargs = self.find_handler(request.path)try:if handler is not None:if inspect.isclass(handler): # 如果是类,则获取其中的方法handler = getattr(handler(), request.method.lower(), None)if handler is None: # 类中该方法不存在,则该类不支持该请求类型raise AttributeError("Method now allowed", request.method)handler(request, response, **kwargs)else:# 返回默认错误self.defalut_response(response)except Exception as e:raise ereturn response复制代码在该方法中,首先实例化webob库的Response对象,然后通过self.find_handler方法获取此次请求路由对应的方法和对应的参数,比如。
@app.route("/hello/{name}")def hello(requst, response, name):response.text = f"Hello, {name}"复制代码它将返回hello方法对象和name参数,如果是/hello/二两,那么name就是二两。
因为route装饰器可能装饰器的类对象,比如。
可以装饰类@app.route("/book")class BooksResource(object):def get(self, req, resp):resp.text = "Books Page"复制代码此时self.find_handler方法返回的hanler就是个类,但我们希望调用的是类中的get、post、delete等方法,所以需要一个简单的判断逻辑,通过inspect.isclass方法判断handler如果是类对象,那么就通过getattr方法获取类对象实例的中对应的请求方法。
获取请求方法,request.method.lower() 可为get、post、deletehandler = getattr(handler(), request.method.lower(), None)复制代码如果类对象中没有该方法属性,则抛出该请求类型不被允许的错误,如果不是类对象或类对象中存在该方法属性,则直接调用则可。
此外,如果方法的路由并没有注册到self.routes中,即404的情况,定义了defalut_response方法返回其中内容,代码如下。