用Python写一个简单的Web框架【2】
五、重构
上面的代码虽然奏效,但是在编码风格和灵活性方面有很多问题,下面逐步对其进行重构。
1、正则匹配URL
消除URL硬编码,增加URL调度的灵活性:
Python
51#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
importre##########修改点
classmy_app:
urls=(
("/","index"),
("/hello/(.*)","hello"),
)##########修改点
def__init__(self,environ,start_response):
self.environ=environ
self.start=start_response
def__iter__(self):##########修改点
path=self.environ['PATH_INFO']
method=self.environ['REQUEST_METHOD']
forpattern,nameinself.urls:
m=re.match('^'+pattern+'$',path)
ifm:
# pass the matched groups as arguments to the function
args=m.groups()
funcname=method.upper()+'_'+name
ifhasattr(self,funcname):
func=getattr(self,funcname)
returnfunc(*args)
returnself.notfound()
defGET_index(self):
status='200 OK'
response_headers=[('Content-type','text/plain')]
self.start(status,response_headers)
yield"Welcome!\n"
defGET_hello(self,name):##########修改点
status='200 OK'
response_headers=[('Content-type','text/plain')]
self.start(status,response_headers)
yield"Hello %s!\n"%name
defnotfound(self):
status='404 Not Found'
response_headers=[('Content-type','text/plain')]
self.start(status,response_headers)
yield"Not Found\n"
2、DRY
消除GET_*方法中的重复代码,并且允许它们返回字符串:
Python
61#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
importre
classmy_app:
urls=(
("/","index"),
("/hello/(.*)","hello"),
)
def__init__(self,environ,start_response):##########修改点
self.environ=environ
self.start=start_response
self.status='200 OK'
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:
# pass the matched groups as arguments to the function
args=m.groups()
funcname=method.upper()+'_'+name
ifhasattr(self,funcname):
func=getattr(self,funcname)
returnfunc(*args)
returnself.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"%name
defnotfound(self):##########修改点
self.status='404 Not Found'
self.header('Content-type','text/plain')
return"Not Found\n"
3、抽象出框架
为了将类my_app抽象成一个独立的框架,需要作出以下修改:
剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)
把方法header实现为类方法(classmethod),以方便外部作为功能函数调用
改用 具有__call__方法的实例来实现application
修改后的application.py(最终版本):
Python
54#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
importre
classmy_app:
"""my simple web framework"""
headers=[]
def__init__(self,urls=(),fvars={}):
self._urls=urls
self._fvars=fvars
def__call__(self,environ,start_response):
self._status='200 OK'# 默认状态OK
delself.headers[:]# 清空上一次的headers
result=self._delegate(environ)
start_response(self._status,self.headers)
# 将返回值result(字符串 或者 字符串列表)转换为迭代对象
ifisinstance(result,basestring):
returniter([result])
else:
returniter(result)
def_delegate(self,environ):
path=environ['PATH_INFO']
method=environ['REQUEST_METHOD']
forpattern,nameinself._urls:
m=re.match('^'+pattern+'$',path)
ifm:
# pass the matched groups as arguments to the function
args=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='404 Not Found'
self.header('Content-type','text/plain')
return"Not Found\n"
@classmethod
defheader(cls,name,value):
cls.headers.append((name,value))
对应修改后的code.py(最终版本):
Python
33#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""code.py"""
fromapplicationimportmy_app
urls=(
("/","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"%name
if__name__=='__main__':
fromwsgiref.simple_serverimportmake_server
httpd=make_server('',8086,wsgiapp)
sa=httpd.socket.getsockname()
print'http://{0}:{1}/'.format(*sa)
# Respond to requests until process is killed
httpd.serve_forever()
当然,您还可以在code.py中配置更多的URL映射,并实现相应的类来对请求作出响应。我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是301,还有056,最后是051,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种分享。如果你看了觉得还可以的麻烦给我点个赞谢谢