Python编程 VIIII Web框架 Flask

跟着Flask文档,编写一个基于Flask的Web 服务器

启动Flask

1
2
3
4
5
6
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
return "Hello,World"

执行

1
2
3
export FLASK_APP=hello.py  
python -m flask run
使 flask 服务器开始运行

这里主要定义了一个路由“/” 和 调用方法的关系。启动服务器后对对应的路径进行访问

通过指定 —host=0.0.0.0 允许操作系统监听所有公开IP的请求

路由中的变量定义

string (缺省值) 接受任何不包含斜杠的文本
int 接受正整数
float 接受正浮点数
path 类似 string ,但可以包含斜杠
uuid 接受 UUID 字符串

url_for()

url_for 可以通过具体的函数名返回其所对应的资源路径

1
2
3
4
5
@app.route('/user/<username>')
def profile(username):
return '{}\'s profile'.format(escape(username))
with app.test_request_context():
print(url_for('profile',username='Codfish')) # 输出/user/codfish

定义http请求方法对应的处理

1
2
3
4
5
6
7
8
from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()

静态文件

url_for('static', filename='style.css')

静态文件和路径的映射

渲染模板

1
2
3
4
5
6
7
from flask import render_template

@app.route('/hello/')
@app.route('/hello/name')
def hello(name=None)
return render_template('hello.html',name=name)

模板可以用来直接向其中填充调用方法中的变量信息,模板会帮助进行后期的替换

文件上传

1
2
3
4
5
6
7
from flaskimport request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')

这里说的是接收文件上传请求将文件保存到服务器。
f = request.files[‘the_file’] 从请求中获取文件信息

f.save(‘/var/www/uploads/uploaded_file.txt’) 将文件保存到目标路径

获取Cookies

request中存在一个对象用来存储所有的cookies

这里是 获取名为username的cookies信息

1
2
3
4
5
6
7
from flask import request

@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.

对应的向客户端设置cookie

1
2
3
4
5
6
7
from flask import make_response

@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp

使用重定向 ,转移到另一个路径

1
2
3
4
5
from flask import abort, redirect, url_for

@app.route('/')
def index():
return redirect(url_for('login'))

将用户跳转到错误页面

1
2
3
4
5
6
7
abort(401) # 返回 401 错误页面

from flask import render_template

@app.errorhandler(404) # 定制出错页面
def page_not_found(error):
return render_template('page_not_found.html'), 404 # 最后返回的是状态码

返回

支持多种返回类型自动转换

响应对象 → 直接返回

字符串 → 包装字符串和缺省参数 返回

字典 → 使用jsonfy创建一个返回对象

元组 → 接收类型为(response,status) (response,headers) 以及(response,status,headers)类型的项目 会将信息组装成新的响应

或者使用make_response() 构建响应返回

或者 直接 返回json 字段

会话

session 对象用于储存同用户的多个请求的信息

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
from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' #使用前需要设置密钥

@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''

@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))

日志打印

1
2
3
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

Web后端框架的核心功能在于

HTTP请求处理 - 将请求文本处理为请求对象

路由资源 - 在框架中定义路由资源添加的方法,以及如何根据请求查找到对应的路径

响应构建 - 请求结束后的响应是如何组装的,以什么形式返回响应

扩展功能 - 一些协助构建整个服务的扩展功能,比如模板渲染,动态资源路径

WSGI (Web Server Gateway Interface)

wsgi 是定义 web server(可以理解为端口监听,线程管理,以及socket的部分) 如何与实际应用(http请求处理,报文头分析等具体的函数处理部分)交互的接口

1
2
3
4
5
6
7
8
9
10
11
def application(environ, start_response):
# environ 是请求的数据信息, start_reponse 是webserver 的回调函数
...
# 接收到请求体后的功能处理部分

# 调用回调函数,告诉服务器响应的状态码和头部
start_response("200 OK", [("Content-Type", "text/plain")])

# 返回响应体(是一个可迭代对象)
return [b"Hello, WSGI World!"]

这里定义了 web server 需要将请求内容以及一个用于向其写入状态码信息和响应头的回调函数传入。在接口内部需要进行请求处理,处理完毕后,调用回调函数写入状态码和响应头

最终将响应体通过return 返回。

Flask 的wsgi实现

flask 对于wsgi的实现主要有3部分

  1. 定义了一个请求对象RequestContext,将请求体中的内容封装到对象中。
1
2
3
4
5
6
class RequestContext:
def __init__(self, app, environ):
self.request = app.request_class(environ) # 构造 Request 对象
self.session = app.open_session(self.request)
self.app = app
...
  1. 定义了一个上下文栈,将请求对象压入栈中

    1
    2
    3
    4
    from werkzeug.local import LocalStack

    _request_ctx_stack = LocalStack()

  2. 定义了一个代理对象request,使flask不必真正的将RequestContext 对象传入函数,而是通过代理获取外部的作用域来查询RequestContext中的属性

  3. 构建了app 这样一个可调用对象来实现wsgi接口,底层是通过wsgi_app 函数来完成整个过程的执行,app对象本身是为了存储一些中间状态

1
2
3
class Flask:
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)

实现一个轻量框架

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import socket
from wsgiref.simple_server import make_server
from werkzeug.local import LocalStack, LocalProxy

# === 上下文栈 ===
_request_ctx_stack = LocalStack()

def _lookup_request():
top = _request_ctx_stack.top
if top is None:
raise RuntimeError("No request context")
return top['request']

# 全局代理变量
request = LocalProxy(_lookup_request)

# === Request 和 Response 封装 ===
class Request:
def __init__(self, environ):
self.method = environ['REQUEST_METHOD']
self.path = environ['PATH_INFO']
self.environ = environ

class Response:
def __init__(self, body, status="200 OK", headers=None):
self.body = [body.encode('utf-8')]
self.status = status
self.headers = headers or [("Content-Type", "text/html")]

def __call__(self, environ, start_response):
start_response(self.status, self.headers)
return self.body

# === Flask Mini 框架核心 ===
class MiniFlask:
def __init__(self):
self.routes = {}

def route(self, path):
def decorator(f):
self.routes[path] = f
return f
return decorator

def wsgi_app(self, environ, start_response):
# 构造 request 上下文并压入栈
req = Request(environ)
_request_ctx_stack.push({'request': req})
try:
handler = self.routes.get(req.path)
if handler is None:
return Response("404 Not Found", "404 NOT FOUND")(
environ, start_response
)
result = handler()
if isinstance(result, Response):
return result(environ, start_response)
else:
return Response(str(result))(environ, start_response)
finally:
_request_ctx_stack.pop()

def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)

def run(self, host="127.0.0.1", port=5000):
print(f"Running on http://{host}:{port}")
with make_server(host, port, self) as httpd:
httpd.serve_forever()

# === 示例使用 ===
app = MiniFlask()

@app.route("/")
def index():
return f"Hello, your method is {request.method}"

@app.route("/hi")
def hi():
return Response("<h1>Hi there!</h1>")

if __name__ == '__main__':
app.run()

这里面实现了我们上述的几个关键点

  1. 定义了 对http请求的封装
1
2
3
4
5
class Request:
def __init__(self, environ):
self.method = environ['REQUEST_METHOD']
self.path = environ['PATH_INFO']
self.environ = environ
  1. 定义了一个上下文栈,在消息到达时,将请求存储上下文栈,响应后将请求弹出
1
2
3
4
5
6
7
8
_request_ctx_stack = LocalStack()

def _lookup_request():
top = _request_ctx_stack.top
if top is None:
raise RuntimeError("No request context")
return top['request']

  1. 一个对于上下文栈中请求的代理
1
request = LocalProxy(_lookup_request)
  1. 实现 wsgi接口,接收请求环境信息和回调函数,返回最终的响应体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

def wsgi_app(self, environ, start_response):
# 构造 request 上下文并压入栈
req = Request(environ)
_request_ctx_stack.push({'request': req})
try:
handler = self.routes.get(req.path)
if handler is None:
return Response("404 Not Found", "404 NOT FOUND")(
environ, start_response
)
result = handler()
if isinstance(result, Response):
return result(environ, start_response)
else:
return Response(str(result))(environ, start_response)
finally:
_request_ctx_stack.pop()

Python编程 VIIII Web框架 Flask
http://gadoid.io/2025/04/18/Python编程-VIIII-Web框架-Flask/
作者
Codfish
发布于
2025年4月18日
许可协议