Flask 是个 Python Web 框架. 网站上文档例子都很详尽, 这里就不废话了, 只是来扯两个使用中需要注意的地方.
装饰器对被装饰函数的名字是敏感的
首先是应用程序装饰器, 如官网上的例子
1
2
3
4
5
6
7
8
|
import
flask
app
=
flask.Flask(__name__)
@app
.route(
'/'
)
def
hello():
return
'Hello World'
if
__name__
=
=
'__main__'
: app.run(port
=
7777
)
|
这个装饰器挂在哪个函数上, 哪个函数就成为一个处理函数. 这个特性让那些看多了 django 或者 tornado 起手先一大堆类定义的人一下子全高潮了, 大呼简单函数拯救世界. 不过这东西有时候相当坑. 先来看一个熊孩子特性, 把上面代码改成这样
1
2
3
4
5
6
|
import
flask
app
=
flask.Flask(__name__)
app.route(
'/'
)(
lambda
:
'Hello World'
)
if
__name__
=
=
'__main__'
: app.run(port
=
7777
)
|
然后试着 curl 一下 http://localhost:7777, 没问题, 还是返回 'Hello World'. 好, 加一句
1
2
3
4
5
6
7
|
import
flask
app
=
flask.Flask(__name__)
app.route(
'/'
)(
lambda
:
'Hello World'
)
app.route(
'/wtf'
)(
lambda
:
'I bought a watch last year'
)
if
__name__
=
=
'__main__'
: app.run(port
=
7777
)
|
再来一发
1
2
3
4
|
$ curl http:
/
/
localhost:
7777
/
I bought a watch last year
$ curl http:
/
/
localhost:
7777
/
wtf
I bought a watch last year
|
怎么都是「I bought a watch last year」?! 我去年买了个表!
查了一通文档才发现 Flask 会索引被装饰函数的名字, 并根据这些名字来派发请求. 好了, 结论是不要在 Flask 里玩 lambda, 因为 Python 里面所有 lambda 名字都一个样
1
2
3
4
5
6
|
>>> x
=
lambda
:
None
>>> x.__name__
'<lambda>'
>>> y
=
lambda
m, n: m
+
n
>>> y.__name__
'<lambda>'
|
傲娇程序员的第一反应可能是「才不会到处用 lambda 只是偶尔写一个换换口味」. 且慢, 倒不是说 lambda 有问题, 而是下面这种用况会出事
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import
flask
app
=
flask.Flask(__name__)
def
handler_logger(f):
def
g(
*
args,
*
*
kwargs):
print
'before request'
r
=
f(
*
args,
*
*
kwargs)
print
'after request'
return
r
return
g
@app
.route(
'/'
)
@handler_logger
def
root():
return
'Hello World'
@app
.route(
'/wtf'
)
@handler_logger
def
wtf():
return
'I bought a watch last year'
if
__name__
=
=
'__main__'
: app.run(port
=
7777
)
|
同样的结果, 访问两个 URL 看到的都是后一个函数的结果.
刚才已经说了 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
|
import
flask
import
functools
app
=
flask.Flask(__name__)
def
handler_logger(f):
@functools
.wraps(f)
def
g(
*
args,
*
*
kwargs):
print
'before request'
r
=
f(
*
args,
*
*
kwargs)
print
'after request'
return
r
return
g
@app
.route(
'/'
)
@handler_logger
def
root():
return
'Hello World'
@app
.route(
'/wtf'
)
@handler_logger
def
wtf():
return
'I bought a watch last year'
def
main():
app.run(port
=
7777
)
if
__name__
=
=
'__main__'
: main()
|
获取 POST 请求体
21 世纪的 Web 交互中服务器跟浏览器互相丢 JSON 已经成了司空见惯的事情. 服务器上要搞到 JSON 数据当然是直接访问 POST 请求体了, 如
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import
flask
import
functools
app
=
flask.Flask(__name__)
@app
.route(
'/wtf'
, methods
=
[
'POST'
])
def
wtf():
return
'Received: '
+
flask.request.data
def
main():
app.run(port
=
7777
)
if
__name__
=
=
'__main__'
: main()
|
按文档的说法, flask.request.data 包含请求数据字符串. 但其实这也是个坑, 默认情况下根本取不到请求数据
1
|
$ curl
-
d
"[1,1,2,3,5,8]"
http:
/
/
localhost:
7777
/
wtf
|
Received:
熊孩子你把拿到的字符串给吃了吧? 实际上如果去看看那文档会看到并不如上面所说的那样, 而是
Contains the incoming request data as string in case it came with a mimetype Flask does not handle.
后面这个状语从句真是着急, 那到底什么 mimetype 会使得 Flask does not handle 呢? 根本没说清楚啊. 扫一眼文档后面, 还有个东西可以用: flask.request.json, 但这货一般是 None, 只有当请求 mimetype 被设置为 application/json 的时候才有用, Flask 你真心是跟 mimetype 过不去啊. 也就是说得这样发请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import
json
import
flask
import
functools
app
=
flask.Flask(__name__)
@app
.route(
'/wtf'
, methods
=
[
'POST'
])
def
wtf():
return
'Received: '
+
json.dumps(flask.request.json)
def
main():
app.run(port
=
7777
)
if
__name__
=
=
'__main__'
: main()
|
1
2
3
4
|
$ curl
-
d
"[1,1,2,3,5,8]"
localhost:
7777
/
wtf
Received: null
$ curl
-
d
"[1,1,2,3,5,8]"
-
H
"Content-Type:application/json"
localhost:
7777
/
wtf
Received: [
1
,
1
,
2
,
3
,
5
,
8
]
|
问题是现在前端攻城狮都被浏览器兼容性折腾得满世界买表, 哪还有心情检查每个请求的 content-type 对不对. 况且这还只对 JSON 有效, 如果是山寨协议又怂了.
好吧, 如果实在不行, 就挖到 WSGI 里面去好了, 比如这样
1
2
3
4
5
|
def
request_data():
d
=
flask.request.data
if
not
d:
return
'
'.join(flask.request.environ['
wsgi.
input
'].readlines())
return
d
|
这样 (在特定的 WSGI 环境中, 比如配合 gevent 使用时) 能获取请求数据 (这不还是坑么). 或者看看这个万能的方法. 好了, 请求字符串快到碗里来!