当前位置: 首页 > 科技观察

用Python写一个简单的web框架

时间:2023-03-20 18:26:15 科技观察

1.概述2.从demo_app开始3.在WSGI中的应用4.区分URL5.重构1.正则匹配URL2.DRY3.抽象框架6.参考文献11.概述在Python中,WSGI(WebServerGatewayInterface)定义了Web服务器和Web应用程序(或Web框架)之间的标准接口。在WSGI的规范下,各种Web服务器和Web框架可以很好的交互。由于WSGI的存在,用Python写一个简单的web框架也非常容易。然而,与许多其他强大的软件一样,实现一个功能丰富、健壮且高效的Web框架并不容易;如果你打算这么做,大概使用现成的web框架(比如Django、Tornado、web.是更合适的选择。本文尝试写一个类似于web.py的web框架。好吧,我承认我夸大了:首先,web.py并不简单;其次,本文只关注URL调度(URLdispatch)部分的实现。构建一个非常简单(简单)的Web应用程序:#!/usr/bin/envpython#-*-coding:utf-8-*-fromwsgiref.simple_serverimportmake_server,demo_apphttpd=make_server('',8086,demo_app)sa=httpd.socket.getsockname()print'http://{0}:{1}/'.format(*sa)#Respondtorequestsuntilprocessiskilledhttpd.serve_forever()运行脚本:$pythoncode.pyhttp://0.0.0.0:8086/打开在浏览器中输入http://0.0.0.0:8086/,可以看到:一行“Helloworld!”和很多环境变量值。3、WSGI中的ApplicationWSGI规定:application是一个可调用对象(callableobject),它接受environ和start_response两个参数,返回一个字符串迭代对象。其中,可调用对象包括函数、方法、类或带有__call__方法的实例;environ是一个字典对象,包括CGI风格的环境变量(CGI-styleenvironmentvariables)和WSGI必需的变量(WSGI-requiredvariables);start_response是一个可调用对象,它有两个常规参数(status,response_headers)和一个默认参数(exc_info);字符串迭代器对象可以是字符串列表、生成器函数或具有__iter__方法的可迭代实例。有关更多详细信息,请参阅规格详细信息。应用程序/框架方面给出了典型的应用程序实现:#!/usr/bin/envpython#-*-coding:utf-8-*-"""application.py"""defsimple_app(environ,start_response):"""Simplestpossibleapplicationobject"""status='200OK'response_headers=[('Content-type','text/plain')]start_response(status,response_headers)return['Helloworld!\n']现在将其替换为simple_appdemo_app:#!/usr/bin/envpython#-*-coding:utf-8-*-"""code.py"""fromwsgiref.simple_serverimportmake_serverfromapplicationimportsimple_appasappif__name__=='__main__':httpd=make_server('',8086,app)sa=httpd.socket.getsockname()print'http://{0}:{1}/'.format(*sa)#Respondtorequestsuntilprocessiskilledhttpd.serve_forever()运行脚本code.py后访问http://0.0。0.0:8086/,可以看到熟悉的一句话:Helloworld!4.区分URL一段时间后,你会发现无论你如何改变URL中的路径部分,得到的响应都是一样的。因为simple_app只识别host+port部分。为了区分URL中的路径部分,需要修改application.py的实现。首先,改用类来实际应用:#!/usr/bin/envpython#-*-coding:utf-8-*-"""application.py"""classmy_app:def__init__(self,environ,start_response):self.environ=environself.start=start_responsedef__iter__(self):status='200OK'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"Helloworld!\n"然后,增加对URL中路径部分的分区处理:#!/usr/bin/envpython#-*-coding:utf-8-*-"""application.py"""classmy_app:def__init__(self,environ,start_response):self.environ=environself.start=start_responsedef__iter__(self):path=self.environ['PATH_INFO']ifpath==”/”:returnsself.GET_index()elifpath==”/hello”:returnsself.GET_hello()else:returnsself.notfound()defGET_index(self):status='200OK'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"Welcome!\n"defGET_hello(self):status='200OK'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"Helloworld!\n"defnotfound(self):status='404NotFound'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"NotFound\n"在code.py中修改fromapplicationimportsimple_appasapp,将simple_app替换成my_app,体验一下效果5.重构上面代码可以,但是编码风格和灵活性方面存在很多问题,会重构下面一步一步。1、正则匹配URL免去URL硬编码,增加URL调度的灵活性:#!/usr/bin/envpython#-*-coding:utf-8-*-"""application.py"""importre##########修改classmy_app:urls=(("/","index"),("/hello/(.*)","hello"),)##########修改点def__init__(self,environ,start_response):self.environ=environself.start=start_responsedef__iter__(self):##########修改点path=self.environ['PATH_INFO']method=self.environ['REQUEST_METHOD']forpattern,nameinself.urls:m=re.match('^'+pattern+'$',path)ifm:#passthematchedgroupsasargumentstothefunctionargs=m.groups()funcname=method.upper()+'_'+nameifhasattr(self,funcname):func=getattr(self,funcname)returnfunc(*args)returnself.notfound()defGET_index(self):status='200OK'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"Welcome!\n"defGET_hello(self,name):##########修改点status='200OK'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"Hello%s!\n"%namedefnotfound(self):status='404NotFound'response_headers=[('Content-type','text/plain')]self.start(status,response_headers)yield"NotFound\n“2。DRY消除了GET_*方法中的重复代码并允许它们返回字符串:#!/usr/bin/envpython#-*-coding:utf-8-*-"""application.py"""importreclassmy_app:urls=(("/","index"),("/hello/(.*)","hello"),)def__init__(self,environ,start_response):##########修改点self.environ=environself.start=start_responseself.status='200OK'self._headers=[]def__iter__(self):##########修改点result=self.delegate()self.start(self.status,self._headers)#将返回值result(字符串或字符串列表)转换为迭代对象ifisinstance(result,basestring):returniter([result])else:returniter(result)defdelegate(self):##########修改点path=self.environ['PATH_INFO']method=self.environ['REQUEST_METHOD']forpattern,nameinself.urls:m=re.match('^'+pattern+'$',path)ifm:#passthematchedgroupsasargumentstothefunctionargs=m.groups()funcname=method.upper()+'_'+nameifhasattr(self,funcname):func=getattr(self,funcname)returnfunc(*args)returnsself.notfound()defheader(self,name,value):##########修改点self._headers.append((name,value))defGET_index(self):##########修改点self.header('Content-type','text/plain')return"Welcome!\n"defGET_hello(self,name):##########修改点self.header('Content-type','text/plain')return"Hello%s!\n"%namedefnotfound(self):##########修改点self.status='404NotFound'self.header('Content-type','text/plain')return"NotFound\n“3。抽象框架为了将类my_app抽象成一个独立的框架,需要做如下修改:剥离出具体的处理细节:urls配置和GET_*方法(改为在多个类中实现对应的GET方法)实现方法头作为类方法(classmethod)方便外部调用作为功能函数并使用带有__call__方法的实例来实现应用程序修改后的application.py(最终版):#!/usr/bin/envpython#-*-coding:utf-8-*-"""application.py"""importreclassmy_app:"""mysimplewebframework"""headers=[]def__init__(self,urls=(),fvars={}):self._urls=urlsself._fvars=fvarsdef__call__(self,environ,start_response):self._status='200OK'#默认状态OKdelself.headers[:]#清空最后一个headersresult=self._delegate(environ)start_response(self._status,self.headers)#will返回值result(字符串或字符串列表)被转换为迭代对象method=environ['REQUEST_METHOD']forpattern,nameinself._urls:m=re.match('^'+pattern+'$',path)ifm:#passthematchedgroupsasargumentstothefunctionargs=m.groups()funcname=method.upper()#方法名大写(如GET、POST)klass=self._fvars.get(name)#根据字符串名查找类对象ifhasattr(klass,funcname):func=getattr(klass,funcname)returnfunc(klass(),*args)returnself._notfound()def_notfound(self):self._status='404NotFound'self.header('Content-type','text/plain')返回"NotFound\n"@classmethoddefheader(cls,name,价值):cls。headers.append((name,value))对应修改后的code.py(最终版):#!/usr/bin/envpython#-*-coding:utf-8-*-"""code.py""“来自应用程序licationimportmy_appurls=(("/","index"),("/hello/(.*)","hello"),)wsgiapp=my_app(urls,globals())classindex:defGET(self):my_app.header('Content-type','text/plain')return"Welcome!\n"classhello:defGET(self,name):my_app.header('Content-type','text/plain')return"Hello%s!\n"%nameif__name__=='__main__':fromwsgiref.simple_serverimportmake_serverhttpd=make_server('',8086,wsgiapp)sa=httpd.socket.getsockname()print'http://{0}:{1}/'。format(*sa)#Respondtorequestsuntilprocessiskilledhttpd.serve_forever()当然你也可以在code.py中配置更多的URL映射,实现相应的类来响应请求六.参考资料本文主要参考了HowtowriteawebframeworkinPython(作者anandology是web.py代码的两个维护者之一,另一个是AaronSwartz,名噪一时却英年早逝),做了一些调整和在此基础上进行修改,并混合了自己的一些想法。如果你还觉得还有很多话要说,为什么这么多Pythonweb框架?也是一篇好文章,也许它会让你对Python中的Web框架的敬畏消失:-)