高性能的Web应用程序托管程序 uWSGI:https://segmentfault.com/a/1190000044454648
uwsgi 设置使用 python3:https://blog.51cto.com/u_16175513/12722644
Flask 文档
- 英文文档:https://flask.palletsprojects.com/en/2.3.x/
- 中文文档:https://dormousehole.readthedocs.io/en/latest/
flask入门教程及实战:http://www.coolpython.net/flask_tutorial/basic/route.html
Flask Web 开发入门:https://www.bookstack.cn/read/head-first-flask/chapter02-README.md
开源的 Web 框架
开源的 Web 框架哪个快:https://zhuanlan.zhihu.com/p/408025404
GitHub 上 web 框架性能基准测试
:https://github.com/the-benchmarker/web-frameworks
:https://web-frameworks-benchmark.netlify.app/result
异步 Python web 框架
Sanic
官方文档:https://sanic.readthedocs.io/en/stable/#
python web 框架 Sanic 比Flask还好用?:https://www.jianshu.com/p/636833c71c2a
Sanic 是一个和类Flask 的基于Python3.5+的web框架,它编写的代码速度特别快。
除了像Flask 以外,Sanic 还支持以异步请求的方式处理请求。这意味着你可以使用新的 async/await 语法,编写非阻塞的快速的代码
quart
:https://github.com/pallets/quart
示例:
from quart import Quart, render_template, websocket
app = Quart(__name__)
@app.route("/")
async def hello():
return await render_template("index.html")
@app.route("/api")
async def json():
return {"hello": "world"}
@app.websocket("/ws")
async def ws():
while True:
await websocket.send("hello")
await websocket.send_json({"hello": "world"})
if __name__ == "__main__":
app.run()
FastAPI
Flask 中的 asyncio 事件循环
- Using async and await
- Performance
- Background tasks
- When to use Quart instead
- Extensions
- Other event loops
要在Flask应用中使用asyncio,我们需要先创建一个事件循环。在Flask的上下文中,我们可以使用current_app
对象来访问应用实例。我们可以通过在应用实例上调用app.before_first_request
装饰器来创建事件循环。
from flask import Flask, current_app
app = Flask(__name__)
@app.before_first_request
def before_first_request():
loop = asyncio.get_event_loop()
current_app.loop = loop
在Flask应用中创建了事件循环,我们就可以在视图函数中使用它了。让我们看一个简单的例子,在视图函数中进行异步操作。
from flask import Flask, current_app, jsonify
app = Flask(__name__)
@app.route('/')
def index():
loop = current_app.loop
async def async_task():
# 异步操作
result = await some_async_function()
return result
result = loop.run_until_complete(async_task())
return jsonify({'result': result})
使用loop.run_until_complete()
方法来运行异步任务,并等待其完成。
后台任务
除了在视图函数中使用asyncio事件循环,我们还可以在后台任务中使用它。Flask支持使用APScheduler这样的库来创建后台任务。
安装:pip install apscheduler
from flask import Flask
from apscheduler.schedulers.background import BackgroundScheduler
app = Flask(__name__)
scheduler = BackgroundScheduler()
@app.route('/')
def index():
return 'Hello, Flask!'
def scheduled_task():
loop = current_app.loop
async def async_task():
# 后台任务中的异步操作
result = await some_async_function()
return result
result = loop.run_until_complete(async_task())
print(result)
# 每隔10秒执行一次后台任务
scheduler.add_job(scheduled_task, 'interval', seconds=10)
scheduler.start()
if __name__ == '__main__':
app.run()
在 Flask 应用中创建了一个后台任务scheduled_task
,它会每隔10秒执行一次。在后台任务中,我们使用和视图函数中一样的方法来使用asyncio事件循环。
示例 :
import threading
import asyncio
from flask import Flask, jsonify
print(f"In flask global level: {threading.current_thread().name}")
app = Flask(__name__)
@app.route("/toy", methods=["GET"])
def index():
print(f"Inside flask function: {threading.current_thread().name}")
loop = asyncio.get_event_loop()
result = loop.run_until_complete(hello())
return jsonify({"result": result})
async def hello():
await asyncio.sleep(1)
return 1
if __name__ == "__main__":
app.run(host="0.0.0.0", port=4567, debug=False)
1、快速入门
安装 Flask:sudo pip install Flask
简单示例
新建 hello_flask.py。( 文件名不是 flask.py ,因为这将与 Flask 本身冲突 )
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello World!"
if __name__ == "__main__":
app.run()
访问 http://127.0.0.1:5000/ ,你会 看见 Hello World 问候。
- 首先导入 Flask 类。Flask是一个类,app是这个类的实例,在创建app这个对象时,需要传入参数import_name,通常,我们使用__name__(模块的一个全局变量,表示模块名称)
- 接下来创建 Flask 类的实例,这个实例是 WSGI 应用程序。第一个参数是应用模块或者包的名称。 如果你使用单一的模块(如本例),你应该使用 __name__ ,因为模块 的名称将会因其作为单独应用启动还是作为模块导入而有不同( 也即是 '__main__' 或实际的导入名)。这是必须的,这样 Flask 才知道到哪去找模板、静态文件等等。详情见 Flask 的文档。
- 然后,使用 route() 装饰器告诉 Flask 什么样的 URL 能触发我们的函数。
- 这个函数的名字也在生成 URL 时被特定的函数采用,这个函数返回我们想 要显示在用户浏览器中的信息。
- 最后我们用 run() 函数来让应用运行在本地服务器上。 其中 if __name__ == '__main__': 确保服务器只会在该脚本被 Python 解释器直接执行的时候才会运行,而不是作为模块导入的时候。
按 Ctrl+C 关闭服务器。如果想要外部访问,可以设置 app.run(host='0.0.0.0')
自动加载技术--reload
自动加载技术在web开发中应用很广泛,设想,你正在编写一个服务,每写完一段代码,都需要进行调试,有时仅仅是修改了一行代码,如果为了测试代码,不停的重启服务,着实让人烦恼。
自动加载技术会监测项目里文件的修改情况,一旦发现文件有修改,就会重新加载这个文件,相当于重新import这个模块,这样,你的每一次改动都会在保存后生效而不需要你重启服务,是不是很爽,关于这个技术,我会专门写文章来介绍。
开启 flask 自动加载只需要将 debug 参数设置为 True,flask 将以调试模式启动。然后修改代码,观察服务,一定会重新启动,出现类似下面的提示信息
* Debugger pin code: 194-794-301
* Detected change in '/Users/zhangdongsheng/finup/experiment/studyflask/simple_app/app.py', reloading
* Restarting with stat
Web 服务的组成
准确来说,一个 Flask后端应用,并不等同于一个完整的Web服务,一个完整的Web服务如下:
Web服务器接收浏览器发出的HTTP请求,并经由WSGI标准接口与APP进行通信,APP处理完请求之后,再将响应经由WSGI处理,最终由Web服务器发送给前端。
Flask应用就是APP的角色,而Server通常会由另一个组件来实现,当通过 app.run() 启动 Flask 应用时,其实是 Flask 内置了一个仅用于开发调试的低性能、简易的 Server,这也是为什么不建议直接在生产环境使用 app.run() 来部署 Flask 应用(不建议并不是不能)。
WSGI
Web 服务器网关接口(Python Web Server Gateway Interface,缩写为 WSGI)是为 Python 语言定义的 Web服务器 和 Web应用程序 或 框架 之间的一种简单而通用的接口。
上面的 Web Server 是由单独的组件来充当,那么 Server 在与 APP 交互过程中,就需要遵循一种规范,这个规范就是 WSGI。
通俗的讲:充当 Web Server 角色的可以有很多组件、框架;也有很多框架可以充当 WebApp 的角色,但只要它们双方都遵守WSGI规范,那么编程人员就可以用任意一个WebServer 组件去和任意一种 WebApp 对接。
web server 承担端口监听和接受请求的任务
web framework 主要承担路由,业务逻辑等任务
一般 web framework 库( 比如 flask ),主要部分是 web framework,同时也自带一个性能不咋滴的 web server,这样在开发和调试时可以直接运行起来看看效果,但是在生产环境中自带的 web server 性能就不够用了。
gunicorn 和 uwsgi 是单独实现的性能强劲的 web server,这种单独实现的 web server 和 web framework 配合起来用就可以提高整个应用的性能。
gunicorn、uwsgi 是 web server,flask 或者 bottle 是 web framework。gevent 是 async io。
uwsgi 是 C语言实现,性能更好。gunicorn 是纯 Python 实现。
Flask、Gunicorn、uWSGI、UWSGI、nginx 等关系:https://zhuanlan.zhihu.com/p/602061497
- WSGI,是一种描述web服务器(如nginx,uWSGI等服务器)如何与web应用程序(如用Django、Flask框架写的程序)通信协议。
- uwsgi 协议 是一个uWSGI服务器自有的协议,它用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型描述,用于与nginx等代理服务器通信,它与WSGI相比是两样东西。
- uWSGI 是实现了 uwsgi 和 WSGI 两种协议的 Web服务器。
gunicorn / uwsgi 都实现了 wsgi 协议(python web server gateway interface)。它们做的事情是协议转换,协议的一头是web app(如flask, django等framework的app),另一头是web server(如apache, nginx等),gunicore/uwsgi在默认的情况下都是同步模型,但都比通常的web framework实现得好。
gevent是利用协程(greenlet库)实现的一个高性能异步IO的库,它做的事情就是实现异步模型。
gunicorn/uwsgi可以配合gevent实现异步模型,并发能力进一步提高(在没有gevent的情况下,并发能力其实来自于web server)。
以上说的都是他们的本职工作,但实际上它们都做了更多的事,如:
gunicorn/uwsgi都实现了自己的web server,可以不依赖nginx等专业server独立提供web服务;
gevent实现了wsgi,可以不依赖gunicorn/uwsgi等完成协议转换(异步模型下的)。
Flask 自带的 HTTP 服务器能够跑起来,nginx + flask 也没啥问题,关键是如果要提升服务的并发能力,你要怎么处理,部署N个Flask进程,通过nginx代理到N个不同服务端口去,启停更新服务时,操作N个Flask进程?
使用 Gunicorn 最本质的原因就是提升服务处理能力,比如,使用 Gunicorn 部署多个 Flask 应用实例,提升服务并发处理能力;使用 Gunicorn + gevent worker,monkeypatch 掉 Python 原生网路库,进一步提升处理能力,遇到 IO 等待时,挂起当前请求,处理其它请求。
nginx+uwsgi 和 nginx+gunicorn 区别、如何部署
:https://www.jianshu.com/p/be2b587a900e
部署项目gunicorn、uwsgi性能,测试对比:优缺点:Flask 快速入门、教程、异步 Python web 框架
Flask Web 高并发
通常都是用 gunicorn 来解决 flask 后端部署并发的问题, 然而觉得自启多进程是为更优雅的高并发方式。这样就不需要gunicorn了。也没有额外的第三方部署工作,于是有了以下flask + gevent + multiprocess + wsgi的测试
gevent 是一个基于协程的 Python 网络库,它提供了异步 I/O 和协作式多任务处理的功能。pywsgi
模块是 gevent 的一个子模块,它提供了用于创建 WSGI 服务器的功能。WSGI (Web Server Gateway Interface) 是 Python 中定义的一种标准接口,用于将 Web 服务器和 Python Web 应用程序连接起来。
Flask 中使用 gevent 和 pywsgi:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
if __name__ == '__main__':
from gevent.pywsgi import WSGIServer
server = WSGIServer(('0.0.0.0', 8000), app)
server.serve_forever()
flask + gevent + multiprocess + wsgi
程序代码 app.py (window 运行报错,linux可以运行)
from gevent import monkey
from gevent.pywsgi import WSGIServer
monkey.patch_all()
import datetime
import os
from multiprocessing import cpu_count, Process
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/cppla", methods=['GET'])
def function_benchmark():
return jsonify(
{
"status": "ok",
"time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M'),
"pid": os.getpid()
}
), 200
def run(multi_process=None):
if not multi_process:
WSGIServer(('0.0.0.0', 8080), app).serve_forever()
else:
multi_server = WSGIServer(('0.0.0.0', 8080), app)
multi_server.start()
def server_forever():
multi_server.start_accepting()
multi_server._stop_event.wait()
for i in range(cpu_count()):
p = Process(target=server_forever)
p.start()
if __name__ == "__main__":
# 单进程 + 协程
run(False)
# 多进程 + 协程
# run(True)
Flask 调试模式
run() 方法适用于启动本地的开发服务器,但是 你每次修改代码后都要手动重启它。这样并不够优雅,而且 Flask 可以做到更 好。如果你启用了调试支持,服务器会在代码修改后自动重新载入,并在发生 错误时提供一个相当有用的调试器。
有两种途径来启用调试模式。一种是直接在应用对象上设置:
app.debug = True
app.run()
另一种是作为 run 方法的一个参数传入:
app.run(debug=True)
两种方法的效果完全相同。
注意:尽管交互式调试器在允许 fork 的环境中无法正常使用(也即在生产服务器 上正常使用几乎是不可能的),但它依然允许执行任意代码。这使它成为一 个巨大的安全隐患,因此它 绝对不能用于生产环境 。
想用其它的调试器? 参见 调试器操作 。
Flask 路由
现代 Web 应用的 URL 十分优雅,易于人们辨识记忆
如下,route() 装饰器把一个函数绑定到对应的 URL 上。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Index Page"
@app.route("/hello")
def hello():
return "Hello World"
if __name__ == "__main__":
app.run()
可以构造含有动态部分的 URL,也可以在一个函数上附着 多个规则。
路由的 endpoint
flask 在通过route装饰器添加路由时,endpoint参数默认是所装饰函数的名称。
在 Flask 中,endpoint
是用来给路由函数或者视图函数起一个唯一的名称标识符,以便在其他地方引用该端点。下面是一些 Flask 中 endpoint
用法示例:
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/', endpoint='endpoint_home')
def home():
return 'Home Page'
@app.route('/about', endpoint='endpoint_about')
def about():
return 'About Page'
# 在其他视图函数中引用端点
@app.route('/redirect')
def redirect_to_home():
return redirect(url_for('endpoint_home'))
if __name__ == '__main__':
app.run(debug=True)
pass
我们为 home
和 about
视图函数分别指定了 endpoint
参数,用于给它们起一个唯一的标识符。然后在 redirect_to_home
视图函数中,使用 url_for
函数结合端点名 'endpoint_home'
来生成对应的 URL。
使用蓝图(Blueprints)时的端点命名空间:
from flask import Flask, redirect, url_for, Blueprint
app = Flask(__name__)
bp_1 = Blueprint('users', __name__, url_prefix='/users')
@bp_1.route('/profile', endpoint='profile')
def profile():
return 'User Profile'
# 在其他视图函数中引用端点
@app.route('/redirect')
def redirect_to_profile():
return redirect(url_for('users.profile'))
app.register_blueprint(bp_1)
if __name__ == '__main__':
app.run(debug=True)
pass
这个示例中,我们定义了一个名为 'users'
的蓝图,并为 profile
视图函数指定了 endpoint='profile'
。然后在 redirect_to_profile
视图函数中,使用 'users.profile'
来引用蓝图下的端点。
上述示例中,我们分别使用了单个路由和蓝图来演示 endpoint
的用法。无论是单个路由还是蓝图,都可以通过指定 endpoint
参数来为视图函数提供唯一的标识符,并在其他地方引用它们。这样可以使代码更加具有可读性和可维护性。
Flask 变量规则
要给 URL 添加变量部分,你可以把这些特殊的字段标记为 <variable_name> , 这个部分将会作为命名参数传递到你的函数。规则可以用 <converter:variable_name> 指定一个可选的转换器。
示例:
from flask import Flask
app = Flask(__name__)
@app.route("/user/<username>")
def show_user_profile(username):
# show the user profile for that user
return f"User {username}"
@app.route("/post/<int:post_id>")
def show_post(post_id):
# show the post with the given id, the id is an integer
return f"Post {post_id}"
if __name__ == "__main__":
app.run()
转换器有下面几种:
int | 接受整数 |
float | 同 int ,但是接受浮点数 |
path | 和默认的相似,但也接受斜线 |
唯一 URL / 重定向行为
Flask 的 URL 规则基于 Werkzeug 的路由模块。这个模块背后的思想是基 于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。
以这两个规则为例:
@app.route('/projects/')
def projects():
return 'The project page'@app.route('/about')
def about():
return 'The about page'
虽然它们看起来着实相似,但它们结尾斜线的使用在 URL 定义 中不同。
- 第一种情况中,指向 projects 的规范 URL 尾端有一个斜线。这种感觉 很像在文件系统中的文件夹。访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范 URL 去。
- 第二种情况的 URL 结尾不带斜线,类似 UNIX-like 系统下的文件的 路径名。访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。这个行为使得在遗忘尾斜线时,允许关联的 URL 接任工作,与 Apache 和其它的服务器的行为并无二异。此外,也保证了 URL 的唯一,有助于 避免搜索引擎索引同一个页面两次。
请求上下文、应用上下文、g
flask上下文 request,session ,g的关系及区别(转):Flask 快速入门、教程、异步 Python web 框架
什么是上下文
实际上所谓的上下文,就是在该场景下,包括了一次请求相关的信息,包括了从客户(一般是浏览器)发送过来的数据,例如,登陆时使用的用户名密码;以及在中间处理过程中生成的数据,例如,每次请求时我们可能会需要新建一个数据库链接。
Flask 会在接收每次请求的时候将参数自动转换为相应的对象,也就是 request、session,一般来说上下文传递可以通过参数进行,这也就意味这每个需要该上下文的函数都要多增加一个入参,为了解决这一问题,Flask 提供了一个类似于全局变量的实现方式(如下会讲到这一参数是线程安全的)。
在多线程服务器中,通过线程池处理不同客户的不同请求,当收到请求后,会选一个线程进行处理,请求的临时对象(也就是上下文)会保存在该线程对应的全局变量中(通过线程 ID 区分),这样即不干扰其他线程,又使得所有线程都可以访问。
生命周期
- current_app 的生命周期最长,只要当前程序实例还在运行,都不会失效。
- request 和 g 的生命周期为一次请求期间,当请求处理完成后,生命周期也就完结了。
- session 就是传统意义上的 session,只要还未失效(用户未关闭浏览器、没有超过设定的失效时间),那么不同的请求会共用同样的 session。
请求上下文(request context)
请求上下文:保存客户端和服务器交互的数据。包括 request (请求对象,封装了 HTTP 请求的内容)、session (用于存储请求之间需要记住的值)。
请求上下文对象有:request、session
request
封装了HTTP请求的内容,针对的是http请求。
举例:user = request.args.get('user'),获取的是get请求的参数
session
用来记录请求会话中的信息,针对的是用户信息。
举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
示例:
from flask import Flask,current_app,url_for
app = Flask(__name__)
#应用上下文
#如果在视图函数外部访问,则必须手动推入一个app上下文到app上下文栈中
with app.app_context():
print(current_app.name) #context_demo
@app.route('/')
def index():
# 在视图函数内部可以直接访问current_app.name
print(current_app.name) #context_demo
return 'Hello World!'
@app.route('/list/')
def my_list():
return 'my_list'
# 请求上下文
with app.test_request_context():
# 手动推入一个请求上下文到请求上下文栈中
# 如果当前应用上下文栈中没有应用上下文
# 那么会首先推入一个应用上下文到栈中
print(url_for('my_list'))
if __name__ == '__main__':
app.run(debug=True)
应用上下文(application context)
字面意思是 "应用上下文", 但它不是一直存在的,它只是 request context 中的一个对 app 的代理人,所谓 local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app、g
- g 保存的是当前请求的 全局变量(临时存储的对象),不同的请求有不同的 g,通过不同的 thread id 区别
- current_app 当前激活程序的程序实例。
current_app:应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name 打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 1.应用的启动脚本是哪个文件,启动时指定了哪些参数
- 2.加载了哪些配置文件,导入了哪些配置
- 3.连了哪个数据库
- 4.有哪些 public 的工具类、常量
- 5. 应用跑在哪个机器上,IP多少,内存多大
示例:
from flask import Flask,current_app
app = Flask(__name__)
#如果在视图函数外部访问,则必须手动推入一个app上下文到app上下文栈中
#第一种方法
# app_context = app.app_context()
# app_context.push()
# print(current_app.name)
#第二种方法
with app.app_context():
print(current_app.name) #context_demo
@app.route('/')
def index():
# 在视图函数内部可以直接访问current_app.name
print(current_app.name) #context_demo
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
g 变量
g 之前是在请求上下文中的,现在被迁移到了应用上下文。
在应用上下文中,通过 flask.current_app、g 会返回当前的应用对象上下文,其中包含了当前的应用对象。
注意:g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的 thread id 区别
g 相当于 "单次请求" 中的 "全局变量",只能在单次请求中调用,和其他请求是互相隔离的
可以参考上下文管理部分,g的创建与销毁流程理解
示例 1:
from flask import Flask, g
app = Flask(__name__)
@app.route("/")
def index():
# g保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过线程id区别
g.test_msg = "g是一个应用上下文,但是只在单次请求中有效"
say_hello()
return "index page"
def say_hello():
test_msg = g.test_msg
print(f"Hello {test_msg}")
if __name__ == '__main__':
app.run()
示例 2
from flask import Flask, g
app = Flask(__name__)
@app.route('/index1')
def index1():
g.name = 'tom'
print(g.name)
return {'data': 'index1页面'}
@app.route('/index2')
def index2():
print(g.name)
return {'data': 'index2页面'}
if __name__ == '__main__':
app.run()
访问 index1,正常返回结果
访问 index2,直接报错,因为在 index2 这个请求中, 从来没有设置 g.name 这个属性,所以报错。
示例 3
from flask import Flask, g
app = Flask(__name__)
@app.before_request
def bfr():
g.name = 'jack'
@app.route('/index1')
def index1():
print(g.name)
return {'data': 'index1页面'}
@app.route('/index2')
def index2():
print(g.name)
return {'data': 'index2页面'}
if __name__ == '__main__':
app.run()
访问 index1、index2 都正常返回,因为每次请求前都会通过 bfr 函数设置 g.name 属性
示例:
import sqlite3
from flask import g
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = connect_to_database()
return db
@app.teardown_appcontext
def teardown_db(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
总 结
"应用上下文" 一般会被认为是一个应用的全局变量,所有请求都可以访问修改其中的内容;
而请求上下文则是请求内可访问的内容。
但事实上,这两者并不是全局与局部的关系,它们都处于一个请求的局部中,每个请求的 g 都是独立的,并且在整个请求内都是可访问修改的。
之所以有应用上下文,是因为 flask 设计之初,就是要支持多个应用。
- 1. application 指的就是当你调用app = Flask(__name__)创建的这个对象app;
- 2.request 指的是每次http请求发生时,WSGI server(比如gunicorn)调Flask.call()之后,在Flask对象内部创建的Request对象;
- 3.application 表示用于响应WSGI请求的应用本身,request 表示每次http请求;
- 4.application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以,也就会有多个request
为什么上下文需要放在栈中?
1.应用上下文:
Flask底层是基于werkzeug,werkzeug是可以包含多个app的,所以这时候用一个栈来保存,如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1那么app应该从栈中删除,方便其他代码使用下面的app。
2.应用上下文:
如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下文,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。
线程隔离的 g对象
g对象是在整个Flask应用运行期间都是可以使用的,并且它也是跟request一样是线程隔离的。这个对象是专门用来存储开发者自定义的一些数据,方便在整个Flask程序中都可以使用。一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不是通过传参的形式,这样更加方便。
线程安全
首先我们看看 request 是如何实现的,实际上之所以有 request 就是为了在多线程(或者协程)的环境下,各个线程可以使用各自的变量,不至于会混乱,接下来我们看看具体是如何实现的。
Python 提供了同样类似的线程安全变量保存机制,也就是 threading.local() 方法,而在 flask 中,使用的是 werkzeug 中的 Local 实现的,详细可以参考 werkzeug.pocoo.org/docs/local 。
总体来说,werkzeug 提供了与 threading.local() 相同的机制,不过是 threading 只提供了线程的安全,对于 greenlet 则无效。
flask 是多线程还是单线程
flask只是一个框架,到底多线程还是单线程,取决于你如何部署它。
内置的服务器 ( 默认单线程 )
flask内置了一个服务器,它不能用于生产环境,只能用于开发测试环境,这个服务器默认情况下是开启多线程的,下面这段代码可以演示这种情况
import threading
import time
from flask import Flask
app = Flask(__name__)
count = 0
@app.route('/')
def hello_world():
global count
count += 1
if count % 2 == 1:
print(threading.current_thread().ident, 'sleep 10')
time.sleep(10)
else:
print(threading.current_thread().ident, 'no sleep')
return 'Hello World!'
if __name__ == '__main__':
app.run()
hello_world 函数在响应请求时会输出线程的唯一标识,如果是第奇数个请求,则 sleep 10秒钟,使用curl 命令连续两次发出请求。执行结果:
这里千万不要用浏览器来做实验,两次请求都是相同的 url,浏览器可能会进行优化导致两次请求使用相同的 socket 连接。
如果 flask 内置的服务器使用的是多线程,那么第一个请求将延迟10秒钟返回,第二个请求则立马返回结果,不受第一次请求的影响,实验结果与前面的分析是一致的。
如果你希望 flask 用单线程来处理请求,那么需要设置 threaded 参数
if __name__ == '__main__':
app.run(threaded=False)
进入 run 函数,在 1181 行可以看到 threaded 默认值是 True
使用 uwsgi
uwsgi 是应用非常广泛的wsgi服务器,它可以指定进程的数量和线程的数量,借用其官方文档上的一个例子
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2
这个配置将启动4个进程,每个进程启动2个线程来提供服务,这意味着最多的时候可以同时服务8个请求。如果flask应用希望自己可以启动多线程,那么uwsgi需要配置 enable-threads 等于True,但如果你已经配置了--threads,且大于1,那么enable-threads默认开启。
python web 框架多进程部署下数据库连接问题
python 常用的 web 框架,诸如 flask,django,在生产部署时,都会选择多进程的部署方式,选用的中间件多为uwsgi或者gunicorn。
如果项目里使用了数据库,那么就要考虑数据库连接在多进程下的一些问题,这里以mysql数据库为例。
多进程下共用数据库连接
python连接mysql的客户端驱动库有很多种,例如pymysql,它们都提供了数据库连接池,连接池是多线程安全的,多进程下并不安全。
多线程的安全,是通过线程锁解决的,这非常容易做到,而多进程加锁则并不容易。
我所谓的多进程,是由主进程fork出来的子进程,如果主进程里创建了数据库连接池,随后fork出子进程,子进程在获取数据库连接时,两个子进程就有可能获得同一个数据库连接,这样就会引发问题。
一个数据库连接,本质上就是一个socket连接,建立socket连接后,得到一个打开的socket对象,当两个进程都用这同一个socket对象发送和接收数据时,就会引发异常。
不论是用uwsgi还是gunicorn,其原理都是相似的,创建出app后,fork子进程,这样做的目的是提供web服务的响应能力,work的数量需要合理配置。
对于这种部署方式,我一直都担心出现子进程共用同一个数据库连接的问题,直到最近,猛然间找到了问题的本质。
避免 fork 前使用连接
目前所用的数据库连接驱动库,都有一个惰性连接的特性。如果你设置连接池的大小是10,那么当程序启动后,连接池并没有真的被建立,只有当程序进行一次数据库操作时,才会真的去建立连接。当连接数量不够时,才会去新建连接,如果连接池里有空闲的连接,会直接拿来使用。
因此,只要保证在创建出app以后,不使用数据库连接进行任何操作,而是等到有真实的请求到来以后再进行数据库操作即可避免多进程共用数据库连接的问题。
只要父进程没有对数据库进行操作,父进程便不会创建数据库连接池。
当请求真实到达时,已经完成了fork动作,此时的子进程,并没有从父进程那里继承数据库连接池,因为父进程自己也没有创建连接池。
请求打到某个字进程,这个子进程在进行数据库操作时创建只属于自己的数据库连接池,不会受到其他子进程的干扰。
合理设置连接池大小
多进程部署模式下,如果你不开启多线程,那么一个子进程便只有一个线程,此时,你设置连接池的大小为1即可。设置的更大,也不会创建出更多的连接,因为对于单个子进程来说,有一个数据库连接就已经足够了。
为了提高响应能力,你开启线程,uwsgi和gunicorn都允许你这样做。那么你要根据线程的数量来设置连接池的大小,与其相等即可,多了也同样不起作用。
Flask 构造 URL ( test_request_context )
如果 Flask 能匹配 URL,那么 Flask 可以生成它们吗?当然可以。用 url_for() 来给指定的函数构造 URL。它接受函数名作为第一个 参数,也接受对应 URL 规则的变量部分的命名参数。未知变量部分会添加到 URL 末尾作为查询参数。
示例:
from flask import Flask, url_for
app = Flask(__name__)
@app.route("/")
def index():
pass
@app.route("/login")
def login():
pass
@app.route("/user/<username>")
def profile(username):
pass
with app.test_request_context():
print(url_for("index"))
print(url_for("login"))
print(url_for("login", next="/"))
print(url_for("profile", username="John Doe"))
执行结果:
/
/login
/login?next=/
/user/John%20Doe
通过 test_request_context 方法可以和 Python 的 shell 进行交互,它依然会告诉 Flask 要 表现为正在处理一个请求。
Flask 与 Python 的 shell 交互
从 Shell 创建一个合适的上下文,最简单的方法是使用 test_request_context 方法,此方法 会创建一个 RequestContext 对象:
>>> ctx = app.test_request_context()
一般来说,您可以使用 with 声明来激活这个请求对象, 但是在终端中,调用 push() 方法和 pop() 方法 会更简单:
>>> ctx.push()
从这里往后,您就可以使用这个请求对象直到您调用 pop 方法为止:
>>> ctx.pop()
仅仅创建一个请求上下文,您仍然不能运行请求发送前通常会运行的代码。 如果您在将连接数据库的任务分配给发送请求前的函数调用,或者在当前 用户并没有被储存在 g 对象里等等情况下,您可能无法 访问到数据库。
您可以很容易的自己完成这件事,仅仅手动调用 preprocess_request() 函数即可:
>>> ctx = app.test_request_context()
>>> ctx.push()
>>> app.preprocess_request()
请注意, preprocess_request() 函数可能会返回 一个响应对象。这时,忽略它就好了。
要关闭一个请求,您需要在请求后的调用函数(由 process_response() 函数激发)运行之前耍一些小小的把戏:
>>> app.process_response(app.response_class())
<Response 0 bytes [200 OK]>
>>> ctx.pop()
被注册为 teardown_request() 的函数将会在 上下文环境出栈之后自动执行。所以这是用来销毁请求上下文(如数据库 连接等)资源的最佳地点。
如果您喜欢在 Shell 里实验您的新点子,您可以创建一个包含你想要导入交互式 回话中的东西的的模块。在这里,您也可以定义更多的辅助方法用来完成一些常用的 操作,例如初始化数据库、删除一个数据表等。
把他们放到一个模块里(比如 shelltools 然后在 Shell 中导入它):
>>> from shelltools import *
Flask HTTP 方法 ( get、post 等 )
HTTP (与 Web 应用会话的协议)有许多不同的访问 URL 方法。默认情况下,路 由只回应 GET 请求,但是通过 route() 装饰器传递 methods 参数可以改变这个行为。
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
do_the_login()
else:
show_the_login_form()
HTTP 方法
- GET:浏览器告知服务器:只 获取 页面上的信息并发给我。这是最常用的方法。
- HEAD:浏览器告诉服务器:欲获取信息,但是只关心 消息头 。应用应像处理 GET 请求一样来处理它,但是不分发实际内容。在 Flask 中你完全无需 人工 干预,底层的 Werkzeug 库已经替你打点好了。
- POST:浏览器告诉服务器:想在 URL 上 发布 新信息。并且,服务器必须确保 数据已存储且仅存储一次。这是 HTML 表单通常发送数据到服务器的方法。
- PUT:类似 POST 但是服务器可能触发了存储过程多次,多次覆盖掉旧值。你可 能会问这有什么用,当然这是有原因的。考虑到传输中连接可能会丢失,在 这种 情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而 不破坏其它东西。因为 POST 它只触发一次,所以用 POST 是不可能的。
- DELETE:删除给定位置的信息。
- OPTIONS:给客户端提供一个敏捷的途径来弄清这个 URL 支持哪些 HTTP 方法。 从 Flask 0.6 开始,实现了自动处理。
在 HTML4 和 XHTML1 中,表单只能以 GET 和 POST 方法提交到 服务器。但是 JavaScript 和未来的 HTML 标准允许你使用其它所有的方法。此 外,HTTP 最近变得相当流行,浏览器不再是唯一的 HTTP 客户端。比如,许多版 本控制系统就在使用 HTTP。
Flask 静态文件
动态 web 应用也会需要静态文件,通常是 CSS 和 JavaScript 文件。理想状况下, 你已经配置好 Web 服务器来提供静态文件,但是在开发中,Flask 也可以做到。 只要在你的包中或是模块的所在目录中创建一个名为 static 的文件夹,在应用 中使用 /static 即可访问。
给静态文件生成 URL ,使用特殊的 'static' 端点名:
url_for('static', filename='style.css')
这个文件应该存储在文件系统上的 static/style.css 。
Flask 模板渲染
用 Python 生成 HTML 十分无趣,而且相当繁琐,因为你必须手动对 HTML 做转 义来保证应用的安全。为此,Flask 配备了 Jinja2 模板引擎。
你可以使用 render_template() 方法来渲染模板。你需要做的一 切就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展示如何 渲染模板的简例:
from flask import Flask, url_for
from flask import render_template
app = Flask(__name__)
@app.route("/hello/")
@app.route("/hello/<name>")
def hello(name=None):
return render_template("hello.html", name=name)
Flask 会在 templates 文件夹里寻找模板。
- 如果你的应用是个模块,这个文件夹应该与模块同级;
- 如果它是一个包,那么这个文件夹作为包的子目录:
关于模板,你可以发挥 Jinja2 模板的全部实例。更多信息请见 Jinja2 模板文档 。
模板实例:
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {
{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
在模板里,你也可以访问 request 、 session 和 g [1] 对象, 以及 get_flashed_messages() 函数。
模板继承让模板用起来相当顺手。如欲了解继承的工作机理,请跳转到 模板继承 模式的文档。最起码,模板继承能使特定元素 (比如页眉、导航栏和页脚)可以出现在所有的页面。
自动转义功能默认是开启的,所以如果 name 包含 HTML ,它将会被自动转 义。如果你能信任一个变量,并且你知道它是安全的(例如一个模块把 Wiki 标 记转换为 HTML),你可以用 Markup 类或 |safe 过滤 器在模板中把它标记为安全的。在 Jinja 2 文档中,你会看到更多的例子。
这里是一个 Markup 类如何使用的简单介绍:
>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
u'Marked up \xbb HTML'
在 0.5 版更改: 自动转义不再在所有模板中启用。下列扩展名的模板会触发自动转义: .html 、 .htm 、.xml 、 .xhtml 。从字符串加载 的模板会禁用自动转义。
不确定 g 对象是什么?它允许你按需存储信息, 查看( g )对象的文档和 在 Flask 中使用 SQLite 3 的文 档以获取更多信息。 |
Flask 访问请求数据
对于 Web 应用,客户端发送给服务器 的数据交互至关重要。在 Flask 中 由全局的 request 对象来提供这些信息。如果你有一定的 Python 经验,你会好奇,为什么这个对象是全局的,为什么 Flask 还能保证 线程安全。答案是环境作用域:
环境局部变量
理解其工作机制及如何利用环境局部变量实现自动化测试
Flask 中的某些对象是全局对象,但却不是通常的那种。这些对象实际上是特定 环境的局部对象的代理。虽然很拗口,但实际上很容易理解。
想象一下处理线程的环境。一个请求传入,Web 服务器决定生成一个新线程( 或者别的什么东西,只要这个底层的对象可以胜任并发系统,而不仅仅是线程)。 当 Flask 开始它内部的请求处理时,它认定当前线程是活动的环境,并绑定当前的应用和 WSGI 环境到那个环境上(线程)。它的实现很巧妙,能保证一个应用调用另一个应用时不会出现问题。
所以,这对你来说意味着什么?除非你要做类似单元测试的东西,否则你基本上 可以完全无视它。你会发现依赖于一段请求对象的代码,因没有请求对象无法正 常运行。解决方案是,自行创建一个请求对象并且把它绑定到环境中。单元测试 的最简单的解决方案是:用 test_request_context() 环 境管理器。结合 with 声明,绑定一个测试请求,这样你才能与之交互。下面 是一个例子:
from flask import Flask
from flask import request
app = Flask(__name__)
with app.test_request_context('/hello', method='POST'):
# now you can do something with the request until the
# end of the with block, such as basic assertions:
assert request.path == '/hello'
assert request.method == 'POST'
另一种可能是:传递整个 WSGI 环境给 request_context() 方法:
from flask import Flask
from flask import request
app = Flask(__name__)
with app.request_context(environ):
assert request.method == 'POST'
请求对象 ( from flask import request )
API 章节对请求对象作了详尽阐述(参见 request ),因此这 里不会赘述。此处宽泛介绍一些最常用的操作。
当前请求的 HTTP 方法可通过 method 属性来访问。通 过:attr:~flask.request.form 属性来访问表单数据( POST 或 PUT 请求 提交的数据)。这里有一个用到上面提到的那两个属性的完整实例:
from flask import Flask
from flask import request
from flask import render_template
app = Flask(__name__)
@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
当访问 form 属性中的不存在的键会发生什么?会抛出一个特殊的 KeyError 异常。你可以像捕获标准的 KeyError 一样来捕获它。 如果你不这么做,它会显示一个 HTTP 400 Bad Request 错误页面。所以,多数 情况下你并不需要干预这个行为。
你可以通过 args 属性来访问 URL 中提交的参数 ( ?key=value ):
searchword = request.args.get('q', '')
推荐用 get 来访问 URL 参数或捕获 KeyError ,因为用户可能会修 改 URL,向他们展现一个 400 bad request 页面会影响用户体验。
欲获取请求对象的完整方法和属性清单,请参阅 request 的 文档。
request.args # url 中传递过来的值
request.form # 获取 form 表单json格式数据
request.json # 传递的json格式的body
request.data # 原始的字节类型
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/index")
def index():
user = request.args.get("name")
age = request.args.get("age")
print(user, age)
result = {"code": 1, "message": "success"}
return jsonify(result)
# # n2=xxxx&n1=12312
# n1={"k1":123}&n2=[111,2233]
@app.route("/home", methods=["POST"])
def home():
print(request.args)
print(request.form) # n2=xxxx&n1=12312
print(request.json) # n1={"k1":123}&n2=[111,2233] + Content-Type:application/json
result = {"code": 1, "message": "success"}
return jsonify(result)
if __name__ == '__main__':
app.run(host="127.0.0.1", port=9999)
文件上传
用 Flask 处理文件上传很简单。只要确保你没忘记在 HTML 表单中设置 enctype="multipart/form-data" 属性,不然你的浏览器根本不会发送文件。
已上传的文件存储在内存或是文件系统中一个临时的位置。你可以通过请求对象 的 files 属性访问它们。每个上传的文件都会存储在 这个字典里。它表现近乎为一个标准的 Python file 对象,但它还有 一个 save() 方法,这个方法 允许你把文件保存到服务器的文件系统上。这里是一个用它保存文件的例子:
from flask import 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')
...
如果你想知道上传前文件在客户端的文件名是什么,你可以访问 filename 属性。但请记住, 永远不要信任这个值,这个值是可以伪造的。如果你要把文件按客户端提供的 文件名存储在服务器上,那么请把它传递给 Werkzeug 提供的 secure_filename() 函数:
from flask import request
from werkzeug import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename))
...
一些更好的例子,见 上传文件 模式。
from flask import *
app = Flask(__name__)
# 限制文件最大大小为10MB
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024
"""
ce_shi.html
<!doctype html>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<h1>File Upload</h1>
<form method="POST" action="" enctype="multipart/form-data">
<p><input type="file" name="file"></p>
<p><input type="submit" value="Submit"></p>
</form>
<br>
<form method="POST" action="" enctype="multipart/form-data">
<p><input type="file" name="file" multiple></p>
<p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
"""
@app.route("/", methods=["POST", "GET"])
def func_upload():
if request.method == "GET":
return render_template("zzz_html.html")
if request.method == "POST":
# 如果我们为<input>标签添加“multiple”属性,那么此时就允许多个文件上传,
# 此时使用:request.files.getlist("发送标签name")返回的就是一个列表
file = request.files.get("file")
if file.filename:
print(file)
# data_dict = json.load(file)
file.save(file.filename)
return "提交成功"
@app.route("/download")
def func_download():
file_path = "项目.pptx"
return send_file(file_path, as_attachment=True)
if __name__ == "__main__":
app.run(debug=True)
Cookies
可以通过 cookies 属性来访问 Cookies,用 响应对象的 set_cookie 方法来设置 Cookies。请 求对象的 cookies 属性是一个内容为客户端提交的 所有 Cookies 的字典。如果你想使用会话,请不要直接使用 Cookies,请参 考 会话 一节。在 Flask 中,已经注意处理了一些 Cookies 安全 细节。
读取 cookies:
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.
存储 cookies:
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
注意:Cookies 是设置在响应对象上的。由于通常视图函数只是返 回字符串,之后 Flask 将字符串转换为响应对象。如果你要显式地转换,你 可以使用 make_response() 函数然后再进行修改。
有时候你想设置 Cookie,但响应对象不能醋在。这可以利用 延迟请求回调 模式实现。
为此,也可以阅读 关于响应 。
Flask 重定向、错误
可以用 redirect() 函数把用户重定向到其它地方。放弃请 求并返回错误代码,用 abort() 函数。这里是一个它们如何 使用的例子:
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
这是一个相当无意义的例子因为用户会从主页重定向到一个不能访问的页面 (401 意味着禁止访问),但是它展示了重定向是如何工作的。
默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面, 可以使用 errorhandler() 装饰器:
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
注意 render_template() 调用之后的 404 。这告诉 Flask,该页的错误代码是 404 ,即没有找到。默认为 200,也就是一切 正常。
Flask 关于响应
视图函数的返回值会被自动转换为一个响应对象。如果返回值是一个字符串, 它被转换为该字符串为主体的、状态码为 200 OK``的 、 MIME 类型是 ``text/html 的响应对象。Flask 把返回值转换为响应对象的逻辑是这样:
- 如果返回的是一个合法的响应对象,它会从视图直接返回。
- 如果返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
- 如果返回的是一个元组,且元组中的元素可以提供额外的信息。这样的 元组必须是 (response, status, headers) 的形式,且至少包含一 个元素。 status 值会覆盖状态代码, headers 可以是一个列表或 字典,作为额外的消息标头值。
- 如果上述条件均不满足, Flask 会假设返回值是一个合法的 WSGI 应用 程序,并转换为一个请求对象。
如果你想在视图里操纵上述步骤结果的响应对象,可以使用 make_response() 函数。
例如,你有这样一个视图:
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'), 404
你只需要把返回值表达式传递给 make_response() ,获取结果对象并修改,然后再返回它:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
Flask 会话 ( session )
除请求对象之外,还有一个 session 对象。它允许你在不 同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名。这意味着用户可以查看你 Cookie 的内容,但却不 能修改它,除非用户知道签名的密钥。
要使用会话,你需要设置一个密钥。这里介绍会话如何工作:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
@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 action="" 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"))
# set the secret key. keep this really secret:
app.secret_key = "A0Zr98j/3yX R~XHH!jmN]LWX/,?RT"
这里提到的 escape() 可以在你模板引擎外做转义(如同本例)。
如何生成强壮的密钥
随机的问题在于很难判断什么是真随机。一个密钥应该足够随机。你的操作系统可以基于一个密钥随机生成器来生成漂亮的随机值,这个值可以用来做 密钥:
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
把这个值复制粘贴进你的代码中,你就有了密钥。
使用基于 cookie 的会话需注意: Flask 会将你放进会话对象的值序列化至 Cookies。如果你发现某些值在请求之间并没有持久存在,然而确实已经启用了 Cookies,但也没有得到明确的错误信息。这时,请检查你的页面响应中的 Cookies 的大小,并与 Web 浏览器所支持的大小对比。
请求钩子
在客户端和服务器之间进行交互的过程中,有些准备工作和扫尾工作需要处理,比如:在请求开始的时候,建立数据库连接,在请求结束的时候指定数据交互的格式,为了让每个视图函数避免编写重复的功能代码,Flask 提供了通用设施的过程,即__请求钩子__。
请求钩子是通过装饰器的形式实现的,Flask支持四中请求钩子:
- 1、app.before_first_request:在处理第一个请求前运行
- 2、app.before_request:在每次请求之前运行
- 3、app.after_request:如果没有未处理的异常抛出,在每次请求后运行(视图函数正常退出)
- 4、app.teardown_request:每次请求后运行,即使有未处理的异常抛出
请求钩子与 django 中的中间件很类似。
示例:
from flask import Flask, request, url_for, abort
app = Flask(__name__)
@app.route("/index")
def index():
print("index page is running")
a = 1 / 0
return "Index page"
@app.route("/hello")
def hello():
print("hello page is running")
return "Hello page"
@app.before_first_request
def handle_before_first_request():
# 在第一次请求处理之前先执行
print("handle_before_first_request is running")
@app.before_request
def handle_before_request():
# 在每次请求处理之前被执行
print("handle_before_request is running")
@app.after_request
def handle_after_request(response):
# 在每次请求(视图函数)处理之后都被执行,前提是视图函数没有异常
# 在请求之后,会接收一个参数,这个参数是前面的请求处理完毕后返回
# 的响应数据,如果需要对响应做额外处理,可以在这里进行
print(f"handle_after_request is running, the response is {response}")
return response
@app.teardown_request
def handle_teardown_request(response):
# 在每次请求(视图函数)处理之后都会被执行,无论视图函数是否异常
# 每一次请求之后都会调用,会接收一个参数,这个参数是服务器出现的
# 错误信息,工作在非调试模式下,当时图函数出现异常时,才会被执行
print(f"handle_teardown_request is running, the response is {response}")
if request.path == url_for("index"):
print("在请求钩子中判断请求的视图逻辑:index")
elif request.path == url_for("hello"):
print("在请求钩子中判断请求的视图逻辑:hello")
return response
if __name__ == '__main__':
app.run()
flak_script 脚本扩展的使用
导包:
- from flask_script import Manager
使用:
- 1、创建 Manager 对象,把当前的 flask 应用传入 Manager类 中
manager = Manager(app) - 2、使用管理类来启动flask
manager.run()
在终端中使用:python 文件名.py --help 查看可用的命令:
- python 文件名.py runserver 启动服务器
- python 文件名.py runserver --help 查看可用命令
- python 文件名.py runserver -h IP地址 -p 端口号 指定ip端口号启动
- python 文件名.py shell 在这个 shell 终端中文件里的包已经全部导进来了,不需要在重新导入了
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
@app.route("/index")
def index():
return "Index page"
if __name__ == '__main__':
manager.run()
渲染模板
在Flask中使用render_template渲染模板
模板变量与django中类似,其中操作字典有两种方式:{ {my_dict.键名}}或{ {my_dict[“键名”]}}
render_template(“需要渲染的模板.html”, xx=value, xxx=value, …) 其中value可以是字典、列表、int、str…
过滤器(支持链式使用过滤器):
1、字符串过滤器:
- 1-1、safe:禁止转义
- 1-2、capitalize:把变量值的首字母转换成大写,其余字母转小写
- 1-3、lower:把变量值转换成小写
- 1-4、upper:把变量值转换成大写
- 1-5、title:把变量值中的每个词的首字母转换成大写
- 1-6、trim:把变量值的首尾空格去掉
- 1-7、reverse:字符串翻转 --> hello–olleh
- 1-8、format:格式化数据 --> { { ‘%s is %d’ | format(‘name’, 18) }}
- 1-9、striptags:渲染之前把变量值中所有的HTML标签都删掉
2、列表过滤器:
- 2-1、first:取第一个元素
- 2-2、last:取最后一个元素
- 2-3、length:获取列表长度
- 2-4、sum:列表求和
- 2-5、sort:列表排序
3、自定义过滤器:自定义的过滤器名称如果与内置的过滤器重名,会覆盖掉内置的过滤器,自定义过滤器有两种方式。
- 方式一:
1)定义一个过滤器函数
2)使用app.add_template_filter(自定义过滤器的函数名, 过滤器名(自己取的名,不传这个参数,默认函数名)) - 方式二:使用装饰器装饰自定义的过滤器函数
@app.template_filter("过滤器名") # 如果不传过滤器名,默认函数名
def 自定义的过滤器函数:
...
from flask import Flask, render_template
app = Flask(__name__)
# 渲染模板
@app.route("/test")
def test():
data = {
"name": "zhaosi",
"age": 20,
"my_dict": {"city": "beijing"},
"my_list": [1, 2, 3, 4, 5, 6, 7],
"my_int": 3
}
return render_template("test.html", **data)
# 自定义过滤器
# 方式一
def li_step_1(li):
# 列表元素隔一个取一个
return li[::2]
app.add_template_filter(li_step_1, "li_1")
# 方式二
@app.template_filter("li_2")
def li_step_2(li):
# 列表元素各两个取一个
return li[::3]
if __name__ == '__main__':
app.run()
# html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板</title>
</head>
<body>
<p>name:{
{ name }}</p>
<p>age:{
{ age }}</p>
<p>字典类型数据:</p>
<p>my_dict: city:{
{ my_dict.city }}</p>
<p>my_dict: city:{
{ my_dict["city"] }}</p>
<p>列表类型数据:</p>
<p>my_list:{
{ my_list }}</p>
<p>int类型数据:</p>
<p>my_int:{
{ my_int }}</p>
<p>my_list[my_int]:{
{ my_list[my_int] }}</p>
<p>模板变量相加减</p>
<p>my_list[0] + my_list[1] = {
{ my_list[0] + my_list[1] }}</p>
<p>字符串变量相加</p>
<p>name + city = {
{ name + my_dict["city"] }}</p>
<p>过滤器的使用,支持链式操作,即:xxx|过滤器1|过滤器2|...</p>
<p>‘ hello flask ’去首尾空格,并首字母大写:{
{ " hello flask "|trim|title }}</p>
<p>自定义的转换器的使用:</p>
<p>列表元素隔一个取一个:{
{ my_list|li_1 }}</p>
<p>列表元素隔两个取一个:{
{ my_list|li_2 }}</p>
</body>
</html>
xss 攻击
xss攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器执行,达到攻击的目的,形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发送虚假信息,可以删除用户的日志等等,有时候还和其他攻击方式同时实施比如SQL注入攻击服务器和数据库、Click劫持、相对链接劫持等实施钓鱼,它带来的危害是巨大的,是web安全的头号大敌。
XSS攻击是在用户的浏览器上执行的,其形成过程则是在服务器端页面渲染时,注入了恶意的HTML代码导致的。
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/xss", methods={"GET", "POST"})
def xss():
text = ""
if request.method == "POST":
text = request.form.get("text")
return render_template("xss攻击.html", text=text)
if __name__ == '__main__':
app.run()
# html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSS攻击</title>
</head>
<body>
<form method="post">
<textarea name="text"></textarea>
<input type="submit" value="提交">
</form>
{# 转义:默认是开启转义的,即:你输入什么,最后返回什么,这是为了防止xss攻击 #}
{#
比如:输入<script>alert("xss attack")</script>
转义成html语言后为: <script>alert("xss attack")</script>
那么在浏览器中显示也是:<script>alert("xss attack")</script>
#}
{
{ text }} {# 默认是开启转义了,渲染模板时,执行的是 <script>alert("xss attack")</script> #}
{
{ text|safe }} {# 关闭转义,渲染模板时,执行的是 <script>alert("xss attack")</script> ,这是就会出现一个弹窗 #}
</body>
</html>
Flask 消息闪现 ( 请求后,立马反馈 )
反馈,是良好的应用和用户界面的重要构成。如果用户得不到足够的反馈,他们 很可能开始厌恶这个应用。 Flask 提供了消息闪现系统,可以简单地给用户反馈。 消息闪现系统通常会在请求结束时记录信息,并在下一个(且仅在下一个)请求 中访问记录的信息。展现这些消息通常结合要模板布局。
使用 flash() 方法可以闪现一条消息。要操作消息本身,请使用 get_flashed_messages() 函数,并且在模板中也可以使用。完整 的例子见 消息闪现 部分。
Flask 日志记录
有时候你会处于这样一种境地,你处理的数据本应该是正确的,但实际上不是。 比如,你会有一些向服务器发送请求的客户端代码,但请求显然是畸形的。这可 能是用户篡改了数据,或是客户端代码的粗制滥造。大多数情况下,正常地返回 400 Bad Request 就可以了,但是有时候不能这么做,并且要让代码继续运 行。
你可能依然想要记录下,是什么不对劲。这时日志记录就派上了用场。从 Flask 0.3 开始,Flask 就已经预置了日志系统。
这里有一些调用日志记录的例子:
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
附带的 logger 是一个标准日志类 Logger ,所以更多信息请查阅 logging 的文档 。
Flask 整合 WSGI 中间件
如果你想给你的应用添加 WSGI 中间件,你可以封装内部 WSGI 应用。例如若 是你想用 Werkzeug 包中的某个中间件来应付 lighttpd 中的 bugs ,可以这 样做:
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
Flask 部署到 Web 服务器
部署 Flask 应用
- 在 Heroku 上部署 Flask
- 在 dotCloud 上部署 Flask 附 Flask 的具体说明
托管 Flask 应用的其它选择:
- 在 Webfaction 上部署 Flask
- 在 Google App Engine 上部署 Flask
- 用 Localtunnel 共享你的本地服务器
如果你有自己的主机,并且准备自己托管,参见 部署选择 章节。
- mod_wsgi (Apache)
- 安装 mod_wsgi
- 创建一个 .wsgi 文件
- 配置 Apache
- 故障排除
- 自动重加载支持
- 使用虚拟环境
- 独立 WSGI 容器
- Gunicorn
- Tornado
- Gevent
- 代理设置
- uWSGI
- 用 uwsgi 启动你的应用
- 配置 nginx
- FastCGI
- 创建一个 .fcgi 文件
- 配置 lighttpd
- 配置 nginx
- 运行 FastCGI 进程
- 调试
- CGI
- 创建一个 .cgi 文件
- 服务器配置
2、Flask 教程
-
Flask 介绍 Flaskr
-
Flask 创建文件夹
-
Flask 数据库模式
-
Flask 应用设置代码
-
Flask 数据库连接
-
Flask 创建数据库
-
Flask 视图函数
-
Flask 模板
-
Flask 添加样式
-
Flask 应用测试
3、Flask 模板
-
Flask Jinja 配置
-
Flask 标准上下文
-
Flask 标准过滤器
-
Flask 控制自转义
-
Flask 注册过滤器
-
Flask 上下文处理器
4、测试 Flask 应用
-
Flask 应用程序
-
Flask 测试的大框架
-
Flask 第一个测试
-
Flask 登录和登出
-
Flask 测试消息的添加
-
Flask 其他测试技巧
-
Flask 伪造资源和上下文
-
Flask 保存上下文
-
Flask 访问和修改 Sessions
5、记录 应用 错误
-
Flask 错误邮件
-
Flask 记录带文件
-
Flask 控制日志格式
-
Flask 其他的库
6、Flask 配置处理
-
Flask 配置基础
-
Flask 内置的配置值
-
Flask 从文件配置
-
Flask 配置的最佳实践
-
Flask 开发/生产
-
Flask 实例文件夹
方式一,使用字典方式配置
app.config['SESSION_COOKE_NAME'] = 'session_liling'
方式二,引入文件,设置
from flask import Flask
app = Flask(__name__)
app.config.from_pyfile('settings.py') # 引用settings.py中的AAAA
print(app.config['AAAA']) # 123# settings.py
AAAA = 123
方法三,使用环境变量设置,推荐使用
from flask import Flask
app = Flask(__name__)
import os
os.environ['FLASK-SETTINGS'] = 'settings.py'
app.config.from_envvar('FLASK-SETTINGS')
方式四,通过对象方式导入使用,可根据不同环境选择不同的配置,推荐使用
from flask import Flask
app = Flask(__name__)
app.config.from_object('settings.BaseConfig')
print(app.config['NNNN']) # 123
# settings.pyclass BaseConfig(object): # 公用配置
NNNN = 123
class TestConfig(object):
DB = '127.0.0.1'
class DevConfig(object):
DB = '192.168.1.1'
class ProConfig(object):
DB = '47.18.1.1'
不同的文件 引用配置
from flask import Flask,current_app
app = Flask(__name__)
app.secret_key = 'adfadsfhjkhakljsdfh'
app.config.from_object('settings.BaseConfig')
@app.route('/index')
def index():
print(current_app.config['NNNN'])
return 'xxx'
if __name__ == '__main__':
app.run()
instance_path、instance_relative_config
from flask import Flask,current_app
app = Flask(__name__,instance_path=None,instance_relative_config=False)
# 默认 instance_relative_config = False 那么 instance_relative_config和instance_path都不会生效
# instance_relative_config=True,instance_path才会生效,app.config.from_pyfile('settings.py')将会失效
# 配置文件找的路径,按instance_path的值作为配置文件路径
# 默认instance_path=None,None会按照当前路径下的instance文件夹为配置文件的路径
# 如果设置路径,按照设置的路径查找配置文件。
app.config.from_pyfile('settings.py')
@app.route('/index')
def index():
print(current_app.config['NNNN'])
return 'xxx'
if __name__ == '__main__':
app.run()
7、Flask 信号
-
Flask 订阅信号
-
Flask 创建信号
-
Flask 发送信号
-
Flask 信号与 Flask 的请求上下文
-
Flask 基于装饰器的信号订阅
-
Flask 核心信号
8、Flask 实时 视图
-
Flask 基本原则
-
Flask 方法提示
-
Flask 基于调度的方法
-
Flask 装饰视图
-
Flask 用于 API 的方法视图
9、Flask 应用上下文、请求上下文
所谓上下文,像考试题目根据上下文,回答一下问题。
程序中,泛指的外部环境,像wsgi来的网络请求,而且通常只有上文。
flask中的上下文,被使用在 current_app,session,request 上。
应用上下文
-
Flask 应用上下文的作用
-
Flask 创建应用上下文
-
Flask 应用上下文局部变量
-
Flask 上下文用法
Flask 请求上下文
-
Flask 深入上下文作用域
-
Flask 上下文如何工作
-
Flask 回调和错误
-
Flask 销毁回调
-
Flask 留意代理
-
Flask 错误是的上下文保护
10、Flask 用蓝图实现模块化的应用
-
Flask 为什么使用蓝图?
-
Flask 蓝图的设想
-
Flask 我的第一个蓝图
-
Flask 注册蓝图
-
Flask 蓝图资源
-
Flask 构造 URL
模块化
随着 flask 程序越来越复杂,我们需要对程序进行模块化的处理。
举例来说:假设有一个博客程序,前台界面需要的路由为:首页、列表、详情等页面
源程序app.py文件:
from flask import Flask
app=Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/list')
def list():
return 'list'
@app.route('/detail')
def detail():
return 'detail'
if __name__=='__main__':
app.run()
如果博主需要编辑博客,要进入后台进行处理:后台主页,编辑,创建,发布博客
改进后程序:
from flask import Flask
app=Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/list')
def list():
return 'list'
@app.route('/detail')
def detail():
return 'detail'
@app.route('/')
def admin_home():
return 'admin_home'
@app.route('/new')
def new():
return 'new'
@app.route('/edit')
def edit():
return 'edit'
@app.route('/publish')
def publish():
return 'publish'
if __name__=='__main__':
app.run()
这样就使得我们在一个py文件中写入了很多路由,将来维护代码会非常麻烦,此时,同学们就考虑到了模块化的处理方式,将admin相关的路由写到一个admin.py文件中,那我们就顺着这个思路走下去
修改后的代码:
app.py
from flask import Flask
app=Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/list')
def list():
return 'list'
@app.route('/detail')
def detail():
return 'detail'
if __name__=='__main__':
app.run()
admin.py
@app.route('/')
def admin_home():
return 'admin_home'
@app.route('/new')
def new():
return 'new'
@app.route('/edit')
def edit():
return 'edit'
@app.route('/publish')
def publish():
return 'publish'
发现app.py文件中的app直接报错,代码无法继续写下去,所以在flask程序中,使用传统的模块化是行不通的,需要flask提供一个特有的模块化处理方式,flask内置了一个模块化处理的类,即Blueprint
Blueprint 概念
Flask 可以通过Blueprint来组织URL以及处理请求。通俗的说:将众多的视图(view)函数,拆分到不用的文件中。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个应用可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名
- 在一个应用中,一个模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
初识蓝图
蓝图/Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效。蓝图作用就是注册不同的模块。
使用蓝图可以分为三个步骤
1,创建一个蓝图对象
admin=Blueprint('admin',__name__)
2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
@admin.route('/')
def admin_home():
return 'admin_home'
3,在应用对象上注册这个蓝图对象
app.register_blueprint(admin,url\_prefix='/admin')
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
运行机制
- 蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
- 当在应用对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
- 然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
- 当执行应用对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的路由表
蓝图的 url 前缀
- 当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)
-
在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
- url_for
url_for('admin.index') # /admin/
注册静态路由
和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
下面的示例将蓝图所在目录下的static_admin目录设置为静态目录
admin = Blueprint("admin",__name__,static_folder='static_admin')
app.register_blueprint(admin,url_prefix='/admin')
现在就可以使用/admin/static_admin/ 访问static_admin目录下的静态文件了 定制静态目录URL规则 :可以在创建蓝图对象时使用 static_url_path 来改变静态目录的路由。下面的示例将为 static_admin 文件夹的路由设置为 /lib
admin = Blueprint("admin",__name__,static_folder='static_admin',static_url_path='/lib')
app.register_blueprint(admin,url_prefix='/admin')
设置模版目录
蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录
admin
=
Blueprint(
'admin'
,__name__,template_folder
=
'my_templates'
)
注:如果在 templates 中存在和 my_templates 同名文件,则系统会优先使用 templates 中的文件 参考链接:python - flask blueprint template folder - Stack Overflow
11、Flask 扩展
-
Flask 寻找扩展
-
Flask 使用扩展
-
Flask 0.8 以前
12、Flask 有用的代码 片段
某些东西非常通用,以至于你有很大的机会在绝大部分 Web 应用中,都能找到 他们的身影。例如相当多的应用在使用关系数据库而且包含用户注册和认证模块。 在这种情况下,请求开始之前,他们会打开数据库连接、获得当前已经登陆的用户 信息。在请求结束的时候,数据库连接又会被关闭。
这章提供了一些由用户贡献的代码片段和模板来加速开发 Flask Snippet Archives.
- 大型应用
- 简单的包
- 与蓝图一起工作
- 应用程序的工厂函数
- 基础的工厂函数
- 使用应用程序
- 工厂函数的改进
- 应用调度
- 如何使用此文档
- 合并应用
- 通过子域名调度
- 使用路径来调度
- 使用 URL 处理器
- 国际化的应用程序 URL
- 多国语言化的 Blueprint URL
- 部署和分发
- 基础的安装脚本
- 分发代码
- 声明依赖关系
- 安装 / 开发
- 使用 Fabric 部署
- 创建第一个 Fabfile
- 运行 Fabfile
- WSGI 文件
- 配置文件
- 第一次部署
- 下一步操作
- 在 Flask 中使用 SQLite 3
- 按需连接
- 简化查询
- 初始化数据库模型
- 在 Flask 中使用 SQLAlchemy
- Flask-SQLAlchemy 扩展
- 显式调用
- 手动实现 ORM
- SQL 抽象层
- 上传文件
- 一点点介绍
- 改进上传功能
- 上传进度条
- 更简单解决方案
- 缓存
- 配置缓存
- 使用缓存
- 视图装饰器
- 过滤未登录用户的装饰器
- 缓存装饰器
- 模板装饰器
- 终端装饰器
- 使用 WTForms 进行表单验证
- 表单
- 在视图里
- 在模板中使用表单
- 模板继承
- 基础模板
- 子模板
- 消息闪现
- 简单的闪现
- 分类闪现
- 过滤闪现消息
- 用 jQuery 实现 Ajax
- 加载 jQuery
- 我的站点在哪?
- JSON 视图函数
- HTML 部分
- 自定义错误页面
- 通常的错误代码
- 错误处理器
- 延迟加载视图
- 转换到中央 URL 映射
- 延迟加载
- 在 Flask 中使用 MongoKit
- 显式调用
- PyMongo 兼容层
- 添加 Favicon
- 参考
- 数据流
- 基本使用
- 在模板中生成流
- 延迟请求回调
- 装饰器
- 调用延迟函数
- 一个实际应用的例子
- 添加 HTTP Method Overrides
- 请求内容校验码
- 基于 Celery 的后台任务
- 安装 Celery
- 配置 Celery
- 最简示例
- 运行 Celery 职程
13、Flask 源码、API
-
Flask 阅读源码
-
Flask 钩子,继承
-
Flask 继承
-
Flask 用中间件包装
-
Flask 分支
-
Flask 像专家一样扩大规模
-
Flask 与社区对话
-
Flask 应用对象
-
Flask 会话接口
-
Flask 消息闪现
-
Flask 模板渲染
-
Flask 信号
-
Flask 基于类的视图
-
Flask 视图函数选项
-
Flask 显式的应用对象
-
Flask 路由系统
-
Flask 某个模板引擎
-
Flask 微与依赖
-
Flask 线程局域变量
-
Flask 是什么,不是什么?
-
Flask XHTML 的历史
-
Flask HTML5 的历史
-
Flask HTML vs. XHTML
-
Flask “严格”意味着什么?
-
Flask HTML5 中的新技术
-
Flask 应该使用什么?
-
Flask 跨站脚本攻击(XSS)
-
Flask 跨站请求伪造(CSRF)
-
Flask JSON 安全
-
Flask 自动转换
-
Flask 金科玉律
-
Flask 自行编解码
-
Flask 配置编辑器
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/105469.html