Closed vieyahn2017 closed 4 years ago
webob在之前写了 2018.1.18 webob #17
openstack基础之python WSGI,paste,Routes,webob 2017年1月15日 - 在阅读OpenStack各个组件源码的过程中,发现所有的模块如nova,neutron等,都通过wsgi的方式对外提供restful API。而且在使用wsgi的过程中,还涉及paste,routes,webob这些python第三方库的使用。因此如果想理解好OpenStack对外提供restful API的代码,必须要先学好python WSGI,以及理解paste,routes,webob这些库所发挥的作用。
http://tumblr.wachang.net/post/38298360375/webob-wsgi-framework-diff https://blog.csdn.net/ztejiagn/article/details/8722853
作者都是Ian Bicking,两个框架分别是遵循WSGI标准的WSGI框架(以下以1方法表示)以及使用Webob来实现WSGI框架(以下一2方法表示),两篇文章的地址分别是:http://pythonpaste.org/do-it-yourself-framework.html,http://docs.webob.org/en/latest/do-it-yourself.html,这里对两个文章做一个总结比较。
基本概念 controller:就是WSGI应用,2方法中格式为module_name:function_name routes:
webob是一个创建请求和回复对象的库,webob对于请求和回应的封装能力,提供了一种简单测试WSGI应用的方法,如下:
from webob import Request req = Request.blank('http://localhost/test') resp = req.get_response(application) print resp 200 OK Content-Type: text/html
Hello World! 请求部分 在普通的WSGI中,请求的信息是environ,这是一个类似CGI的字典形式。而使用webob,需要创建一个request对象,这是对environ的一个包装。如下:
from webob import Request
def app(environ, start_response):
print 'This is environ info',environ['HTTP_HOST']
start_response('200 OK', [('content-type', 'text/html')])
req = Request(environ)
print 'This is Request info',req.environ['HTTP_HOST']
return ['Hello world!']
root@Node1:~/python# python wsgi.py serving on http://192.168.1.11:8080 This is environ info 192.168.1.11:8080 This is Request info 192.168.1.11:8080 关于返回 通常是函数中先调用start_response,然后函数返回可迭代对象,webob中直接构造Response对象,说白了,Response对象就是一个WSGI应用,如下:
from webob import Request
from webob import Response
def app(environ,start_response):
resp = Response(body='Hello World!')
resp.content_type='text/html'
print resp
return resp(environ,start_response)
httpserver.serve(app, host='192.168.1.11', port='8080')
root@Node1:~/python# python wsgi.py serving on http://192.168.1.11:8080 200 OK Content-Length: 12 Content-Type: text/html; charset=UTF-8 关于WSGI服务器 官方有两个参考,都可以:
from paste import httpserver
httpserver.serve(app, host='127.0.0.1', port=8080)
from wsgiref.simple_server import make_server
server = make_server('127.0.0.1', 8080, app)
server.serve_forever()
Webob实现的WSGI框架 所谓的routes,router就是根据HTTP请求的PATH的层次调度到不同的WSGI应用上面去。2中使用了Router这个类来实现,如下:
app = Router() app.add_route(‘/’, controller=’controllers:index’) app.add_route(‘/post’, controller=’controllers:post’)
有了router以后,我们就要看看如何载入这个controller了,根据controller的格式,我们需要载入一个模块,然后执行函数,所以写了一个此功能的函数:
import sys
def load_controller(string):
module_name, func_name = string.split(':', 1) #分割出module和func的名字
__import__(module_name) #buildin函数,载入模块
module = sys.modules[module_name] #import的返回不好处理,所以这里返回Model名字
func = getattr(module, func_name)
return func #返回函数对象
Router有add_route方法可以加入路由香,并且Router实例有call方法,着同样,ROuter实例就可以当做一个WSGI应用来使用了。所以当一个请求到来的时候,它会根据PATH_INFO(req.path_info)作为匹配,并传递到相应的controller(WSGI应用),Router的代码如下:
from webob import Request
from webob import exc
class Router(object):
def __init__(self):
self.routes = [] #里面是元组,每个元组包含了匹配规则,相应的应用
def add_route(self, template, controller, **vars):
if isinstance(controller, basestring):
controller = load_controller(controller)
self.routes.append((re.compile(template_to_regex(template)),
controller,
vars))
def __call__(self, environ, start_response):
req = Request(environ)
for regex, controller, vars in self.routes:
match = regex.match(req.path_info)
if match:
req.urlvars = match.groupdict()
req.urlvars.update(vars)
return controller(environ, start_response)
return exc.HTTPNotFound()(environ, start_response)
我们详细看看这个函数:
self.routes = [],是一个匹配表,表中内容为(regex, controller, vars)
add_route会判断controller应用是字符串或者是对象,都可以处理,如果是对象,需要实现call方法。
call方法使你可以像函数一样调用一个对象。
对于请求,产生了一个request object对象,controller可以选择以request对象作为参数(在最后返回response(environ,start_response)),或者直接处理(environ,start_response)参数。
req.urlvars变量实际上是environ[‘wsgiorg.routing_args’]的一个映射,environ[‘wsgiorg.routing_args’]是经过match以后,WSGI应用对请求信息的修改,加入了这个wsgiorg.routing_args,值就为匹配的一些参数。
webob.exc.HTTPNotFound()是一个 WSGI application 用于返回404回应(注意还是要以environ和start_response参数调用).也可以加入自定义信息webob.exc.HTTPNotFound(‘No route matched’)(environ,start_response)
基本流程清楚以后,就是来看看controller端了,controller就是一个WSGI应用,但是为了简单的写应用,一般框架都会提供一个装饰器(把一个函数装饰warp成另外一个函数),利用这个装饰器,可以简化controller的开发,如下一个装饰器:
from webob import Request, Response
from webob import exc
def controller(func): #func是自己写的应用
def replacement(environ, start_response):
req = Request(environ) #首先封装environ环境
try:
resp = func(req, **req.urlvars) #将请求和附加参数传给应用处理。返回resp是一个字符串或者一个Response对象。
except exc.HTTPException, e:
resp = e
if isinstance(resp, basestring):#如果应用返回一个字符串,那么就封装为Response对象
resp = Response(body=resp)
return resp(environ, start_response)#Response对象是一个WSGI应用,如此调用的话就成功返回。返回的是自己,webob特色!
return replacement#函数定义中调用另外一个函数,用这种方式。
经过如上装饰以后,自己写的WSGI应用就只需要两个参数controller_func(req, **urlvars)了,确实简化了,不用考虑一直保持environ,start_response的传递了。然后这个装饰器就可以如下使用:
@controller
def index(req):
return 'This is the index'
再来一个复杂一点的:
@controller
def hello(req):
if req.method == 'POST':
return 'Hello %s!' % req.params['name']
elif req.method == 'GET':
return '''<form method="POST">
You're name: <input type="text" name="name">
<input type="submit">
</form>'''
hello_world = Router()
hello_world.add_route('/', controller=hello)
上面一个WSGI应用实际上是一个函数,前面说到,一个WSGI应用也可以是一个类。这样的话,在写controller装饰器的时候,就要注意一点用法:
def rest_controller(cls):
def replacement(environ, start_response):
req = Request(environ)
try:
instance = cls(req, **req.urlvars)
method = getattr(instance, action)
resp = method()
resp = Response(body=resp)
return resp(environ, start_response)
return replacement
action是req中的方法,method是类中的的方法,method()就是一个相应的执行。
class Hello(object):
def __init__(self, req):
self.request = req
def get(self):
return '''<form method="POST">
You're name: <input type="text" name="name">
<input type="submit">
</form>'''
def post(self):
return 'Hello %s!' % self.request.params['name']
hello = rest_controller(Hello)
原创li_101357 最后发布于2016-10-07 21:31:15 阅读数 1080 收藏 https://blog.csdn.net/li_101357/article/details/52751556
程序代码:
from wsgiref.simple_server import make_server
from routes import Mapper,middleware
from webob import Request,Response
import webob.dec
import webob.exc
class Controller:
@webob.dec.wsgify
def __call__(self, req):
return Response("Hello World!")
class Router(object):
def __init__(self):
self.mapper = Mapper()
self.mapper.connect('/lixin',controller=Controller(), action='index',conditions={'method': ['GET']})
self.router = middleware.RoutesMiddleware(self.dispatch, self.mapper) #创建实例,调用的时候进行路由匹配,修改环境变量
@webob.dec.wsgify
def __call__(self, req):
urll = ['%s : %s' % (k,v) for k,v in sorted(req.environ.items())]
print '\n'.join(urll) #原始的环境变量
return self.router
@staticmethod
@webob.dec.wsgify
def dispatch(req):
urll = ['%s : %s' % (k,v) for k,v in sorted(req.environ.items())]
print '\n'.join(urll) #从这里可以看出来环境变量已经改变了
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
app = Router()
print app
print 'Listen port on 8000'
httpd = make_server('localhost', 8000, app)
httpd.serve_forever()
输出结果: self.mapper = Mapper() 创建一个Mapper()
self.mapper.connect 注册一个路由
self.router = middleware.RoutesMiddleware(self.dispatch, self.mapper) 创建一个RoutesMiddleware对象,匹配路由,修改环境变量后,调用self.dispatch
基本上这一块的知识点掌握的还可以,终于可以往下进行了!!!
原创sxmatch 最后发布于2013-08-08 19:42:07 阅读数 1315 收藏 https://blog.csdn.net/sxmatch/article/details/9840759
6 使用webob来包装wsgi请求和响应 先介绍下webob: WebOb是一个Python库,主要是用在WSGI中对请求环境变量request environment(也就是WSGI应用中的参数environ)进行包装(提供wrapper),并提供了一个对象来方便的处理返回response消息。WebOb提供的对象映射了大多数的HTTP方法,包括头解析,content协商等。这里的映射,就是说只需要对对象进行操作,就可以完成HHTP的方法,从而大大简化开发难度(http://blog.csdn.net/ztejiagn/article/details/8722838)。
原创self-motivation 最后发布于2017-01-15 23:11:20 阅读数 4842 收藏 https://blog.csdn.net/happyAnger6/article/details/54518491
在阅读OpenStack各个组件源码的过程中,发现所有的模块如nova,neutron等,都通过wsgi的方式对外提供restful API。
而且在使用wsgi的过程中,还涉及paste,routes,webob这些python第三方库的使用。因此如果想理解好OpenStack对外提供restful API的代码,必须要先学好python WSGI,以及理解paste,routes,webob这些库所发挥的作用。
在网上也看了许多人写的这方面的相关文章,发现大部分是零星介绍其中某一方面概念的,很少有全面介绍清楚的,因此决定写一篇这种文档分享出来,方便大家阅读理解OpenStack的源码。
出现的原因和目标:
python有大量的web框架实现,如Zope,Quixote, Webware, SkunkWeb, PSO, 和 Twisted Web等等。这么多的框架对于python初学者是个困惑,因为他们认为他们选择的web框架会限制它们使用哪个web服务器,反之亦然。
作为一个对比,Java也有很多可用的web框架,但是Java的"servlet API"使使用不同web框架编写的web应用能够运行在不同的支持"servelet API"的web服务器上。
wsgi的目的就是提供一个可以在Python web服务中可以广泛使用的API--------------这样无论这些服务器是使用Python(如Medusa),还是嵌入式Python(如mod_python),或者使用CGI,FastCGI这些网关协议来使用python应用,都可以自由地组合选择python web框架或者python web服务器。这样开源的web服务器和框架开发者可以专注于它们领域的专业化。
因此PEP333的目标就是提供一个在web服务器和web应用程序之间简单且通用的接口:也就是Python Web ServerGateway Interface (WSGI)。
但是WSGI规范对于已经存在的Python web服务器和web框架并没有作用,服务器和框架的作者和维护者需要实现WSGI才能使规范起作用。
然而,由于PEP333提出时并没有web服务器和框架支持WSGI,而且对于实现WSGI并没有直接的奖励,因此WSGI的设计必须容易实现,这样在实现WSGI时的投资和代价可以很低。
这样对于web server和web框架侧接口的实现都应该简单,这对WSGI来说至关重要,这也是任何决策设计的关键原则。
然而需要注意的是,简化框架作者的实现和框架对于web应用容易使用不是一回事.WSGI设计的接口不会有过多的干涉对于框架的作者,比如干涉web响应对象和cookie的控制只会阻碍现有的框架。因此,WSGI只是为了现存的web server和web框架能够方便的交互,而不是发明一个新的web框架。
同样地,WSGI的另一个目标是在任何Python版本中都能够部署。这样,新的标准模块不建议使用,WSGI也不应该要求2.2.2以上的Python版本。
另外,为了简化现有和将来web框架和服务的实现,WSGI应该容易创建request预处理和response后续处理,其它基于WSGI的组件对于包含它们的服务器来说就像一个应用一样,同时对于web服务包含的应用来说看起来就像是一个server。
如果中间件即简单又健壮,并且WSGI广泛地应用在web server和框架中,这就可能产生一个全新的Python web应用框架:由WSGI组件构成的松耦合的框架。另外,现在的框架作者甚至会选择用这种方式来重构他们的框架,这样框架就像是一个使用WSGI的库,而不是一个庞大的框架。这也允许应用开发者为他们的应用选择最合适的组件,而不是只能使用一个既有优点又有缺点的单一框架。
最后,需要提及的一点是,这个PEP333并不涉及部署。在有大量的server和框架实现了WSGI之后,可能会有新的PEP来描述如何部署WSGI服务器和应用框架。
规范概述
WSGI接口包含两方面:"server"或"gateway"侧,和应用和框架侧。服务器侧调用一个应用侧提供的可调用对象。服务器侧规定如何可调用对象如何提供。这里可以假定一些服务器或者网关要求应用的部署都提供一个脚本来创建一个服务或者网关实例,然后给这个实例提供一个应用对象。其它的服务器或者网关则可能使用配置文件或者其它机制来指定应用对象如何导入或者如何获取。
另外,为了使servers/gateways和applications/frameworks保持简洁,也可以创建实现WSGI两侧规范的中间件。这个中间件对包含它们的服务器就像是应用,对于应用来说就像是包含它们的服务器,中间件可以用来提供扩展API,内容转换,导航和其它有用的功能。
在整个规范的描述中,我们将会使用"a callable"来表示一个函数,方法,类或者一个包含call方法的实例。选择何种callable来实现取决server,gateway或者应用程序的需要。反过来,server,gateway,或者应用不能依赖于提供给它的callable是如何实现的。callables仅仅是被调用而已。
应用/框架方面: 应用对象是一个简单的callable对象,并接受2个参数。这个对象术语不能被误解为需要一个实际的对象实例:一个函数,方法,类或者拥有call方法的实例都可以被当作一个应用对象。应用对象必须能够被调用多次,事实上所有的servers/gateways(除了CGI)都会作出这样的重复要求。
注意:尽管我们称其为“应用”对象,它不应该被理解为:意味着应用开发者使用WSGI作为编程API。它假定应用开发者将会使用现在的,高层次的框架来开发他们的应用。WSGI是一个框架和server开发者工具,它并不直接关心应用开发者。
是一个方便我们使用WSGI的工具包。它提供了一系列的WSGI中间件,可以嵌套使用它们来构建web应用程序。因此,它属于上面讲述的WSGI的中间件部分。它提供的所有中间件都符合上面的PEP333接口,并同其它基于PEP333的中间件相兼容。
它提供了以下一些特性:
测试方面:
通过paste.fixture来方便地测试WSGI应用程序。 通过paste.fixture来测试命令行应用。 通过paste.lint来对组件做是否符合WSGI的静态检查。 调度:
通过paste.cascade对WSGI应用程序进行串联和级联(返回第一个没有错误的response)。 通过paste.urlmap基于URL prefix将request分发给不同的WSGI应用程序。 通过paste.recursive允许应用程序创建子请求并在内部传递请求。 Web应用:
通过paste.cgiapp以WSGI运行CGI程序。 通过paste.urlparser来从.py文件中加载WSGI应用程序。 通过paste.urlparser从目录或egg中加载WSGI应用。 工具:
通过paste.httpexceptions来捕获HTTP相关的异常(如HTTPNotFound),并返回适合的response。 通过paste.auth包来提供一些认证方面的支持,包括HTTP(Basic和Digest),签名cookies,和CAS单点登陆。 通过paste.session和paste.flup_session来创建session。 通过paste.gzip来压缩Response。 通过paste.request,paste.response,paste.wsgilib来提供各种各样的程序来操纵request和产生response。 调试过滤器:
通过paste.exceptions来捕获异常的扩展堆栈(使用Zope/ZPT)。 通过paste.cgitb_catcher来捕获错误并呈现一个基于cgitb的输出。 通过paste.debug.profile为每个请求设置一个Profile并在HTML中添加Profile 信息。 通过paste.debug.prints来捕获打印输出并将其呈现在浏览器上用于调试。 通过paste.debug.wgd_validator来通过WDG Validator,验证应用程序的HTML输出,并在页面中添加告警或错误信息。 其它工具:
通过paste.reloader来提供文件监控功能,当有文件更新时(如编辑代码)自动重启服务器。 paste.url,一个用来生成和遍历URL的类,并能创建相关的HTML代码。
提到paste,就不得不提PasteDeploy,可以把PasteDeploy看作paste的一个扩展包,它主要是用来发现和配置WSGI应用。对WSGI的使用者,可以方便地从配置文件汇总加载WSGI应用;对于WSGI的开发者,只需要给自己的应用提供一套简单的入口点即可。由于paste提供的中间件符合PEP333规范,因此我们在使用PasteDeploy加载WSGI应用时可以配置使用paste的组件作为WSGI应用,后面讲解openstack应用时会看到其使用paste.urlmap中间件。
https://github.com/vieyahn2017/iBlog/issues/34 2018.2.8 paste
https://github.com/vieyahn2017/iBlog/issues/35 2018.2.8 routes
WebOb和通用标准实现WSGI框架的比较