bigywater.tech
Flask · 141

WSGI 到 ASGI 的适配

WSGI 到 ASGI 的适配器到底做了什么?环境字典里少了什么?流式响应经过它们会怎样?这篇用 Flask 的 request.environ 把四种适配方式摊开对比,并亲手写一个最小适配器。

24 / 19 / 17
environ 键的个数:gunicorn、a2wsgi、asgiref
asgi.scope
a2wsgi 额外放进 environ 的原始 ASGI scope
1233 ms
一次性收集响应的适配器下,流式响应第一块到达的时间

environ 的差别

实测 · 本机执行输出
  [gunicorn(原生 WSGI)] 键 24 个
     比 gunicorn 多出:[]
     比 gunicorn 少了:[]
  [uvicorn + a2wsgi] 键 19 个
     比 gunicorn 多出:['asgi.scope']
     比 gunicorn 少了:['RAW_URI', 'SERVER_SOFTWARE', 'gunicorn.socket', 'wsgi.early_hints', 'wsgi.file_wrapper', 'wsgi.input_terminated']
  [uvicorn + asgiref WsgiToAsgi] 键 17 个
     比 gunicorn 多出:[]
     比 gunicorn 少了:['RAW_URI', 'REMOTE_PORT', 'SERVER_SOFTWARE', 'gunicorn.socket', 'wsgi.early_hints', 'wsgi.file_wrapper', 'wsgi.input_terminated']
  [uvicorn + 自己写的 mini 适配器] 键 18 个
     比 gunicorn 多出:['CONTENT_LENGTH']
     比 gunicorn 少了:['RAW_URI', 'REMOTE_PORT', 'SERVER_SOFTWARE', 'gunicorn.socket', 'wsgi.early_hints', 'wsgi.file_wrapper', 'wsgi.input_terminated']
  几个关键值:
     gunicorn(原生 WSGI)              SERVER_NAME=127.0.0.1 REMOTE_ADDR=127.0.0.1 wsgi.url_scheme=http wsgi.multithread=True wsgi.input=Body
     uvicorn + a2wsgi               SERVER_NAME=127.0.0.1 REMOTE_ADDR=127.0.0.1 wsgi.url_scheme=http wsgi.multithread=True wsgi.input=Body
     uvicorn + asgiref WsgiToAsgi   SERVER_NAME=127.0.0.1 REMOTE_ADDR=127.0.0.1 wsgi.url_scheme=http wsgi.multithread=True wsgi.input=SpooledTemporaryFile
     uvicorn + 自己写的 mini 适配器        SERVER_NAME=127.0.0.1 REMOTE_ADDR=127.0.0.1 wsgi.url_scheme=http wsgi.multithread=True wsgi.input=BytesIO

上一篇:Flask 与 ASGI;下一篇:Greenlet 与 Flask Context。

请求体与响应流

实测 · 本机执行输出
  gunicorn(原生 WSGI)              2 MB 请求体读到 2097152 字节 | 流式响应各块到达(ms):[1, 312, 618, 923, 1232]
  uvicorn + a2wsgi               2 MB 请求体读到 2097152 字节 | 流式响应各块到达(ms):[1, 305, 609, 920, 1223]
  uvicorn + asgiref WsgiToAsgi   2 MB 请求体读到 2097152 字节 | 流式响应各块到达(ms):[2, 304, 611, 916, 1221]
  uvicorn + 自己写的 mini 适配器        2 MB 请求体读到 2097152 字节 | 流式响应各块到达(ms):[1233, 1233, 1233, 1233, 1234]

自己写的最小适配器

实测 · 本机执行输出
  GET /hello → 200 hi
  GET /nope → 404

分步演示

WSGI 到 ASGI 的适配
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。uvicorn 0.54.0、a2wsgi 1.10.10、asgiref 3.12.1、gunicorn gthread
    • 没有验证:ASGI lifespan 事件的处理、WebSocket 的转发、请求体边到边读(流式上传)、错误与异常在适配器中的传播、SCRIPT_NAME 与 root_path 的映射细节
    京ICP备2026067502号-1京公网安备11010502063487号