Bottle源码阅读(2) WSGIHandler
WSGI Handler
def WSGIHandler(environ, start_response):
"""The bottle WSGI-handler."""
global request
global response
request.bind(environ)
response.bind()
try:
handler, args = match_url(request.path, request.method)
if not handler:
raise HTTPError(404, "Not found")
output = handler(**args)
except BreakTheBottle as shard:
output = shard.output
except Exception as exception:
response.status = getattr(exception, 'http_status', 500)
errorhandler = ERROR_HANDLER.get(response.status, error_default)
try:
output = errorhandler(exception)
except:
output = "Exception within error handler! Application stopped."
if response.status == 500:
request._environ['wsgi.errors'].write("Error (500) on '%s': %s\n" % (request.path, exception))
db.close() # DB cleanup
if hasattr(output, 'read'):
fileoutput = output
if 'wsgi.file_wrapper' in environ:
output = environ['wsgi.file_wrapper'](fileoutput) # 注意文件不能直接返回,必须要用file_wrapper进行包装。
else:
output = iter(lambda: fileoutput.read(8192), '') #将文件变成一个每个元素4kb的list
elif isinstance(output, str):
output = [output]
for c in response.COOKIES.values():
response.header.add('Set-Cookie', c.OutputString())
status = '%d %s' % (response.status, HTTP_CODES[response.status])
start_response(status, list(response.header.items()))
return output
这段代码有40行,这段代码就是Bottle的与WSGIServer通信的核心。
要看懂这段代码,就不得不了解WSGI的实现了。WSGI是一个协议,该协议定义了WSGI的实现必须是一个可以调用的对象,在Python中也就是必须要有Object.call()这个方
法,这个callable对象还必须有两个条件:
- 接受两个参数
- 一个包含CGI参数字典
- 一个发送HTTP头、HTTP状态码、HTTP内容的回调函数,这个函数有服务器提供
- 返回一个可以迭代的包含响应内容的字符串给服务器
有了这两点,上面这段代码就不难看懂了。
environ就是包含CGI参数地字典,而start_response就是那个回调函数,而WSGIHandler这个函数就是要交给WSGIServer来调用。
这个handler里处理了request和response,也就是当有一个用户来访问时,WSGIServer调用WSGIHandler,然后request和response绑定该handler。
output=[output]
这就是将字符串变成一个可迭代的对象。
Request
class Request(threading.local):
""" Represents a single request using thread-local namespace. """
def bind(self, environ):
""" Binds the enviroment of the current request to this request handler """
self._environ = environ
self._GET = None
self._POST = None
self._GETPOST = None
self._COOKIES = None
self.path = self._environ.get('PATH_INFO', '/').strip()
if not self.path.startswith('/'):
self.path = '/' + self.path
@property
def method(self):
''' Returns the request method (GET,POST,PUT,DELETE,...) '''
return self._environ.get('REQUEST_METHOD', 'GET').upper()
@property
def query_string(self):
''' Content of QUERY_STRING '''
return self._environ.get('QUERY_STRING', '')
@property
def input_length(self):
''' Content of CONTENT_LENGTH '''
try:
return int(self._environ.get('CONTENT_LENGTH', '0'))
except ValueError:
return 0
@property
def GET(self):
"""Returns a dict with GET parameters."""
if self._GET is None:
raw_dict = parse_qs(self.query_string, keep_blank_values=1)
self._GET = {}
for key, value in raw_dict.items():
if len(value) == 1:
self._GET[key] = value[0]
else:
self._GET[key] = value
return self._GET
@property
def POST(self):
"""Returns a dict with parsed POST data."""
if self._POST is None:
raw_data = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ)
self._POST = {}
if raw_data:
for key in raw_data:
if isinstance(raw_data[key], list):
self._POST[key] = [v.value for v in raw_data[key]]
elif raw_data[key].filename:
self._POST[key] = raw_data[key]
else:
self._POST[key] = raw_data[key].value
return self._POST
@property
def params(self):
''' Returns a mix of GET and POST data. POST overwrites GET '''
if self._GETPOST is None:
self._GETPOST = dict(self.GET)
self._GETPOST.update(dict(self.POST))
return self._GETPOST
@property
def COOKIES(self):
"""Returns a dict with COOKIES."""
if self._COOKIES is None:
raw_dict = Cookie.SimpleCookie(self._environ.get('HTTP_COOKIE',''))
self._COOKIES = {}
for cookie in raw_dict.values():
self._COOKIES[cookie.key] = cookie.value
return self._COOKIES
这个在WSGI中也属于协议的一部分,是必须要实现的。需要注意的这里继承了threading.local这个类,那么这个类是干什么的呢?注释中写到,使用的是thread-local的命名空间。文档中写到threading-local的数据是具有线程特性的。那么该类的所有属性都是具有线程特性的即Thread-local data。wiki上解释为一个线程需要用到全局变量,那么就需要用到TLS。这些函数都将会在Server中调用,在绑定之后。
Response
class Response(threading.local):
""" Represents a single response using thread-local namespace. """
def bind(self):
""" Clears old data and creates a brand new Response object """
self._COOKIES = None
self.status = 200
self.header = HeaderDict()
self.content_type = 'text/html'
self.error = None
@property
def COOKIES(self):
if not self._COOKIES:
self._COOKIES = Cookie.SimpleCookie()
return self._COOKIES
def set_cookie(self, key, value, **kargs):
""" Sets a Cookie. Optional settings: expires, path, comment, domain, max-age, secure, version, httponly """
self.COOKIES[key] = value
for k in kargs:
self.COOKIES[key][k] = kargs[k]
def get_content_type(self):
'''Gives access to the 'Content-Type' header and defaults to 'text/html'.'''
return self.header['Content-Type']
def set_content_type(self, value):
self.header['Content-Type'] = value
content_type = property(get_content_type, set_content_type, None, get_content_type.__doc__)
作者是真喜欢@property这个装饰器啊,这个类同样是一个必须要实现的类。因为在WSGIHandler中必须要实现处理Request和Response。
这就是整个的逻辑关系,application与WSGIServer通信,WSGIServer调用WSGIHandler来处理,WSGHandler在调用响应Request类和Response类,将Response返回给WSGIServer,WSGIServer再将结果返回给Application就是那个通信的Application。