flask 一个基于 python 实现的Web开发微框架,主要依赖:
flask 只建立 Werkezug
和 Jinja2
的桥梁,前者实现一个合适的 WSGI 应用,后者处理模板。 Flask 也绑定了一些通用的标准库包,比如 logging
。其它所有一切取决于扩展。
一、WSGI
WSGI(Web Server Gateway Interface)的本质是一种约定,是 Python web 开发中 web 服务器与 web 应用程序之间数据交互的约定。它封装了接受 HTTP 请求
、解析 HTTP 请求
、发送 HTTP
,响应等等的这些底层的代码和操作,使开发者可以高效的编写Web应用。
网关协议的本质是为了解耦,实现 web 服务器(提供 web 服务)和 web 应用程序(资源处理)的分离,WSGI 就是一个支持 WSGI 的 web 服务器与 Python web 应用程序之间的约定。
WSGIWSGI 服务器
一个 WSGI 服务器需要实现两个函数
:
1、解析 http 请求,为应用程序提供 environ 字典
def get_environ(self):
env = {}
env['wsgi.version'] = (1, 0)
env['wsgi.url_scheme'] = 'http'
env['wsgi.input'] = StringIO.StringIO(self.request_data)
env['wsgi.errors'] = sys.stderr
env['wsgi.multithread'] = False
env['wsgi.multiprocess'] = False
env['wsgi.run_once'] = False
env['REQUEST_METHOD'] = self.request_method # GET
env['PATH_INFO'] = self.path # /hello
env['SERVER_NAME'] = self.server_name # localhost
env['SERVER_PORT'] = str(self.server_port) # 8888
return env
2、实现 start_response 函数
def start_response(self, status, response_headers, exc_info=None):
# Add necessary server headers
server_headers = [
('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
('Server', 'WSGIServer 0.2'),
]
self.headers_set = [status, response_headers + server_headers]
# To adhere to WSGI specification the start_response must return
# a 'write' callable. We simplicity's sake we'll ignore that detail
# for now.
WSGI 服务器
服务器与应用程序交互过程
主要过程是:
服务器与应用程序交互过程服务器从客户端获取到请求,然后通过
get_env
获得env
变量。再调用应用程序,传入env
变量(字典) 和start_response
函数, 并获得响应。最后将响应返回给客户端。
代码如下:
def handle_one_request(self):
self.request_data = request_data = self.client_connection.recv(1024)
print(''.join(
'< {line}\n'.format(line=line)
for line in request_data.splitlines()
))
self.parse_request(request_data)
env = self.get_environ() #获取 environ
result = self.application(env, self.start_response) #调用应用程序
self.finish_response(result)
在上述这个过程中,Python 应用程序主要工作就是根据输入的 environ 字典信息生成相应的 http 报文返回给服务器。
下面是一个简单的例子:
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()
其中:
-
environ
: 一个包含全部 HTTP 请求信息的字典,由WSGI Server
解包 HTTP 请求生成。 -
start_response
: 一个WSGI Server
提供的函数,调用可以发送响应的状态码和 HTTP 报文头, 函数在返回前必须调用一次start_response()
。 -
simple_app()
应当返回一个可以迭代的对象(HTTP正文)。 -
simple_app()
函数由WSGI Server
直接调用和提供参数。 - Python 内置了一个
WSGIREF
的WSGI Server
,不过性能不是很好,一般只用在开发环境。可以选择其他的如Gunicorn
。
总结
WSGI Server 和 App交互图flask 中实现 WSGI 接口
1.通过 __call__
方法将 Flask 对象变为可调用
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
2. 实现 wsgi_app
函数处理 web 服务器转发的请求
def wsgi_app(self, environ, start_response):
"""
The actual WSGI application.
:param environ: a WSGI environment
:param start_response: a callable accepting a status code,
a list of headers and
an optional exception context to start the responseresponse
"""
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
二、路由
Flask 类中支持路由功能的数据结构,在 __init__
函数中初始化:
url_rule_class = Rule
self.url_map = Map()
self.view_functions = {}
Rule 和 Map 是 werkzeug
中实现的 路由类
和 映射类
。
>>> m = Map([
... # Static URLs
... Rule('/', endpoint='static/index'),
... Rule('/about', endpoint='static/about'),
... Rule('/help', endpoint='static/help'),
... # Knowledge Base
... Subdomain('something', [
... Rule('/', endpoint='something/index'),
... Rule('/browse/', endpoint='something/browse'),
... Rule('/browse/<int:id>/', endpoint='something/browse'),
... Rule('/browse/<int:id>/<int:page>', endpoint='something/browse')
... ])
... ], default_subdomain='www')
通过 Map 我们就可以实现动态路由的功能。这里我们注意到 Map 类先建立了 url 到 endpoint 的映射,而不是直接映射到函数,这是为什么呢?
主要是两个原因:
- 一是为了实现动态路由功能,
- 二是为不同的 url 映射到同一个视图函数提供了便利。
而 view_functions
是一个字典,它负责建立 endpoint
和视图函数之间的映射关系。
下面是一个小实验,证明我们所说的映射关系:
>>> from flask import Flask
>>> app = Flask(__name__)
>>> @app.route('/')
... def index():
... return "hello world"
...
>>> app.url_map
Map([<Rule '/' (HEAD, GET, OPTIONS) -> index>,
<Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])
>>> app.view_functions
{'index': <function index at 0x7f6ced14c840>, 'static': <bound method _PackageBoundObject.send_static_file of <Flask '__main__'>>}
这里我们可以看到
<Rule '/' (HEAD, GET, OPTIONS) -> index>,'index': <function index at 0x7f6ced14c840>
通过 endpoint
这个中间量,我们让把 路由
和 函数
建立了映射关系。
要注意一下,为什么会有 '/static/<filename>'
这个路由呢,这是因为在初始化时 flask 调用了 add_url_rule
函数做了如下绑定:
if self.has_static_folder:
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
self.add_url_rule(
self.static_url_path + '/<path:filename>',
endpoint='static',
host=static_host,
view_func=self.send_static_file
)
总结
Flask 理由注册路由
在 flask 中注册路由有两种方式,
- 一种是用 route 装饰器,如上所示,
@app.route()
- 另一种是直接调用
add_url_rule
函数绑定视图类,
但是本质上二者都是调用 add_url_rule
函数,下面我们来看一下 add_url_rule
函数的实现。
在 Flask 的 add_url_rule
函数很长,但是核心的代码为以下几行:
self.url_map.add(rule)
rule = self.url_rule_class(rule, methods=methods, **options)
self.view_functions[endpoint] = view_func
1. 装饰器
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
2. 视图类
class CounterAPI(MethodView):
def get(self):
return session.get('counter', 0)
def post(self):
session['counter'] = session.get('counter', 0) + 1
return 'OK'
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
注册路由之后,flask 就需要分发路由,调用相应的视图函数。
def dispatch_request(self):
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
return self.view_functions[rule.endpoint](**req.view_args)
网友评论