在后端开发中,我们经常会使用web框架来实现各种应用,比如python中的flask、django等,go语言中的gin等。web框架提供了很多现成的-自制工具,大大加快了开发速度。这一次,我们将实现一个自己的网络框架。当我们在浏览器中打开链接并发出请求时发生了什么?http请求会通过WSGI服务器转发给flask等web框架,flask会处理请求并返回响应。WSGI相当于一个中间人,处理客户端和框架之间的信息交换。那么WSGI到底是什么?WSGIWSGI代表服务器网关接口。仔细想想,要为每个服务器实现相应的处理接口,是一件很麻烦的事情。WSGI规定了统一的应用接口实现。具体来说,WSGI规定应用程序应该实现一个要调用的对象(函数、类、方法或带有__call__的实例),这个对象应该接受两个位置参数:环境变量(如头信息、状态码等)、回调函数(WSGIserverresponsible),用于发送http当状态和标头相等时,该对象需要返回一个可迭代的响应文本。WSGI的实现为了充分理解WSGI,我们定义了一个应用,其参数是environ和回调函数。defapp(environ,start_response):response_body=b"Hello,World!"status="200OK"#发送响应状态和header给gunicorn等WSGI服务器start_response(status,headers=[])returniter([response_body])当使用gunicorn等服务器启动代码时,gunicornapp:app,打开浏览器,可以看到返回的“helloworld”信息。可以看到app函数中的回调函数start_response将响应状态和header发送给了WSGI服务器。web框架实现flask等web框架的核心是实现WSGI规范、路由分发、视图渲染等功能,这样我们就不用自己写相关模块了。以flask为例,使用方法如下:fromflaskimportFlaskapp=Flask(__name__)@app.route("/home")defhello():return"helloworld"if__name__='__main__':app.run()首先定义一个全局的app实例,然后在相应的函数上定义路由装饰器,这样就可以将不同的路由分配给不同的函数进行处理。WSGI实现出于功能上的考虑,我们将application定义为一个类,新建一个api.py文件,先实现WSGI。这里为了方便我们使用webob库,将WSGI处理封装成一个方便的接口,使用pipinstallwebob安装。从webob导入请求,响应类API:def__call__(self,environ,start_response):request=Request(environ)response=Response()response.text="Hello,World!"returnresponse(environ,start_response)API类中定义的`__call__内置方法被实现。很简单,对吧。路由路由是web框架中一个非常重要的功能。它将不同的请求转发给不同的处理器并返回处理结果。例如:对于路由/home,和路由/about,就像flask一样,使用装饰器将它们绑定到不同的函数。#app.pyfromapi.pyimportAPIapp=API()@app.route("/home")defhome(request,response):response.text="HellofromtheHOMEpage"@app.route("/about")defabout(request,response):response.text="HellofromtheABOUTpage"这个装饰器是如何实现的?不同的routes对应不同的handler,应该是存字典吧?这样,当有新路由来的时候,直接route.get(path,None)即可。classAPI:def__init__(self):self.routes={}defroute(self,path):defwrapper(handler):self.routes[path]=handlerreturnhandlerreturnwrapper...如上所述,定义了一个路由字典,然后是路由装饰器方法,因此您可以使用@app.route("/home")。然后您需要检查每个传入的请求并将其转发给不同的处理功能。路由绑定处理程序有问题。有静态路由和动态路由。我应该怎么办?使用parse库解析请求路径和已有路径,获取动态参数。例如:>>>fromparseimportparse>>>result=parse("/people/{name}","/people/shiniao")>>>print(result.named){'name':'shiniao'}除了动态路由,还要考虑装饰器是否可以绑定到类上,比如django。另外,如果请求没有路由,需要返回404。importinspectfromparseimportparsefromwebobimportRequest,ResponseclassAPI(object):def__init__(self):#存储所有路由self.routes={}#WSGI需要的__call__def__call__(self,environ,start_response):request=Request(environ)response=self.handle_request(request)#将响应状态和header交给gunicorn等WSGI服务器#同时返回响应文本returnresponse(environ,start_response)#找到对应的处理对象和参数routedeffind_handler(self,request_path):forpath,handlerinself.routes.items():parse_result=parse(path,request_path)ifparse_resultisnotNone:returnhandler,parse_result.namedreturnNone,None#匹配请求路由并分发到不同的处理函数defhandle_request(self,request):response=Response()handler,kwargs=self.find_handler(request.path)ifhandlerisnotNone:#Ifthehandlerisaclassifinspect.isclass(handler):#获取定义类方法中的ition例如get/posthandler=getattr(handler(),request.method.low(),None)#如果handler为None则不支持:raiseAttributeError("methodnotallowed.",request.method)handler(request,response,**kwargs)else:self.default_response(response)returnresponsedefroute(self,path):#ifthepathalreadyexistsifpathinself.routes:raiseAssertionError("routealreadyexists.")#绑定路由路径和处理函数defwrapper(handler):self.routes[path]=handlerreturnhandlerreturnwrapper#Defaultresponsedefdefault_response(self,response):response.status_code=404response.text="notfound."新建一个app.py文件,然后定义我们的路由处理函数:fromapiimportAPIapp=API()@app.route("/home")defhome(request,response):response.text="hello,ding”@app.route("/people/{name}")defpeople(req,resp,name)resp.text="hello,{}".format(name)然后我们启动gunicorn:gunicornapp:app打开浏览器访问:http://127.0.0.1:8000/people/shiniao你可以看到返回的信息:你好,小鸟。测试访问重复路由是否会抛出异常,新建test_ding.py文件。使用pytest进行测试。从api导入pytestpytest.raises(AssertionError):@api.route("/home")defhome2(req,resp):resp.text="ding"好了,上面就是一个简单的web框架,支持基本的路由转发。动态路由等功能。我一直认为最好的学习方式就是模仿,自己实现不同的轮子,写一个解释器,web框架,小数据库,这些都会对我的技术进步有很大的帮助。还有,新年快乐!
