欢迎各位兄弟 发布技术文章
这里的技术是共享的
在Python中,WSGI(Web Server Gateway Interface)定义了Web服务器与Web应用(或Web框架)之间的标准接口。在WSGI的规范下,各种各样的Web服务器和Web框架都可以很好的交互。
由于WSGI的存在,用Python写一个简单的Web框架也变得非常容易。然而,同很多其他的强大软件一样,要实现一个功能丰富、健壮高效的Web框架并非易事;如果您打算这么做,可能使用一个现成的Web框架(如 Django、Tornado、web.py 等)会是更合适的选择。
本文尝试写一个类似web.py的Web框架。好吧,我承认我夸大其辞了:首先,web.py并不简单;其次,本文只重点实现了 URL调度(URL dispatch)部分。
首先,作为一个初步体验,我们可以借助 wsgiref.simple_server 来搭建一个简单无比(trivial)的Web应用:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from wsgiref.simple_server import make_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()
运行脚本:
$ python code.py
http://0.0.0.0:8086/
打开浏览器,输入http://0.0.0.0:8086/
后可以看到:一行"Hello world!" 和 众多环境变量值。
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"""
def simple_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!\n']
现在用simple_app来替换demo_app:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""code.py"""
from wsgiref.simple_server import make_server
from application import simple_app as app
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中的path部分,得到的响应都是一样的。因为simple_app只识别host+port部分。
为了对URL中的path部分进行区分处理,需要修改application.py的实现。
首先,改用 类 来实现application:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
class my_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!\n"
然后,增加对URL中path部分的区分处理:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
class my_app:
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
path = self.environ['PATH_INFO']
if path == "/":
return self.GET_index()
elif path == "/hello":
return self.GET_hello()
else:
return self.notfound()
def GET_index(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Welcome!\n"
def GET_hello(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Hello world!\n"
def notfound(self):
status = '404 Not Found'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Not Found\n"
修改code.py中的from application import simple_app as app
,用my_app来替换simple_app后即可体验效果。
上面的代码虽然奏效,但是在编码风格和灵活性方面有很多问题,下面逐步对其进行重构。
消除URL硬编码,增加URL调度的灵活性:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
import re ##########修改点
class my_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']
for pattern, name in self.urls:
m = re.match('^' + pattern + '$', path)
if m:
# pass the matched groups as arguments to the function
args = m.groups()
funcname = method.upper() + '_' + name
if hasattr(self, funcname):
func = getattr(self, funcname)
return func(*args)
return self.notfound()
def GET_index(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Welcome!\n"
def GET_hello(self, name): ##########修改点
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Hello %s!\n" % name
def notfound(self):
status = '404 Not Found'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Not Found\n"
消除GET_*方法中的重复代码,并且允许它们返回字符串:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
import re
class my_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(字符串 或者 字符串列表)转换为迭代对象
if isinstance(result, basestring):
return iter([result])
else:
return iter(result)
def delegate(self): ##########修改点
path = self.environ['PATH_INFO']
method = self.environ['REQUEST_METHOD']
for pattern, name in self.urls:
m = re.match('^' + pattern + '$', path)
if m:
# pass the matched groups as arguments to the function
args = m.groups()
funcname = method.upper() + '_' + name
if hasattr(self, funcname):
func = getattr(self, funcname)
return func(*args)
return self.notfound()
def header(self, name, value): ##########修改点
self._headers.append((name, value))
def GET_index(self): ##########修改点
self.header('Content-type', 'text/plain')
return "Welcome!\n"
def GET_hello(self, name): ##########修改点
self.header('Content-type', 'text/plain')
return "Hello %s!\n" % name
def notfound(self): ##########修改点
self.status = '404 Not Found'
self.header('Content-type', 'text/plain')
return "Not Found\n"
为了将类my_app抽象成一个独立的框架,需要作出以下修改:
__call__
方法的 实例 来实现application修改后的application.py(最终版本):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""application.py"""
import re
class my_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
del self.headers[:] # 清空上一次的headers
result = self._delegate(environ)
start_response(self._status, self.headers)
# 将返回值result(字符串 或者 字符串列表)转换为迭代对象
if isinstance(result, basestring):
return iter([result])
else:
return iter(result)
def _delegate(self, environ):
path = environ['PATH_INFO']
method = environ['REQUEST_METHOD']
for pattern, name in self._urls:
m = re.match('^' + pattern + '$', path)
if m:
# pass the matched groups as arguments to the function
args = m.groups()
funcname = method.upper() # 方法名大写(如GET、POST)
klass = self._fvars.get(name) # 根据字符串名称查找类对象
if hasattr(klass, funcname):
func = getattr(klass, funcname)
return func(klass(), *args)
return self._notfound()
def _notfound(self):
self._status = '404 Not Found'
self.header('Content-type', 'text/plain')
return "Not Found\n"
@classmethod
def header(cls, name, value):
cls.headers.append((name, value))
对应修改后的code.py(最终版本):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""code.py"""
from application import my_app
urls = (
("/", "index"),
("/hello/(.*)", "hello"),
)
wsgiapp = my_app(urls, globals())
class index:
def GET(self):
my_app.header('Content-type', 'text/plain')
return "Welcome!\n"
class hello:
def GET(self, name):
my_app.header('Content-type', 'text/plain')
return "Hello %s!\n" % name
if __name__ == '__main__':
from wsgiref.simple_server import make_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框架的敬畏之心荡然无存:-)
从上图可以看出来,wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application 。
def simple_app(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [u"This is hello wsgi app".encode('utf8')]
我们再用wsgiref 作为wsgi server ,然后调用这个wsgi app,就能直观看到一次request,response的效果,简单修改代码如下:
from wsgiref.simple_server import make_server def simple_app(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [u"This is hello wsgi app".encode('utf8')] httpd = make_server('', 8000, simple_app) print "Serving on port 8000..." httpd.serve_forever()
访问http://127.0.0.1:8000 就能看到效果了。
此外,上面讲到了wsgi app只要是一个callable对象就可以了,因此不一定要是函数,一个实现了__call__方法的实例也可以,示例代码如下:
from wsgiref.simple_server import make_server class AppClass: def __call__(self,environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return ["hello world!"] app = AppClass() httpd = make_server('', 8000, app) print "Serving on port 8000..." httpd.serve_forever()
WSGI MiddleWare
from wsgiref.simple_server import make_server URL_PATTERNS= ( ('hi/','say_hi'), ('hello/','say_hello'), ) class Dispatcher(object): def _match(self,path): path = path.split('/')[1] for url,app in URL_PATTERNS: if path in url: return app def __call__(self,environ, start_response): path = environ.get('PATH_INFO','/') app = self._match(path) if app : app = globals()[app] return app(environ, start_response) else: start_response("404 NOT FOUND",[('Content-type', 'text/plain')]) return ["Page dose not exists!"] def say_hi(environ, start_response): start_response("200 OK",[('Content-type', 'text/html')]) return ["kenshin say hi to you!"] def say_hello(environ, start_response): start_response("200 OK",[('Content-type', 'text/html')]) return ["kenshin say hello to you!"] app = Dispatcher() httpd = make_server('', 8000, app) print "Serving on port 8000..." httpd.serve_forever()
上面的例子可以看出来,middleware 包装之后,一个简单wsgi app就有了URL dispatch功能。然后我还可以在这个app外面再加上其它的middleware来包装它,例如加一个权限认证的middleware:
class Auth(object): def __init__(self,app): self.app = app def __call__(self,environ, start_response): #TODO return self.app(environ, start_response) app = Dispatcher() auth_app = Auth(app) httpd = make_server('', 8000, auth_app) print "Serving on port 8000..." httpd.serve_forever()
经过这些middleware的包装,已经有点框架的感觉了。其实基于wsgi的框架,例如paste,pylons就是这样通过一层层middleware组合起来的。只是一个成熟的框架,这样的middleware会有很多,例如:
def configure(app): return ErrorHandlerMiddleware( SessionMiddleware( IdentificationMiddleware( AuthenticationMiddleware( UrlParserMiddleware(app))))))
只要这些Middleware符合wsgi规范,甚至还可以在各个框架之间组合重用。例如pylons的认证Middleware可以直接被TurboGears拿去使用。
好了,各个部分都写完了,以后有时间再看看pylons的代码,相信又会对wsgi有很多新的理解。
翻译自
Basics of WSGI
http://agiliq.com/blog/2013/07/basics-wsgi/
在这篇文章里,我们将会写一个web app,这个app它会服务一些url。我们将不会使用任何Python框架来写它。我们只是去说明一下这些机制的背后原理。
在开始写这个web app之前,我们先来澄清几个下文将要用到的术语。
Web服务器(Web Server): 当我们说Web 服务器时,我们指的是软件,而不是那些存储你代码的硬件机器。这个服务器会接收从客户端(Web浏览器)发送过来的请求并返回一个响应(response)。Web服务器本身并不创建响应,它只负责返回响应。所以,Web服务器就需要同Web应用程序交流,因为Web应用程序它能产生响应。
Web应用程序(Web Application):Web服务器从它这里拿到响应。Web Application的职责就是根据url来创建响应并将响应传回给Web服务器。然后Web服务器就只是返回这个响应到客户端而已。
WSGI: WSGI是一个接口,它只是一份规范或者说是一系列的规则。WSIG不是一个软件。
WSGI会引起人们的注意是因为Web服务器需要跟Web应用程序通信。WSGI规定了Web应用程序和Web服务器双方必须实现的规则,以便让它们之间能够互相交互。这么说吧,一个符合WSGI规范的服务器就可以跟一个符合WSGI规范的Web应用程序通信。
在WSGI架构里,WSGI应用程序必须是一个可调用者(callable),并且这个可调用者(callable)必须被传递给Web服务器,这样不管什么时候,只要Web服务器接收到客户端的一个请求,它就能随时调用Web应用程序。
想要了解更多关于为什么WSGI会得以出现的原因,请参照 WSGI的维基百科。
现在开始写我们的Web应用程序。你暂时只需要全部拷贝下面的代码即可,我们稍后会对每一行代码做详细解释以便清楚地知道它们都做了什么。
#web_application.py
from wsgiref.simple_server import make_server
def application(environ, start_response):
path = environ.get('PATH_INFO')
if path == '/':
response_body = "Index"
else:
response_body = "Hello"
status = "200 OK"
response_headers = [("Content-Length", str(len(response_body)))]
start_response(status, response_headers)
return [response_body]
httpd = make_server(
'127.0.0.1', 8051, application)
httpd.serve_forever()
这个脚本可以通过运行命令python web_application.py
来执行,接着你可以在浏览器中访问链接http://127.0.0.1:8051/
和http://127.0.0.1:8051/abcd
。你将看到第一个页面会返回Index,而第二个页面返回Hello。
现在,我们来逐句分析下代码。
make_server
。我们可以用它来创建一个遵循WSGI规范的基于Python的Web服务器。callable
叫做application
。你可以认为这个callable
就是上文所说的Web应用程序。make_server()
方法创建了一个遵循WSGI规范的web服务器。这里的httpd
就是Web服务器。make_server()
的前两个参数指定了主机(host)以及Web服务器监听请求的端口(port)。make_server()
的第三个参数是Web应用程序,Web服务器将利用这个参数来获取响应。serve_forever
来启动Web服务器。不管什么时候,只要有请求进来,运行在8051端口的Web服务器就会去调用Web应用程序,在我们这个例子里,Web应用程序就是callable application
.
下面是关于Web应用程序代码的细节。
callable
。应用程序必须利用这个callable
来通知服务器关于响应的状态以及设定各种报头(headers)。这是Web应用程序遵循WSGI规范的第二个条件。我之前在application的代码里忽略了一件事,defnull
童鞋在reddit上指出了我的错误。
application的最后一行不能返回response_body
,正确的情况是它应该返回[response_body]
。原因是:
iterable
然后它会将这个iterable
的每一个元素以一种无缓冲的方式发送给客户端。response_body
,那么response_body
是一个string同时也是一个iterable
,这样的话我们的代码确实还能正常工作不会报错。但是同时也使得response_body
中的内容将会被一个字符一个字符地传递。[response_body]
, 在这里,这个iterable
是一个list, 这个list只有唯一一个元素即response_body
字符串。这样,在我们这个例子里,整个response_body
就只需要被传递一次。[response_body]
而不是response_body
。上面的代码我已经改过来了。你应该读读这篇WSGI来了解更多关于WSGI。
2014-11-28
2015-1-15阅读183 评论0
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
本文为参考网上一些博客翻译以及想法,自己写的一篇总结博文,可能有重复的地方,纯粹总结只用。
阅读之前可参考
1、 How to write a web framework in Python(作者 anandology 是web.py代码的两位维护者之一,另一位则是大名鼎鼎却英年早逝的 Aaron Swartz)
2、Why so many Python web frameworks? 也是一篇很好的文章,也许它会让您对Python中Web框架的敬畏之心荡然无存:-)
如果你打算用python进行网络开发的话,自己写的框架可以说是一种不受支持的想法,可能使用一个现成的Web框架(如 Django、Tornado、web.py 、Pylons等)会是更合适的选择,毕竟都是大师级的作品。
一、一次最简单的web之旅
#!/usr/bin/env python # -*- coding: utf-8 -*- """myweb.py""" from wsgiref.simple_server import make_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()
打开浏览器:http://0.0.0.0:8086/
一行"Hello world!" 和 众多环境变量值。
定位到simple_server.py文件,我们看到make_server函数和WSGIServer类
def make_server( host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler ): """Create a new WSGI server listening on `host` and `port` for `app`""" server = server_class((host, port), handler_class) server.set_app(app) return server class WSGIServer(HTTPServer): """BaseHTTPServer that implements the Python WSGI protocol""" application = None def server_bind(self): """Override server_bind to store the server name.""" HTTPServer.server_bind(self) self.setup_environ() def setup_environ(self): # Set up base environment env = self.base_environ = {} env['SERVER_NAME'] = self.server_name env['GATEWAY_INTERFACE'] = 'CGI/1.1' env['SERVER_PORT'] = str(self.server_port) env['REMOTE_HOST']='' env['CONTENT_LENGTH']='' env['SCRIPT_NAME'] = '' def get_app(self): return self.application def set_app(self,application): self.application = application
很多时候,要简单写一个web框架,主要需要改动传入的app以及server。
二、app的修改
其中,可调用对象 包括 函数、方法、类 或者 具有__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实现:
<span style="font-size:18px;">#!/usr/bin/env python # -*- coding: utf-8 -*- """application.py""" def simple_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!\n']</span>替换原来自带的demo_app,重新运行之
#!/usr/bin/env python # -*- coding: utf-8 -*- """code.py""" from wsgiref.simple_server import make_server from application import my_app as app 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()
这时就输出hello world!而没有环境变量。因为demo_app.py是这样的:
def demo_app(environ,start_response): from StringIO import StringIO stdout = StringIO() print >>stdout, "Hello world!" print >>stdout h = environ.items(); h.sort() for k,v in h: print >>stdout, k,'=', repr(v) start_response("200 OK", [('Content-Type','text/plain')]) return [stdout.getvalue()]
之前的访问server都是基于host+port的形式,那要怎样实现url的分发呢。这需要对app进行修改才行。说到这里,就先将app从一个函数改为一个类吧,再做url区分处理。
#!/usr/bin/env python # -*- coding: utf-8 -*- """application.py""" class my_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!\n"
复习一下python类的语法,说说为什么可以这样写。开始的app可以这样用
list = simple_app(a,b)
现在也可以这样用
list = my_app(a,b)
注:其中参数来自__init__(),返回值来自__iter__()的return值(yield返回的就是一个可迭代对象),也许你会问,如果是传类对象的话呢?且看下下文。
再在return的函数即__iter__()中修改根据不同的path进行不同返回。
#!/usr/bin/env python # -*- coding: utf-8 -*- """application.py""" class my_app: def __init__(self, environ, start_response): self.environ = environ self.start = start_response def __iter__(self): path = self.environ['PATH_INFO'] #environ的作用看到了吧 if path == "/": return self.GET_index() elif path == "/hello": return self.GET_hello() else: return self.notfound() def GET_index(self): status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Welcome!\n" def GET_hello(self): status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Hello world!\n" def notfound(self): status = '404 Not Found' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Not Found\n"
四、重构
1、正则匹配URL
消除URL硬编码,增加URL调度的灵活性:
#!/usr/bin/env python # -*- coding: utf-8 -*- """application.py""" import re ##########修改点 class my_app: urls = ( ("/", "index"), ("/hello/(.*)", "hello"), ) ##########修改点,Django工程中url.py即视感 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'] for pattern, name in self.urls: m = re.match('^' + pattern + '$', path) #注意这里,url匹配函数名 if m: # pass the matched groups as arguments to the function args = m.groups() funcname = method.upper() + '_' + name if hasattr(self, funcname): func = getattr(self, funcname) return func(*args) return self.notfound() def GET_index(self): status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) #遵循调用start_response后再return iterObject yield "Welcome!\n" def GET_hello(self, name): ##########修改点 status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Hello %s!\n" % name def notfound(self): status = '404 Not Found' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Not Found\n"
2、消除GET_*方法中的重复代码,并且允许它们返回字符串:
#!/usr/bin/env python # -*- coding: utf-8 -*- """application.py""" import re class my_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) #start_response # 将返回值result(字符串 或者 字符串列表)转换为迭代对象 if isinstance(result, basestring): return iter([result]) else: return iter(result) def delegate(self): ##########修改点 path = self.environ['PATH_INFO'] method = self.environ['REQUEST_METHOD'] for pattern, name in self.urls: m = re.match('^' + pattern + '$', path) if m: # pass the matched groups as arguments to the function args = m.groups() funcname = method.upper() + '_' + name if hasattr(self, funcname): func = getattr(self, funcname) return func(*args) return self.notfound() def header(self, name, value): ##########修改点 self._headers.append((name, value)) def GET_index(self): ##########修改点 self.header('Content-type', 'text/plain') return "Welcome!\n" def GET_hello(self, name): ##########修改点 self.header('Content-type', 'text/plain') return "Hello %s!\n" % name def notfound(self): ##########修改点 self.status = '404 Not Found' self.header('Content-type', 'text/plain') return "Not Found\n"
为了将类my_app抽象成一个独立的框架,需要作出以下修改:
1、剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)
2、把方法header实现为类方法(classmethod),以方便外部作为功能函数调用
3、改用 具有__call__方法的 实例 来实现application(上文提到)
修改后的application.py(最终版本):
#!/usr/bin/env python # -*- coding: utf-8 -*- """application.py""" import re class my_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 del self.headers[:] # 清空上一次的headers result = self._delegate(environ) start_response(self._status, self.headers) # 将返回值result(字符串 或者 字符串列表)转换为迭代对象 if isinstance(result, basestring): return iter([result]) else: return iter(result) def _delegate(self, environ): path = environ['PATH_INFO'] method = environ['REQUEST_METHOD'] for pattern, name in self._urls: m = re.match('^' + pattern + '$', path) if m: # pass the matched groups as arguments to the function args = m.groups() funcname = method.upper() # 方法名大写(如GET、POST) klass = self._fvars.get(name) # 根据字符串名称查找类对象 if hasattr(klass, funcname): func = getattr(klass, funcname) return func(klass(), *args) return self._notfound() def _notfound(self): self._status = '404 Not Found' self.header('Content-type', 'text/plain') return "Not Found\n" @classmethod def header(cls, name, value): cls.headers.append((name, value))
五、wsgiref原理介绍
1、概述
a.什么是WSGI, WSGI application, WSGI server, WSGI middleware.
WSGI是关于Python脚本与Web服务器交互的协议,wsgi将 web 组件分为三类: web服务器,web中间件,web应用程序。
b.WSGI Server有哪些
比如 Django、CherryPy 都自带 WSGI server,主要是测试用途, 发布时则使用生产环境的 WSGI server,例如Apache,nginx等,而有些 WSGI 下的框架比如 pylons、bfg 等, 自己不实现 WSGI server。wsgiref就是python自带的WSGI server。上面提到的app需要传入的两个参数application(environ, start_response),其实就是一个接口两个参数的集合体。一篇博文这样说明:
simple_server
这一模块实现了一个简单的 HTTP 服务器,并给出了一个简单的 demo,运行:
python simple_server.py
会启动这个demo,运行一次请求,并把这次请求中涉及到的环境变量在浏览器中显示出来。
simple_server 模块主要有两部分内容,上面一到四的内容可以总结。
# M: # +------------+ # | BaseServer | # +------------+ # | # V # +------------+ # | TCPServer | # +------------+ # | # V # +------------+ # | HTTPServer | # +------------+ # | # V # +------------+ # | WSGIServer | # +------------+
# M: # +--------------------+ # | BaseRequestHandler | # +--------------------+ # | # V # +-----------------------+ # | StreamRequestHandler | # +-----------------------+ # | # V # +------------------------+ # | BaseHTTPRequestHandler | # +------------------------+ # | # V # +--------------------+ # | WSGIRequestHandler | # +--------------------+
# M: # +-------------+ # | BaseHandler | # +-------------+ # | # V # +----------------+ # | SimpleHandler | # +----------------+ # | # V # +---------------+ # | ServerHandler | # +---------------+ #
这个模块是对HTTP 响应部分的头部设立的数据结构,实现了一个类似Python 中 dict的数据结构。可以看出,它实现了一些函数来支持一些运算符,例如 __len__
, __setitem__
, __getitem__
, __delitem__
, __str__
, 另外,还实现了 dict 操作中的get
, keys
, values
函数。
这个模块主要就是一些有用的函数,用于处理URL, 环境变量。
这个模块主要是检查你对WSGI的实现,是否满足标准,包含三个部分:
validator 调用后面两个部分来完成验证工作,可以看出Check部分对WSGI中规定的各个部分进行了检查。
来自 http://m.blog.csdn.net/blog/gugugujiawei/42742343