勇闯前后端:快速了解Flask的API
前言
在之前的博客中,我们已经快速的了解了,如何来利用VSCode + Poerty来安装Flask,下面我们就聚焦于Flask API本身,来了解一下Flask的这些API在做什么。
一个最小的Demo来试试味道
Flask自己有自己的快速Toturial,我们来看看:
我们先不看官方的,因为涉及到路由等概念了,笔者打算放到下面讲。
这个是笔者自己写的一个最简单的Flask的mini demo
py
from flask import Flask, request, jsonify
from flask.typing import ResponseReturnValue
app = Flask(__name__)
@app.get('/')
@app.get('/hello')
def index_page() -> ResponseReturnValue:
return {"message": "Welcome Charlie's Server!"}, 200
if __name__ == "__main__":
app.run(debug=True)
包导入
我们强调过,Python中使用第三方(比如说,咱们这里的Flask)需要导包,那么:from flask import Flask, request, jsonify就是再导入三个重要的组件。
Flask:核心类,用来创建应用实例(app = Flask(...))。request:当前请求对象(包含请求方法、表单、JSON、头部等)。本代码里没有使用到request,但通常会在处理 POST/GET 参数时用到。jsonify:把 Python 对象转换成 FlaskResponse(application/json)。在这里,我们也没有实际调用jsonify,因为直接return dict在现代 Flask 版本中也会被自动转换为 JSON
当然,如果您的Pylance开启了strict模式,index_page不写返回值,他就会疯狂的抱怨你没有typing返回值的类型。所以,from flask.typing import ResponseReturnValue就是解决这个问题的。因为------
- 这是 Flask 提供的类型别名,用于函数类型注解(
-> ResponseReturnValue)。 - 意味着视图函数可以返回多种形式(
Response对象、字符串、字典、或(body, status)/(body, status, headers)的元组等)。它是为了类型检查/提示而用,不影响运行时行为。
创建 Flask 应用实例
py
app = Flask(__name__)
我们要创建一个WSGI应用对象,这个是一个新东西。WSGI(Web Server Gateway Interface)是Web服务器跟Python应用程序的一个翻译协议。他是跟我们常见的生产使用的Nginx / Apache / uWSGI / Gunicorn 等 服务器可以理解我们Python Backend Framework的通信,反之亦然。换而言之,双方有规定的接口标准进行交流。
PEP 333(Python Enhancement Proposal)决定制定一个规范:所有 Python Web 应用都必须提供一个标准接口。
这个接口是:
python
def application(environ, start_response):
...
return [b"response body"]
这就是经典的 WSGI Application。下面的链条就把我们的逻辑关系说清楚了,可以看到这是一个经典的中间件。
浏览器 <-> Nginx <-> Gunicorn (WSGI Server) <-> Flask App
- 浏览器发送 HTTP 请求
- Nginx 转发给 Gunicorn
- Gunicorn 依照 WSGI 规范 调用 Flask 应用
- Flask 生成响应,再通过 WSGI 传回 Gunicorn → Nginx → 浏览器
所以,Flask(__name__) 会创建一个 应用对象并赋给 app。这个就是我们的应用程序句柄了,我们下面就要让他具备行为。
路由定义与装饰器(routes & decorators)
py
@app.get('/')
@app.get('/hello')
def index_page() -> ResponseReturnValue:
...
在之前,可能很多朋友编写Flask Application还是使用的app.route方法,填写受理的请求方法,但是现在不再需要了,比如说,当我们的函数可以受理针对访问路径是:base_url/(也就是网站的根目录的时候)的GET方法时,就将请求的Callback送到被修饰的函数上。
当然,修饰是可以叠加的,比如说笔者这里就说明了,如果客户端试图访问根目录或者是根目录下的hello路径,index_page函数就会接手请求。
GET /
GET /hello
视图函数(view function)与返回值
py
def index_page() -> ResponseReturnValue:
return {"message": "Welcome Charlie's Server!"}, 200
这里就是我们非常具体的处理回调了。一个良好的规范是------返回请求结果的同时告知正确的状态码,我们在这里针对客户的请求,返回他们感兴趣的内容。
返回的可以是一个元组(当然,直接返回body也是可行的)。这里,笔者返回的是一个元组:(body, status_code),其中:
body是一个 Python 字典{"message": "Welcome Charlie's Server!"}。status_code是整数200(表示成功)。
在 Flask(较新版本)中,返回字典会被自动序列化为 JSON,并设置
Content-Type: application/json;等同于手动jsonify(dict)。因此浏览器/客户端会接收到一个 JSON 响应体和状态码 200。你可以试一下的:)如果使用的是旧版本 Flask(非常老),返回字典可能不被自动转换,这种情况下应使用
jsonify(...)或make_response(...)。但现代 Flask(2.x)会支持直接返回 dict。
主程序入口(仅当直接运行时才启动服务器)
py
if __name__ == "__main__":
app.run(debug=True)
if __name__ == "__main__"::当且仅当该脚本被直接执行 (python script.py)时,这个条件为真;若该模块被别的模块导入则为假。这样子可以防止在被导入时自动启动服务器。
app.run(debug=True),这个稍微提一嘴,现在直接提供debug=True的参数还是不会开启debug模式的,你需要在命令行运行的时候,强制开启--debug,标记调试模式才行。
请求流程(运行时简单说明)
- 用户在浏览器或用 curl 请求
GET http://127.0.0.1:5000/或/hello。 - Flask 的路由系统匹配到
index_page。 - Flask 调用
index_page(),得到({"message": ...}, 200)。 - Flask 将字典序列化为 JSON,构造响应,设置
Content-Type: application/json,返回给客户端;状态码为 200。
请求与响应的深水区(参数、Body、Headers、状态码)
但是,我们的后端还有一个更加重要的功能,就是跟用户进行交互,现代的浏览器会执行POST操作来提交自客户端的相关信息。这个时候,回顾我们之前讲述的内容,显然,我们要挂载的就是post的请求方式。
python
from flask import Flask, request
from flask.typing import ResponseReturnValue
from user import Users
app = Flask(__name__)
us = Users()
@app.get("/")
@app.get("/hello")
def hello_function() -> ResponseReturnValue:
user_name: str = request.args.get("user_name", "")
if (len(user_name) == 0):
return {"message": "Hello Stranger!", "status": "Ok, but wait registered!"}, 200
if us.is_user_registered(user_name):
return {"message": f"Welcome back {user_name}!", "status": "OK"}, 200
else:
return {"message": f"Hello {user_name}!", "status": "OK"}, 200
@app.get("/device")
def device_getter() -> ResponseReturnValue:
device_name: str = request.headers.get("User-Agent", "Unknown")
response_data = {
"device_getter": device_name
}
return response_data, 200
@app.get("/users/<user_name>")
def user_service(user_name: str) -> ResponseReturnValue:
return f"Hello User! {user_name}", 200
@app.get("/uuid/<int:user_id>")
def check_user_id(user_id: int) -> ResponseReturnValue:
return f"User ID: {user_id}", 200
if __name__ == "__main__":
app.run(debug=True, port=5000)
python
class Users:
def __init__(self) -> None:
self._user_list: list[str] = []
self._user_list.append("Alice")
self._user_list.append("Charlie")
def is_user_registered(self, user_name: str) -> bool:
return user_name in self._user_list
def register_user(self, user_name: str) -> tuple[bool, str]:
if user_name in self._user_list:
return False, "User Already in lists!"
self._user_list.append(user_name)
return True, "User Registered!"
一个值得我们注意的就是request变量。这个变量简单的说就是记录我们当前线程处理请求解析URL得到的User Requests,比如说,上面的代码就假设我们的用户如果在访问/或者是/hello下,如果携带了访问的请求参数user_name,比如说,我们可以访问URL得到:/hello?user_name=Charlie的时候,request就会成为一个携带存在键值对的字典,获取user_name就能得到我们关心的Charlie
当然,还有一种方式来携带请求参数的方式就是在路由中携带了相关的请求参数,这里笔者帮助您聚焦一下相关的代码:
python
@app.get("/users/<user_name>")
def user_service(user_name: str) -> ResponseReturnValue:
return f"Hello User! {user_name}", 200
@app.get("/uuid/<int:user_id>")
def check_user_id(user_id: int) -> ResponseReturnValue:
return f"User ID: {user_id}", 200
这个时候,假设我们访问了/user/Charliechen,这个时候Flask的内部回调就会收集和解析出来Charliechen这个参数,投放到user_service中来。当然,如果解析出来的类型不是匹配的(比如说,可以试一试访问/uuid/Charliechen),因为没办法转化成int类型,这个时候就会抛404错误,这个是外部转化的时候就会决定的。
Blueprint(模块化)------从单文件走向可维护项目
显然,项目变得足够庞大的时候,我们不太可能(说真的,也不能)吧所有的代码全部写在一个文件中,那么这个时候,蓝图就会显得非常的重要。Flask的确提供一个模块化的注册机制。这就是Blueprint。我们来试试看。下面这个图是笔者使用tree打印出来的,可以参考一下
.
├── api
│ ├── app_collection.py
│ ├── __init__.py
│ ├── main_page.py
│ ├── ping.py
├── app.py
app.py是咱们的主入口,这里的代码非常的简单!
from api import create_registered_app
from flask import Flask
app: Flask = create_registered_app()
if __name__ == "__main__":
app.run()
下一步就是设计一两个蓝图,将他挂载到咱们的app上:
main_page.py
python
from flask import Blueprint
from flask.typing import ResponseReturnValue
main_page_bp = Blueprint("main_page", __name__)
@main_page_bp.get("/")
def show_main_page() -> ResponseReturnValue:
return "<h1>Welcome Charlies Page!</h1>\n" + \
"<h2>Visit /ping for pong!</h2>\n", 200
python
from flask import Blueprint
from flask.typing import ResponseReturnValue
ping_api = Blueprint("ping", __name__, url_prefix="/ping")
@ping_api.get("/")
def simple_ping() -> ResponseReturnValue:
return "<p>pong!</p>", 200
url_prefix单独谈一下吧,这个是告诉新的根基是/ping/,所以调用这蔟API必须要是基于/ping的,这个就可以讲我们的URL分层做好了
然后下面我们在app_collections上进行挂载
python
from flask import Flask
from .ping import ping_api
from .main_page import main_page_bp
def create_registered_app() -> Flask:
app = Flask(__name__)
# Register APIs
app.register_blueprint(ping_api)
app.register_blueprint(main_page_bp)
return app
完事!
参考与延伸阅读(Reference)
- Flask 快速入门(Quickstart):官方说明最小应用与路由。(flask.palletsprojects.com)
- Flask API 文档(关于请求/响应与返回值行为):说明 dict/list 自动被转换为 JSON 等细节。(flask.palletsprojects.com)
Request.get_json()文档(解释force/silent/ 异常行为):关于如何安全解析 JSON。(tedboy.github.io)- Blueprints(模块化):官方蓝图章节,讲如何把功能封装为蓝图并注册到 app。(flask.palletsprojects.com)
- Error handling(错误处理):如何注册全局或特定错误处理器并返回 JSON。(flask.palletsprojects.com)