一、environ字典值
environ 字典中包含了在 CGI 规范中定义了的 CGI 环境变量,WSGI变量和一些系统变量(如果需要的话)
environ字典 | 变量名 |
---|---|
CGI变量 | REQUEST_METHOD |
CGI变量 | SCRIPT_NAME |
CGI变量 | PATH_INFO |
CGI变量 | SERVER_NAME |
CGI变量 | SERVER_PORT |
CGI变量 | QUERY_STRING |
CGI变量 | CONTENT_TYPE |
CGI变量 | SERVER_PROTOCOL |
WSGI变量 | wsgi.version |
WSGI变量 | wsgi.input |
WSGI变量 | wsgi.errors |
WSGI变量 | wsgi.multithread |
WSGI变量 | wsgi.run_once |
1.CGI环境变量
1.REQUEST_METHOD:HTTP 请求的类型,比如「GET」或者「POST」。这个不可能是空字符串,所以是必须给出的。
2.SCRIPT_NAME:URL 请求中路径的开始部分,对应应用程序对象,这样应用程序就知道它的虚拟位置。如果该应用程序对应服务器的根目录的话,它可能是空字符串。
3.PATH_INFO:URL 请求中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置。如果请求的目标是应用程序根目录并且没有末尾的斜杠的话,可能为空字符串。
SCRIPT_NAME是URL中CGI脚本在服务器(如apache,IIS等)根目录下的路径(物理路径),而PATH_INFO则是这个脚本中具体执行部分的位置(虚拟路径),SCRIPT_NAME和PATH_INFO不能同时为空字符串。
4.SERVER_NAME,SERVER_PORT:这些变量可以和 SCRIPT_NAME、PATH_INFO 一起组成完整的URL。然而要注意的是,重建请求 URL 的时候应该优先使用 HTTP_HOST 而非 SERVER_NAME 。SERVER_NAME 和 SERVER_PORT 永远不能为空字符串,也总是必须存在的。
5.QUERY_STRING:URL 请求中跟在 “ ? ” 后面的那部分,可能为空或不存在。通常为GET方法提交的变量。
6.CONTENT_TYPE:HTTP 请求中任何 Content-Type 域的内容,可能为空或不存在。
7.CONTENT_LENGTH:HTTP 请求中任何 Content-Length 域的内容,可能为空或不存在。
8.SERVER_PROTOCOL:客户端发送请求所使用协议的版本。通常是类似「HTTP/1.0」或「HTTP/1.1」的东西,可以被用来判断如何处理请求包头。(既然这个变量表示的是请求中使用的协议,而且和服务器响应时使用的协议无关,也许它应该被叫做REQUEST_PROTOCOL。不过为了保持和 CGI 的兼容性,我们还是使用这个名字。)
服务器或网关应尝试提供适用的其他CGI变量。 此外,如果正在使用SSL,则服务器或网关还应提供尽可能多的Apache SSL环境变量,例如HTTPS = on和SSL_PROTOCOL。 但请注意,使用除上面列出的CGI变量之外的任何CGI变量的应用程序必然不可移植到不支持相关扩展的Web服务器。 (例如,不发布文件的Web服务器将无法提供有意义的DOCUMENT_ROOT或者PATH_TRANSLATED。)
注意:不需要的变量(例如不用身份验证时的REMOTE_USER)不应该在environ词典中。CGI定义的变量必须是字符串,如果它们存在的话。 如果CGI变量的值是除str之外的任何类型,则违反此规范。
2.WSGI定义变量
除了CGI定义的变量之外,environ字典还可以包含任意操作系统“环境变量”,并且必须包含以下WSGI定义的变量:
1.wsgi.version:元组(1, 0),代表 WSGI 1.0 版
wsgi.url_scheme:字符串,表示应用请求的 URL 所属的协议,通常为「http」或「https」。
2.wsgi.input: 类文件对象的输入流,用于读取 HTTP 请求包体的内容。(服务端在应用端请求时开始读取,或者预读客户端请求包体内容缓存在内存或磁盘中,或者视情况而定采用任何其他技术提供此输入流。)
3.wsgi.errors: 类文件对象的输出流,用于写入错误信息,以集中规范地记录程序产生的或其他相关错误信息。这是一个文本流,即应用应该使用「n」来表示行尾,并假定其会被服务端正确地转换。(在 str 类型是 Unicode 编码的平台上,错误流应该正常接收并记录任意 Unicode 编码而不报错,并且允许自行替代在该平台编码中无法渲染的字符。)很多 Web 服务器中 wsgi.errors 是主要的错误日志,也有一些使用 sys.stderr 或其他形式的文件来记录。Web 服务器的自述文档中应该包含如何配置错误日志以及如何找到记录的位置。服务端可以在被要求的情况下,向不同的应用提供不同的错误日志。
4.wsgi.multithread:如果应用对象可能会被同一进程的另一个线程同步调用,此变量值为真,否则为假。
5.wsgi.multiprocess:如果同一个应用对象可能会被另一个进程同步调用,此变量值为真,否则为假。
6.wsgi.run_once:如果服务端期望(但是不保证能得到满足)应用对象在生命周期中只被调用一次,此变量值为真,否则为假。一般只有在基于类似 CGI 的网关服务器中此变量才会为真。
3.使用environ
environ就是一个字典,所以操作environ参数就是操作一个dict变量。
创建
字典由键和对应值成对组成。字典也被称作关联数组或哈希表。基本语法如下:
environ = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'localhost','SERVER_PORT': '8080',......}
注意:
- 每个键与值用冒号隔开(:),每对用逗号,每对用逗号分割,整体放在花括号中({})。
- 键必须独一无二,但值则不必。值可以取任何数据类型,但必须是不可变的,如字符串,数或元组。
- 创建environ字典通常是Server需要完成的,且必须字典中每个键都要存在(值可以为空)。我们一般不需要创建。
访问
访问字典中某个键的值
environ['SERVER_NAME']
如果字典中不存在该键则会报错。
修改
向字典添加新内容的方法是增加新的键/值对,修改已有键/值:
修改SERVER_PORT键的值
environ['SERVER_PORT'] = 8080
增加一个验证的键
environ['AUTH'] = "x-auth"
删除
能删单一的元素也能清空字典,清空只需一项操作。显示删除一个字典用del命令:
删除键是'AUTH'的条目
del environ['AUTH']
清空词典所有条目
environ.clear()
删除environ字典
del environ
遍历
将遍历后的tuple(key,value)放到一个list中
urll = ['%s : %s' % (key,value) for key,value in sorted(environ.items())]
print '\n'.join(urll)
字典内置函数&方法
Python字典包含了以下内置函数:
cmp(dict1, dict2) #比较两个字典元素。
len(dict) #计算字典元素个数,即键的总数。
str(dict) #输出字典可打印的字符串表示。
type(variable) #返回输入的变量类型,如果变量是字典就返回字典类型。
Python字典包含了以下内置方法:
dict.clear() #删除字典内所有元素
dict.copy() #返回一个字典的浅复制
dict.fromkeys() #创建一个新字典,以序列seq中元素做字典的键,val为字典所有键对应的初始值
dict.get(key, default=None) #返回指定键的值,如果值不在字典中返回default值
dict.has_key(key) #如果键在字典dict里返回true,否则返回false
dict.items() #以列表返回可遍历的(键, 值) 元组数组
dict.keys() #以列表返回一个字典所有的键
dict.setdefault(key, default=None) #和get()类似, 但如果键不已经存在于字典中,将会添加键并将值设为default
dict.update(dict2) #把字典dict2的键/值对更新到dict里
dict.values() #以列表返回字典中的所有值
字典键的特性
字典值可以没有限制地取任何python对象,既可以是标准的对象,也可以是用户定义的,但键不行。两个重要的点需要记住:
- 不允许同一个键赋值两次。创建时如果同一个键被赋值两次,后一个值会被记住。但可以有两个相同的键存在。
- 键必须不可变,所以可以用数,字符串或元组充当,而不能用列表。
更多关于environ字典的内容:https://www.python.org/dev/peps/pep-0333/#environ-variables
二、WebOb包
1.Request对象
webob.Request是WebOb中的一个重要对象。它会对WSGI Server接收到的http request进行封装。
构造一个Request
environ有许多必需的变量。 为了更容易测试和使用,Request类有一个构造函数,它将填充一个最小的environ,一个简单的例子:
from webob import Request
from pprint import pprint
req = Request.blank('/article?id=1')
pprint(req.environ)
我们可以直接使用的req.environ属性直接操作填充好的environ字典。
操作Request body
既然是request,那么必然有个body(request body可以为空),并且也有request的方法(如GET,POST,DELETE,PUT,INDEX),所以可以这样操作body和method:
>>>hasattr(req.body_file, 'read')
True
>>>req.body
b' '
>>>req.method = 'PUT'
>>>req.body = 'test'
>>>hasattr(req.body_file, 'read')
True
>>>req.body
'test'
操作Request headers
请求头(request 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'
操作请求参数:
>>> req = Request.blank('/test?check=a&check=b&name=Bob')
>>> req.GET
GET([('check', 'a'), ('check', 'b'), ('name', 'Bob')])
>>> req.GET['check']
'b'
>>> req.GET.getall('check')
['a', 'b']
>>> list(req.GET.items())
[('check', 'a'), ('check', 'b'), ('name', 'Bob')]
下面这个是比较常见的查看参数的方法:
>>> req.params
NestedMultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob'), ('name', 'Joe'), ('email', '[email protected]')])
>>> req.params['name']
'Bob'
>>> req.params.getall('name')
['Bob', 'Joe']
>>> for name, value in req.params.items():
... print('%s: %r' % (name, value))
check: 'a'
check: 'b'
name: 'Bob'
name: 'Joe'
email: '[email protected]'
把Request传递给WSGI应用
from webob import Request
req = Request.blank('/')
def wsgi_app(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
return ['Hi!']
print req.call_application(wsgi_app)
输出:
[root@OS_DEV dev]# python webobtest.py
('200 OK', [('Content-type', 'text/plain')], ['Hi!'])
call_application和get_response方法都可以返回response的内容:
- req.call_application(wsgi_app)返回的是一个tuple。
- req.get_response(wsgi_app)返回一个Response类,操作方便,更常用。
2.Response对象
webob.Response包含了标准WSGI response的所有要素。其本身也可以看成是一个WSGI的application。你可以通过req.call_application(res)对其调用。
操作Response Header
以list的方式输出一个header,最简单的例子如下:
>>> from webob import Response
>>> res = Response()
>>> res.status = 200
>>> res.status
'200 OK'
>>> res.status_code
200
>>> res.headerlist
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
>>> res.body
b''
除了res.headerlist,还可以以字典的方式操纵header。
>>> res.headers
ResponseHeaders([('Content-Type', 'text/html'), ('Content-Length', '4')])
还可以通过res.headers.add(key,value)增加键值对,通过res.headers.getall(key)获取所有键值对。
写入Response body:
res.body属性将请求的整个主体表示为一个str(不是unicode,但如果定义了charset,则可以将其设置为unicode)。 还有一个res.app_iter属性,它将body作为迭代器输出,app_iter是一个特殊的list。 WSGI应用程序返回这些app_iter迭代器而不是str,有时一次加载整个迭代器可能会有问题(例如,如果返回一个非常大的文件的内容时)。但通常迭代器通常很简单,就像包含整个主体的字符串的单项列表一样。
如果设置了body,那么也将设置Content-Length,并为创建res.app_iter。 如果设置res.app_iter,则会清除Content-Length而不会设置,所以如果通过res.app_iter修改body,记得需要同时修改Content-Length的值。
通常可以通过以下简单的方式为整个body赋值:
res.body = b'test'
我们还可以访问一个 file-like 对象,它将直接更新app_iter(如果需要,将app_iter转换为list),如果charset = None,则需要先设置charset才能通过file-like对象操作body:
>>> res = Response(content_type='text/plain', charset=None)
>>> f = res.body_file
>>> f.write('hey')
Traceback (most recent call last):
...
TypeError: You can only write text to Response if charset has been set
>>> f.encoding
>>> res.charset = 'UTF-8'
>>> f.encoding
'UTF-8'
>>> f.write('test')
>>> res.app_iter
[b'', b'test']
>>> res.body
b'test'
获取WSGI APP返回的Response
可以通过前面说过的req的一个方法得到WSGI APP返回的res。
res = req.get_response(wsgi_app)
注意下这个例子,这个例子把普通的WSGI的应用通过Request和Response做了一个简单的包装,虽然没有太大的修改,但可以帮我们理解装饰器的原理:
def my_app(environ, start_response):
... req = Request(environ)
... res = Response()
... res.content_type = 'text/plain'
... parts = []
... for name, value in sorted(req.environ.items()):
... parts.append('%s: %r' % (name, value))
... res.body = 'n'.join(parts)
... return res(environ, start_response)
>>> req = Request.blank('/')
>>> res = req.get_response(my_app)
>>> print res
200 OK
......
3.Exceptions
其实就是对HTTP错误代码的一个封装。也可以看成是一个WSGI的应用。
>>> from webob.exc import *
>>> exc = HTTPTemporaryRedirect(location='foo')
>>> req = Request.blank('/path/to/something')
>>> print(str(req.get_response(exc)).strip())
307 Temporary Redirect
Location: http://localhost/path/to/foo
Content-Length: 126
Content-Type: text/plain; charset=UTF-8
\r
307 Temporary Redirect
The resource has been moved to http://localhost/path/to/foo; you should be redirected automatically.
4.WSGIfy decorator
结合上面的例子,既然WebOb可以让WSGI的请求变得更加简单、强大,那么能不能不用原始的那种WSGI的参数和返回格式,而全部用WebOb替代?可以的,通过WSGIfy decorator这个装饰器。
比如这个最简单的例子:
@wsgify
def myfunc(req):
return webob.Response('hey there')
调用的时候有两个选择:
app_iter = myfunc(environ, start_response)
或:
resp = myfunc(req)
第一种选择就是最原始和标准的的WSGI格式,第二种选择则是WebOb封装过后的格式。说实话后者看上去更加符合逻辑(给你个请求,给我个响应)。需要注意的是返回的值必须和输入的参数对应。
如果myfanc直接返回一个Exception,那么就会的相当于直接调用Exception这个WebOb的WSGI Application,可以很容易的返回异常页面。
另外也可以对Request进行继承,修改其内容,对真正的Request做一些判断(就是一个Middleware),比如:
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修饰过后的那些func,其Return的是个对Response的调用,即一个Response对象。