I. Overview
Second, start from demo_app
Three, WSGI of application
Fourth, the distinction URL
Fifth, reconstruction
1, the regular matching URL
2、DRY
3, abstract frame
Sixth, reference
I. Overview
In Python, WSGI (Web Server Gateway Interface) defines a Web server and Web application (Web or frame) between the standard interface. In WSGI specification, a variety of Web servers and Web frameworks can be very good interaction.
Due to the presence of WSGI, Python and write a simple Web framework has become very easy. However, as with many other powerful software, to implement a feature-rich, robust and efficient Web framework will not be easy; if you are going to do, you may use a ready-made Web frameworks (such as Django, Tornado, web.py, etc.) will be It is a more appropriate choice.
This paper attempts to write a Web framework of similar web.py. Okay, I admit I'm exaggerating: the first, web.py not simple; Secondly, this paper focuses only realized the URL dispatch (URL dispatch) section.
Second, start from demo_app
First, as a preliminary experience, we can help wsgiref.simple_server to build a very simple (trivial) Web applications:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
fromwsgiref.simple_serverimportmake_server, demo_app
httpd = make_server('',8086, demo_app)
sa = httpd.socket.getsockname()
print'http://{0}:{1}/'.format(*sa)
# Respond to requests until process is killed
httpd.serve_forever()
Run the script:
$ python code.py
http://0.0.0.0:8086/
打开浏览器,输入http://0.0.0.0:8086/后可以看到:一行”Hello world!” 和 众多环境变量值。
三、WSGI中的application
WSGI中规定:application是一个 可调用对象(callable object),它接受 environ 和 start_response 两个参数,并返回一个 字符串迭代对象。
其中,可调用对象 包括 函数、方法、类 或者 具有__call__方法的 实例;environ 是一个字典对象,包括CGI风格的环境变量(CGI-style environment variables)和 WSGI必需的变量(WSGI-required variables);start_response 是一个可调用对象,它接受两个 常规参数(status,response_headers)和 一个 默认参数(exc_info);字符串迭代对象 可以是 字符串列表、生成器函数 或者 具有__iter__方法的可迭代实例。更多细节参考 Specification Details。
The Application/Framework Side 中给出了一个典型的application实现:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
defsimple_app(environ, start_response):
"""Simplest possible application object"""
status ='200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return['Hello world! ']
现在用simple_app来替换demo_app:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""code.py"""
fromwsgiref.simple_serverimportmake_server
fromapplicationimportsimple_appasapp
if__name__ =='__main__':
httpd = make_server('',8086, app)
sa = httpd.socket.getsockname()
print'http://{0}:{1}/'.format(*sa)
# Respond to requests until process is killed
httpd.serve_forever()
运行脚本code.py后,访问http://0.0.0.0:8086/就可以看到那行熟悉的句子:Hello world!
四、区分URL
倒腾了一阵子后,您会发现不管如何改变URL中的path部分,得到的响应都是一样的。因为simple_app只识别host+port部分。
为了对URL中的path部分进行区分处理,需要修改application.py的实现。
首先,改用 类 来实现application:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
classmy_app:
def__init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def__iter__(self):
status ='200 OK'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield"Hello world! "
然后,增加对URL中path部分的区分处理:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
classmy_app:
def__init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def__iter__(self):
path = self.environ['PATH_INFO']
ifpath =="/":
returnself.GET_index()
elifpath =="/hello":
returnself.GET_hello()
else:
returnself.notfound()
defGET_index(self):
status ='200 OK'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield"Welcome! "
defGET_hello(self):
status ='200 OK'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield"Hello world! "
defnotfound(self):
status ='404 Not Found'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield"Not Found "
修改code.py中的from application import simple_app as app,用my_app来替换simple_app后即可体验效果。
如果你在学习Python的过程当中有遇见任何问题,可以加入我的python交流学企鹅群:【611+530+101】,多多交流问题,互帮互助,群里有不错的学习教程和开发工具。学习python有任何问题(学习方法,学习效率,如何就业),可以随时来咨询我
五、重构
上面的代码虽然奏效,但是在编码风格和灵活性方面有很多问题,下面逐步对其进行重构。
1、正则匹配URL
消除URL硬编码,增加URL调度的灵活性:
#!/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! "
defGET_hello(self, name):##########修改点
status ='200 OK'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield"Hello %s! "% name
defnotfound(self):
status ='404 Not Found'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield"Not Found "
2、DRY
消除GET_*方法中的重复代码,并且允许它们返回字符串:
#!/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! "
defGET_hello(self, name):##########修改点
self.header('Content-type','text/plain')
return"Hello %s! "% name
defnotfound(self):##########修改点
self.status ='404 Not Found'
self.header('Content-type','text/plain')
return"Not Found "
3、抽象出框架
为了将类my_app抽象成一个独立的框架,需要作出以下修改:
剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)
把方法header实现为类方法(classmethod),以方便外部作为功能函数调用
改用 具有__call__方法的 实例 来实现application
修改后的application.py(最终版本):
#!/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 "
@classmethod
defheader(cls, name, value):
cls.headers.append((name, value))
对应修改后的code.py(最终版本):
#!/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! "
classhello:
defGET(self, name):
my_app.header('Content-type','text/plain')
return"Hello %s! "% 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映射,并实现相应的类来对请求作出响应。
六、参考
本文主要参考了 How to write a web framework in Python(作者 anandology 是web.py代码的两位维护者之一,另一位则是大名鼎鼎却英年早逝的 Aaron Swartz),在此基础上作了一些调整和修改,并掺杂了自己的一些想法。
如果您还觉得意犹未尽,Why so many Python web frameworks? 也是一篇很好的文章,也许它会让您对Python中Web框架的敬畏之心荡然无存:-)
转载于:https://www.jianshu.com/p/d83789a9b98a