python-webob

webob(WSGI request and response objects)是一个python 库,主要提供WGSI 请求环境变量封装,以及提供一个对象来方便的处理repsonse消息。

webob使我们不用去管复杂的WSGI与HTTP协议,帮助我们非常容易地创建丰富的应用与有效的中间件。

webob的优点:

1、大多数的HTTP spec非常友好的映射到数据结构中
2、久经考验的代码库,隐藏了所有的已知的WSGI怪癖
3、零已知bug
4、100%测试覆盖率
5、没有外部依赖
6、支持python 3

在使用webob的著名框架与网站:

Pyramid
•Pylons 
•TurboGears 
•Google App Engine 
•OpenStack (Rackspace CloudFiles)
•Restish, Bobo, webapp2 
•Reddit, Sourceforge, SurveyMonkey, Dropbox, bittorrent.com, Freshbooks, Freebase, Digg and countless others  

Request

在webob中,主要对象之一就是webob.Request,其是对WSGI环境变量的一个封装。

产生request对象的基本方法如下:

>>> from webob import Request
>>> environ = {'wsgi.url_scheme': 'http', ...}  
>>> req = Request(environ) 

(注意:WSGI环境变量是一个由12关键字组成的字典,因此展示一个完整的例子有点长—通常,WSGI server将为我们创建他们)

WSGI环境变量比较多,为了更加容易地测试与使用他,Request类有个产生最小环境变量的构造器。

>>> req = Request.blank('/article?id=1')
>>> from pprint import pprint
>>> pprint(req.environ)
{'HTTP_HOST': 'localhost:80',
 'PATH_INFO': '/article',
 'QUERY_STRING': 'id=1',
 'REQUEST_METHOD': 'GET',
 'SCRIPT_NAME': '',
 'SERVER_NAME': 'localhost',
 'SERVER_PORT': '80',
 'SERVER_PROTOCOL': 'HTTP/1.0',
 'wsgi.errors': <open file '<stderr>', mode 'w' at ...>,
 'wsgi.input': <...IO... object at ...>,
 'wsgi.multiprocess': False,
 'wsgi.multithread': False,
 'wsgi.run_once': False,
 'wsgi.url_scheme': 'http',
 'wsgi.version': (1, 0)}

Request Body

req.body是一个类文件的对象。存放请求的内容(POST 的表单,PUT的内容等)。

>>> hasattr(req.body_file, 'read')
True
>>> req.body
''
>>> req.method = 'PUT'
>>> req.body = 'test'
>>> hasattr(req.body_file, 'read')
True
>>> req.body
'test'

Request  & URL

请求的所有常规部分都可以通过访问Request对象获得

>>> req.method
'PUT'
>>> req.scheme
'http'
>>> req.script_name  # The base of the URL
''
>>> req.script_name = '/blog' # make it more interesting
>>> req.path_info    # The yet-to-be-consumed part of the URL
'/article'
>>> req.content_type # Content-Type of the request body
''
>>> print req.remote_user  # The authenticated user (there is none set)
None
>>> print req.remote_addr  # The remote IP
None
>>> req.host
'localhost:80'
>>> req.host_url
'http://localhost'
>>> req.application_url
'http://localhost/blog'
>>> req.path_url
'http://localhost/blog/article'
>>> req.url
'http://localhost/blog/article?id=1'
>>> req.path
'/blog/article'
>>> req.path_qs
'/blog/article?id=1'
>>> req.query_string
'id=1'

你可以通过如下方法产生一个新的URLs:

>>> req.relative_url('archive')
'http://localhost/archive'

通过如下方法处理PATH_INFO的path segment

>>> req.path_info_peek() # Doesn't change request
'article'
>>> req.path_info_pop()  # Does change request!
'article'
>>> req.script_name
'/blog/article'
>>> req.path_info
''

Headers

所有的请求头部都可以通过一个类字典类进行访问,字典的键值大小写敏感

>>> req.headers['Content-Type'] = 'application/x-www-urlencoded'
>>> sorted(req.headers.items())
[('Content-Length', '4'), ('Content-Type', 'application/x-www-urlencoded'), ('Host', 'localhost:80')]
>>> req.environ['CONTENT_TYPE']
'application/x-www-urlencoded'

Query & POST 变量

在两个位置可能含有请求变量,一个是查询字符串中(?id=1),一个是请求主体内容中(如POST表单)。注意,POST请求也有可能具有查询字符串,所有这两个位置可能同时含有请求变量。此外,字典键值可以重复,如?check=a & check = b

WebOb使用一个MultiDict表示这些变量,MUltiDict是一个键值对列表的字典封装。它看起来像一个单值字典,但是我们可以通过.getall(key)获得一个键值的所有值(它总是返回一个列表,可能是一个空列表)。我们也可以通过.items()方法获得所有的键值对,通过.values()方法获得所有的值。

>>> req = Request.blank('/test?check=a&check=b&name=Bob')
>>> req.GET
MultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')])
>>> req.GET['check']
u'b'
>>> req.GET.getall('check')
[u'a', u'b']
>>> req.GET.items()
[(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]

Unicode Variables

我们可以强制使用编码,这样影响所有的变量

>>> req.charset = 'utf8'
>>> req.GET
MultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')])

更多内容,请到官方网站查看



Response

下面讲解Response部分,Response对象包含了所有的WSGI response的内容,其实例就是一个WSGI应用。一个WSGI的回应包含了一个状态status,一个header列表,以及一个body(或者一个body的迭代器)。

Core Attributes

>>> from webob import Response
>>> res = Response()
>>> res.status
'200 OK'
>>> res.headerlist
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
>>> res.body


Headers

元组列表,通过.headers.add(key, value) .headers.getall(key)添加和读取,一个key对应多个值

>>> res.headers
ResponseHeaders([('Content-Type', 'text/html; charset=utf8'), ('Content-Length', '4')])

Body & app_iter

.body包含了整个返回的body,其实就是一个字符串,还有另外一种形式:.app_iter属性,表示body是一个迭代器。

>>> res = Response(content_type='text/plain', charset=None)
>>> f = res.body_file
>>> f.write('hey')
>>> f.write(u'test')
Traceback (most recent call last):
  . . .
TypeError: You can only write unicode to Response if charset has been set
>>> f.encoding
>>> res.charset = 'utf8'
>>> f.encoding
'utf8'
>>> f.write(u'test')
>>> res.app_iter
['', 'hey', 'test']
>>> res.body
'heytest'

Header Getters

和Request对象一样,Response对象的header也作为属性存在

>>> res = Response()
>>> res.content_type = 'text/html'
>>> res.charset = 'utf8'
>>> res.content_type
'text/html'
>>> res.headers['content-type']
'text/html; charset=utf8'



wsgify

webob.dec中的装饰器,将一个函数封装为一个WSGI应用。

class webob.dec.wsgify(func=None, RequestClass=None, args=(), kwargs=None, middleware_wraps=None)

将request-taking, response-returning函数变为一个WSGI应用。

使用示例如下:

@wsgify
def myfunc(req):
    return webob.Response('hey there')

经过装饰后, 函数myfunc作为一个WSGI应用,有两种调用的方式:

app_iter = myfunc(environ, start_response)
resp = myfunc(req)

我们可以使用一些参数来定制装饰器,如使用webob.Request的子类。

class MyRequest(webob.Request):     @property     def is_local(self):         return self.remote_addr == '127.0.0.1' @wsgify(RequestClass=MyRequest) def myfunc(req):     if req.is_local:         return Response('hi!')     else:         raise webob.exc.HTTPForbidden

wsgify.middleware()创建中间件

中间件就是在WSGI服务器与应用之间再做一层,用于处理LOG信息,安全限制等:

@wsgify.middleware
def restrict_ip(app, req, ips):
    if req.remote_addr not in ips:
        raise webob.exc.HTTPForbidden('Bad IP: %s' % req.remote_addr)
    return app

@wsgify
def app(req):
    return 'hi'

wrapped = restrict_ip(app, ips=['127.0.0.1'])

一个wsgi application 完整的例子

from webob import Request, Response
from webob import exc

class WikiApp(object):
    ...

    def __call__(self, environ, start_response):
        req = Request(environ) #把environ封装成Request对象
        action = req.params.get('action', 'view')
        # Here's where we get the Page domain object:
        page = self.get_page(req.path_info)
        try:
            try:
                # The method name is action_{action_param}_{request_method}:
                meth = getattr(self, 'action_%s_%s' % (action, req.method))
            except AttributeError:
                # If the method wasn't found there must be
                # something wrong with the request:
                raise exc.HTTPBadRequest('No such action %r' % action)
            resp = meth(req, page)
        except exc.HTTPException, e:
            # The exception object itself is a WSGI application/response:
            resp = e
        return resp(environ, start_response)

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
永久连接: http://www.nfvschool.cn/?p=665
标签:

发表评论