更新时间:2018年07月26日 09:51:36 做者:Dear、 我要评论python
Flasknginx
Flask是什么?web
Flask是一个使用 Python 编写的轻量级 Web 应用框架, 让咱们可使用Python语言快速搭建Web服务, Flask也被称为 "microframework" ,由于它使用简单的核心, 用 extension 增长其余功能数据库
为何选择Flask?flask
咱们先来看看python如今比较流行的web框架服务器
Flask: 轻, 组件间松耦合, 自由、灵活,可扩展性强,第三方库的选择面广的同时也增长了组件间兼容问题session
Django: Django至关于一个全家桶, 几乎包括了全部web开发用到的模块(session管理、CSRF防伪造请求、Form表单处理、ORM数据库对象化、模板语言), 可是相对应的会形成一个紧耦合的状况, 对第三方插件不太友好app
Tornado: 底层经过eventloop来实现异步处理请求, 处理效率高, 学习难度大, 处理稍有不慎很容易阻塞主进程致使不能正常提供服务, 新版本也支持asyncio框架
Sanic: 一个类Flask框架, 可是底层使用uvloop进行异步处理, 可使用同步的方式编写异步代码, 并且运行效率十分高效.异步
WSGI
先来看看维基百科对WSGI的定义
Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口.
何为网关, 即从客户端发出的每一个请求(数据包)第一个到达的地方, 而后再根据路由进行转发处理. 而对于服务端发送过来的消息, 老是先经过网关层, 而后再转发至客户端
那么可想而知, WSGI实际上是做为一个网关接口, 来接受Server传递过来的信息, 而后经过这个接口调用后台app里的view function进行响应.
先看一段有趣的对话:
Nginx:Hey, WSGI, 我刚收到了一个请求,我须要你做些准备, 而后由Flask来处理这个请求.
WSGI:OK, Nginx. 我会设置好环境变量, 而后将这个请求传递给Flask处理.
Flask:Thanks. WSGI给我一些时间,我将会把请求的响应返回给你.
WSGI:Alright, 那我等你.
Flask:Okay, 我完成了, 这里是请求的响应结果, 请求把结果传递给Nginx.
WSGI:Good job! Nginx, 这里是响应结果, 已经按照要求给你传递回来了.
Nginx:Cool, 我收到了, 我把响应结果返回给客户端.你们合做愉快~
对话里面能够清晰了解到WSGI、nginx、Flask三者的关系
下面来看看Flask中的wsgi接口(注意:每一个进入Flask的请求都会调用Flask.__call__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
# 摘自Flask源码 app.py
class
Flask(_PackageBoundObject):
# 中间省略
def
__call__(
self
, environ, start_response):
return
self
.wsgi_app(environ, start_response)
def
wsgi_app(
self
, environ, start_response):
# environ: 一个包含所有HTTP请求信息的字典, 由WSGI Server解包HTTP请求生成
# start_response: WSGI Server提供的函数, 调用能够发送响应的状态码和HTTP报文头,
# 函数在返回前必须调用一次.
: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 response.
# 建立上下文
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)
|
wsgi_app中定义的就是Flask处理一个请求的基本流程,
1.建立上下文
2.把上下文入栈
3.分发请求
4.上下文出栈
5.返回结果
其中response = self.full_dispatch_request()请求分发的过程咱们须要关注一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# 摘自Flask源码 app.py
class
Flask(_PackageBoundObject):
# 中间省略
def
full_dispatch_request(
self
):
self
.try_trigger_before_first_request_functions()
try
:
request_started.send(
self
)
rv
=
self
.preprocess_request()
if
rv
is
None
:
rv
=
self
.dispatch_request()
except
Exception as e:
rv
=
self
.handle_user_exception(e)
return
self
.finalize_request(rv)
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)
def
finalize_request(
self
, rv, from_error_handler
=
False
):
response
=
self
.make_response(rv)
try
:
response
=
self
.process_response(response)
request_finished.send(
self
, response
=
response)
except
Exception:
if
not
from_error_handler:
raise
self
.logger.exception(
'Request finalizing failed with an '
'error while handling an error'
)
return
response
|
咱们能够看到, 请求分发的操做实际上是由dispatch_request来完成的, 而在请求进行分发的先后咱们能够看到Flask进行了以下操做:
1.try_trigger_before_first_request_functions, 首次处理请求前的操做,经过@before_first_request定义,能够进行数据库链接
2.preprocess_request, 每次处理请求前进行的操做, 经过@before_request来定义, 能够拦截请求
3.process_response, 每次正常处理请求后进行的操做, 经过@after_request来定义, 能够统计接口访问成功的数量
4.finalize_request, 把视图函数的返回值转换成一个真正的响应对象
以上的这些是Flask提供给咱们使用的钩子(hook), 能够根据自身需求来定义,
而hook中还有@teardown_request, 是在每次处理请求后执行(不管是否有异常), 因此它是在上下文出栈的时候被调用
若是同时定义了四种钩子(hook), 那么执行顺序应该是
graph LR
before_first_request --> before_request
before_request --> after_request
after_request --> teardown_request
在请求函数和钩子函数之间,通常经过全局变量g实现数据共享
如今的处理流程就变为:
1.建立上下文
2.上下文入栈
3.执行before_first_request操做(若是是第一次处理请求)
4.执行before_request操做
5.分发请求
6.执行after_request操做
7.执行teardown_request操做
8.上下文出栈
9.返回结果
其中3-7就是须要咱们完成的部分.
如何使用Flask
上面咱们知道, Flask处理请求的步骤, 那么咱们来试试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
from
flask
import
Flask
app
=
Flask(__name__)
@app
.before_first_request
def
before_first_request():
print
(
'before_first_request run'
)
@app
.before_request
def
before_request():
print
(
'before_request run'
)
@app
.after_request
def
after_request(param):
print
(
'after_request run'
)
return
param
@app
.teardown_request
def
teardown_request(param):
print
(
'teardown_request run'
)
@app
.route(
'/'
)
def
hello_world():
return
'Hello World!'
if
__name__
=
=
'__main__'
:
app.run()
|
当运行flask进程时, 访问127.0.0.1:5000, 程序输出, 正好认证了咱们以前说的执行顺序.
before_first_request run
before_request run
after_request run
teardown_request run
127.0.0.1 - - [03/May/2018 18:42:52] "GET / HTTP/1.1" 200 -
路由分发
看了上面的代码, 咱们可能仍是会有疑问, 为何咱们的请求就会跑到hello world 函数去处理呢?咱们先来普及几个知识点:
添加路由的方法:
1.@app.route
2.add_url_rule
咱们先来看看@app.route干了什么事情
1
2
3
4
5
6
7
8
9
|
# 摘自Flask源码 app.py
class
Flask(_PackageBoundObject):
# 中间省略
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
|
咱们能够看到, route函数是一个装饰器, 它在执行时会先获取endpoint, 而后再经过调用add_url_rule来添加路由, 也就是说全部添加路由的操做其实都是经过add_url_rule来完成的. 下面咱们再来看看add_url_rule.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 摘自Flask源码 app.py
class
Flask(_PackageBoundObject):
# 中间省略
# 定义view_functions
self
.view_functions
=
{}
# 定义url_map
self
.url_map
=
Map
()
def
add_url_rule(
self
, rule, endpoint
=
None
, view_func
=
None
,
provide_automatic_options
=
None
,
*
*
options):
# 建立rule
rule
=
self
.url_rule_class(rule, methods
=
methods,
*
*
options)
rule.provide_automatic_options
=
provide_automatic_options
# 把rule添加到url_map
self
.url_map.add(rule)
if
view_func
is
not
None
:
old_func
=
self
.view_functions.get(endpoint)
if
old_func
is
not
None
and
old_func !
=
view_func:
raise
AssertionError(
'View function mapping is overwriting an '
'existing endpoint function: %s'
%
endpoint)
# 把view_func 添加到view_functions字典
self
.view_functions[endpoint]
=
view_func
|
能够看到, 当咱们添加路由时, 会生成一个rule, 并把它存放到url_map里头, 而后把view_func与其对应的endpoint存到字典.
当一个请求进入时, Flask会先根据用户访问的Url到url_map里边根据rule来获取到endpoint, 而后再利用view_functions获取endpoint在里边所对应的视图函数
graph LR
url1 -->url_map
url2 -->url_map
url3 -->url_map
urln -->url_map
url_map --> endpoint
endpoint --> view_functions
上下文管理
下面咱们再来看看以前一直忽略的上下文,什么是上下文呢?
上下文即语境、语意,是一句话中的语境,也就是语言环境. 一句莫名其妙的话出现会让人不理解什么意思, 若是有语言环境的说明, 则会更好, 这就是语境对语意的影响. 而对应到程序里每每就是程序中须要共享的信息,保存着程序运行或交互中须要保持或传递的信息.
Flask中有两种上下文分别为:应用上下文(AppContext)和请求上下文(RequestContext). 按照上面提到的咱们很容易就联想到:应用上下文就是保存着应用运行或交互中须要保持或传递的信息, 如当前应用的应用名, 当前应用注册了什么路由, 又有什么视图函数等. 而请求上下文就保存着处理请求过程当中须要保持或传递的信息, 如此次请求的url是什么, 参数又是什么, 请求的method又是什么等.
咱们只须要在须要用到这些信息的时候把它从上下文中取出来便可. 而上下文是有生命周期的, 不是全部时候都能获取到.
上下文生命周期:
那么上下文是在何时建立的呢?咱们又要如何建立上下文: 刚才咱们提到, 在wsgi_app处理请求的时候就会先建立上下文, 那个上下文实际上是请求上下文, 那应用上下文呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 摘自Flask源码 ctx.py
class
RequestContext(
object
):
# 中间省略
def
push(
self
):
top
=
_request_ctx_stack.top
if
top
is
not
None
and
top.preserved:
top.pop(top._preserved_exc)
# 获取应用上下文
app_ctx
=
_app_ctx_stack.top
# 判断应用上下文是否存在并与当前应用一致
if
app_ctx
is
None
or
app_ctx.app !
=
self
.app:
# 建立应用上下文并入栈
app_ctx
=
self
.app.app_context()
app_ctx.push()
self
._implicit_app_ctx_stack.append(app_ctx)
else
:
self
._implicit_app_ctx_stack.append(
None
)
if
hasattr
(sys,
'exc_clear'
):
sys.exc_clear()
# 把请求上下文入栈
_request_ctx_stack.push(
self
)
|
咱们知道当有请求进入时, Flask会自动帮咱们来建立请求上下文. 而经过上述代码咱们能够看到,在建立请求上下文时会有一个判断操做, 若是应用上下文为空或与当前应用不匹配, 那么会从新建立一个应用上下文. 因此说通常状况下并不须要咱们手动去建立, 固然若是须要, 你也能够显式调用app_context与request_context来建立应用上下文与请求上下文.
那么咱们应该如何使用上下文呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from
flask
import
Flask, request, g, current_app
app
=
Flask(__name__)
@app
.before_request
def
before_request():
print
'before_request run'
g.name
=
"Tom"
@app
.after_request
def
after_request(response):
print
'after_request run'
print
(g.name)
return
response
@app
.route(
'/'
)
def
index():
print
(request.url)
g.name
=
'Cat'
print
(current_app.name)
if
__name__
=
=
'__main__'
:
app.run()
|
访问127.0.0.1:5000时程序输出
before_request run
http://127.0.0.1:5000/
flask_run
after_request run
Cat
127.0.0.1 - - [04/May/2018 18:05:13] "GET / HTTP/1.1" 200 -
代码里边应用到的current_app和g都属于应用上下文对象, 而request就是请求上下文.
那么随之而来的问题是: 这些上下文的做用域是什么?
线程有个叫作ThreadLocal的类,也就是一般实现线程隔离的类. 而werkzeug本身实现了它的线程隔离类: werkzeug.local.Local. 而LocalStack就是用Local实现的.
这个咱们能够经过globals.py能够看到
1
2
3
4
5
6
7
8
9
10
11
|
# 摘自Flask源码 globals.py
from
functools
import
partial
from
werkzeug.local
import
LocalStack, LocalProxy
_request_ctx_stack
=
LocalStack()
_app_ctx_stack
=
LocalStack()
current_app
=
LocalProxy(_find_app)
request
=
LocalProxy(partial(_lookup_req_object,
'request'
))
session
=
LocalProxy(partial(_lookup_req_object,
'session'
))
g
=
LocalProxy(partial(_lookup_app_object,
'g'
))
|
_lookup_app_object思就是说, 对于不一样的线程, 它们访问这两个对象看到的结果是不同的、彻底隔离的. Flask经过这样的方式来隔离每一个请求.
以上就是本文的所有内容,但愿对你们的学习有所帮助,也但愿你们多多支持脚本之家。