防御向地验证 Flask 调试器的暴露面:flask run --debug 时错误页面带出源码、路径、异常类型和交互式控制台;不开 debug 只有通用 500;gunicorn 下没有 Werkzeug 调试器、堆栈留在服务端日志;调试服务器默认只监听 127.0.0.1;--debug 参数压过 FLASK_DEBUG=0。
在真实的 Chrome 里验证安全响应头:没有头时内联脚本与 MIME 不符的脚本都执行、能被别的站点 iframe 嵌入、Referer 带来源;加 CSP(nonce)、nosniff、X-Frame-Options、Referrer-Policy 后分别被拦下;HSTS 只能在 HTTPS 响应里发,代理后面要靠 ProxyFix(x_proto=1) 才能判断。
用真实的 nginx(自签证书终止 TLS)加 gunicorn 实测 HTTPS 与反向代理:Flask 看不到 https 时外部链接与重定向变成 http;gunicorn 默认只翻译 X-Forwarded-Proto;ProxyFix 与 nginx 的 X-Forwarded-* 配套;Host 要用 $http_host;nginx 的 client_max_body_size 先于 Flask 返回 413;proxy_buffering 让流式响应一次性到达。
用真实的 nginx 与 gunicorn 实测信任代理头的配置:nginx 覆盖或追加 X-Forwarded-For、ProxyFix 的 x_for 层数、多信任一层会取到客户端伪造的地址、取 XFF 第一个元素的常见写法不可信、没有可信代理却配 ProxyFix 时客户端能决定自己的 IP。
实测 Flask 的同步请求模型:8 个各等 1 秒的请求在 sync 单 worker 下 8.0 秒、4 个 worker 下 2.0 秒、8 线程下 1.0 秒;纯计算请求线程不加速而多进程加速;一个慢请求挡住 sync worker 里的快请求 1706 ms;线程里的 request 与 g 不会串。
实测 Flask 的 async 视图:每个请求新建事件循环、请求内 asyncio.gather 把 3 个 0.5 秒的等待压到 0.5 秒、每个请求多约 251 µs 的开销、在 gunicorn 下 8 个并发的 async 视图在 sync worker 上仍需 8071.5 秒而不会突破 worker 并发数。
实测 Flask async 视图的执行边界:协程在工作线程的新事件循环里运行、request 与 g 与 session 与 contextvars 都可用、响应返回后 create_task 的任务被取消、模块级的 asyncio.Event 与 Lock 第二个请求报 bound to a different event loop、asyncio.to_thread 带上下文而普通线程没有。
实测同步代码阻塞异步请求:uvicorn 上的纯 ASGI 应用里 time.sleep 让另一个请求等 1620 ms 而 await 与 to_thread 只要几 ms;gunicorn gthread 下互不影响;同一个 Flask 应用经 asgiref 的 WsgiToAsgi 放到 uvicorn 上全部串行(1609 ms),换 a2wsgi 就不串行;asyncio debug 模式能暴露阻塞调用。
实测 Flask 与 ASGI:同一个 Flask 应用用 gunicorn sync、gunicorn gthread、uvicorn 加 a2wsgi、uvicorn 加 WsgiToAsgi 四种方式,用 ApacheBench 压测轻量与计算型两种接口(单次测量);WSGI 适配器下 WebSocket 握手返回 403。
实测 WSGI 到 ASGI 的适配:对比 gunicorn、a2wsgi、asgiref 与自己写的 25 行适配器交给 Flask 的 environ 差别(asgi.scope、缺失的服务器特有键、wsgi.input 的类型),2 MB 请求体读取,以及流式响应是否边生成边发出——收集完再发的适配器让 4 块同时到达。
实测 Greenlet 与 Flask 上下文:gunicorn 的 gevent worker 用 1 个进程处理 100 个各睡 1 秒的请求只要 1.05 秒且 g 没有串(gthread 8 线程要 13.1 秒);协作式调度下纯计算请求会卡住其它请求;未 patch 的 threading.local 在 greenlet 之间共享而 contextvars 不会。
实测 Flask 的请求隔离:request、g、current_app 是 LocalProxy,Werkzeug 的 Local 基于 ContextVar;三个线程各自的请求上下文互不影响;新线程要用 copy_current_request_context;g 属于应用上下文;模块级全局变量被线程共享,100 次并发读算写不加锁只计到 29。