diff --git a/gunicorn/asgi/protocol.py b/gunicorn/asgi/protocol.py index 8bdafc70..365ed59a 100644 --- a/gunicorn/asgi/protocol.py +++ b/gunicorn/asgi/protocol.py @@ -922,7 +922,13 @@ class ASGIProtocol(asyncio.Protocol): # Skip for 1xx informational responses (RFC 9110) # Skip if Transfer-Encoding already set by framework is_informational = 100 <= response_status < 200 - if not has_content_length and not has_transfer_encoding and request.version >= (1, 1) and not is_informational: + needs_chunked = ( + not has_content_length + and not has_transfer_encoding + and request.version >= (1, 1) + and not is_informational + ) + if needs_chunked: use_chunked = True response_headers = list(response_headers) + [(b"transfer-encoding", b"chunked")] diff --git a/tests/docker/asgi_framework_compat/docker-compose.yml b/tests/docker/asgi_framework_compat/docker-compose.yml index 92653f2b..cf70f530 100644 --- a/tests/docker/asgi_framework_compat/docker-compose.yml +++ b/tests/docker/asgi_framework_compat/docker-compose.yml @@ -17,8 +17,8 @@ x-healthcheck: &healthcheck services: django: build: - context: ./frameworks/django_app - dockerfile: Dockerfile + context: ../../.. + dockerfile: tests/docker/asgi_framework_compat/frameworks/django_app/Dockerfile ports: - "8001:8000" command: ["gunicorn", "asgi:application", "-k", "asgi", "-b", "0.0.0.0:8000", "--workers", "1", "--worker-connections", "100", "--asgi-loop", "${ASGI_LOOP:-auto}"] @@ -28,8 +28,8 @@ services: fastapi: build: - context: ./frameworks/fastapi_app - dockerfile: Dockerfile + context: ../../.. + dockerfile: tests/docker/asgi_framework_compat/frameworks/fastapi_app/Dockerfile ports: - "8002:8000" command: ["gunicorn", "app:app", "-k", "asgi", "-b", "0.0.0.0:8000", "--workers", "1", "--worker-connections", "100", "--asgi-loop", "${ASGI_LOOP:-auto}"] @@ -39,8 +39,8 @@ services: starlette: build: - context: ./frameworks/starlette_app - dockerfile: Dockerfile + context: ../../.. + dockerfile: tests/docker/asgi_framework_compat/frameworks/starlette_app/Dockerfile ports: - "8003:8000" command: ["gunicorn", "app:app", "-k", "asgi", "-b", "0.0.0.0:8000", "--workers", "1", "--worker-connections", "100", "--asgi-loop", "${ASGI_LOOP:-auto}"] @@ -50,8 +50,8 @@ services: quart: build: - context: ./frameworks/quart_app - dockerfile: Dockerfile + context: ../../.. + dockerfile: tests/docker/asgi_framework_compat/frameworks/quart_app/Dockerfile ports: - "8004:8000" command: ["gunicorn", "app:app", "-k", "asgi", "-b", "0.0.0.0:8000", "--workers", "1", "--worker-connections", "100", "--asgi-loop", "${ASGI_LOOP:-auto}"] @@ -61,8 +61,8 @@ services: litestar: build: - context: ./frameworks/litestar_app - dockerfile: Dockerfile + context: ../../.. + dockerfile: tests/docker/asgi_framework_compat/frameworks/litestar_app/Dockerfile ports: - "8005:8000" command: ["gunicorn", "app:app", "-k", "asgi", "-b", "0.0.0.0:8000", "--workers", "1", "--worker-connections", "100", "--asgi-loop", "${ASGI_LOOP:-auto}"] @@ -72,8 +72,8 @@ services: blacksheep: build: - context: ./frameworks/blacksheep_app - dockerfile: Dockerfile + context: ../../.. + dockerfile: tests/docker/asgi_framework_compat/frameworks/blacksheep_app/Dockerfile ports: - "8006:8000" command: ["gunicorn", "app:app", "-k", "asgi", "-b", "0.0.0.0:8000", "--workers", "1", "--worker-connections", "100", "--asgi-loop", "${ASGI_LOOP:-auto}"] diff --git a/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/Dockerfile b/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/Dockerfile index a5e7a1f0..73265ba8 100644 --- a/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/Dockerfile +++ b/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/Dockerfile @@ -2,13 +2,20 @@ FROM python:3.12-slim WORKDIR /app -# Install curl for healthcheck and git for pip install from git -RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* +# Install curl for healthcheck +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Copy gunicorn source and install from local +COPY gunicorn /gunicorn-src/gunicorn +COPY pyproject.toml /gunicorn-src/ +COPY README.md /gunicorn-src/ +RUN pip install --no-cache-dir /gunicorn-src + +# Install other requirements +COPY tests/docker/asgi_framework_compat/frameworks/blacksheep_app/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY app.py . +COPY tests/docker/asgi_framework_compat/frameworks/blacksheep_app/app.py . EXPOSE 8000 diff --git a/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/requirements.txt b/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/requirements.txt index 5561732a..d974c849 100644 --- a/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/requirements.txt +++ b/tests/docker/asgi_framework_compat/frameworks/blacksheep_app/requirements.txt @@ -1,4 +1,4 @@ -gunicorn @ git+https://github.com/benoitc/gunicorn.git@master +# gunicorn is installed from local source in Dockerfile blacksheep>=2.0.0 uvloop>=0.19.0 websockets>=12.0 diff --git a/tests/docker/asgi_framework_compat/frameworks/django_app/Dockerfile b/tests/docker/asgi_framework_compat/frameworks/django_app/Dockerfile index fd603b79..3c767b01 100644 --- a/tests/docker/asgi_framework_compat/frameworks/django_app/Dockerfile +++ b/tests/docker/asgi_framework_compat/frameworks/django_app/Dockerfile @@ -2,13 +2,25 @@ FROM python:3.12-slim WORKDIR /app -# Install curl for healthcheck and git for pip install from git -RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* +# Install curl for healthcheck +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Copy gunicorn source and install from local +COPY gunicorn /gunicorn-src/gunicorn +COPY pyproject.toml /gunicorn-src/ +COPY README.md /gunicorn-src/ +RUN pip install --no-cache-dir /gunicorn-src + +# Install other requirements +COPY tests/docker/asgi_framework_compat/frameworks/django_app/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY . . +COPY tests/docker/asgi_framework_compat/frameworks/django_app/asgi.py . +COPY tests/docker/asgi_framework_compat/frameworks/django_app/settings.py . +COPY tests/docker/asgi_framework_compat/frameworks/django_app/urls.py . +COPY tests/docker/asgi_framework_compat/frameworks/django_app/views.py . +COPY tests/docker/asgi_framework_compat/frameworks/django_app/consumers.py . +COPY tests/docker/asgi_framework_compat/frameworks/django_app/routing.py . ENV DJANGO_SETTINGS_MODULE=settings ENV PYTHONPATH=/app diff --git a/tests/docker/asgi_framework_compat/frameworks/django_app/requirements.txt b/tests/docker/asgi_framework_compat/frameworks/django_app/requirements.txt index 7c75f91b..226191c6 100644 --- a/tests/docker/asgi_framework_compat/frameworks/django_app/requirements.txt +++ b/tests/docker/asgi_framework_compat/frameworks/django_app/requirements.txt @@ -1,4 +1,4 @@ -gunicorn @ git+https://github.com/benoitc/gunicorn.git@master +# gunicorn is installed from local source in Dockerfile Django>=5.0 channels>=4.0.0 uvloop>=0.19.0 diff --git a/tests/docker/asgi_framework_compat/frameworks/fastapi_app/Dockerfile b/tests/docker/asgi_framework_compat/frameworks/fastapi_app/Dockerfile index a5e7a1f0..42b80cd2 100644 --- a/tests/docker/asgi_framework_compat/frameworks/fastapi_app/Dockerfile +++ b/tests/docker/asgi_framework_compat/frameworks/fastapi_app/Dockerfile @@ -2,13 +2,20 @@ FROM python:3.12-slim WORKDIR /app -# Install curl for healthcheck and git for pip install from git -RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* +# Install curl for healthcheck +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Copy gunicorn source and install from local +COPY gunicorn /gunicorn-src/gunicorn +COPY pyproject.toml /gunicorn-src/ +COPY README.md /gunicorn-src/ +RUN pip install --no-cache-dir /gunicorn-src + +# Install other requirements +COPY tests/docker/asgi_framework_compat/frameworks/fastapi_app/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY app.py . +COPY tests/docker/asgi_framework_compat/frameworks/fastapi_app/app.py . EXPOSE 8000 diff --git a/tests/docker/asgi_framework_compat/frameworks/fastapi_app/requirements.txt b/tests/docker/asgi_framework_compat/frameworks/fastapi_app/requirements.txt index 7f526a7f..c1bc195c 100644 --- a/tests/docker/asgi_framework_compat/frameworks/fastapi_app/requirements.txt +++ b/tests/docker/asgi_framework_compat/frameworks/fastapi_app/requirements.txt @@ -1,4 +1,4 @@ -gunicorn @ git+https://github.com/benoitc/gunicorn.git@master +# gunicorn is installed from local source in Dockerfile fastapi>=0.110.0 uvloop>=0.19.0 websockets>=12.0 diff --git a/tests/docker/asgi_framework_compat/frameworks/litestar_app/Dockerfile b/tests/docker/asgi_framework_compat/frameworks/litestar_app/Dockerfile index a5e7a1f0..2b512598 100644 --- a/tests/docker/asgi_framework_compat/frameworks/litestar_app/Dockerfile +++ b/tests/docker/asgi_framework_compat/frameworks/litestar_app/Dockerfile @@ -2,13 +2,20 @@ FROM python:3.12-slim WORKDIR /app -# Install curl for healthcheck and git for pip install from git -RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* +# Install curl for healthcheck +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Copy gunicorn source and install from local +COPY gunicorn /gunicorn-src/gunicorn +COPY pyproject.toml /gunicorn-src/ +COPY README.md /gunicorn-src/ +RUN pip install --no-cache-dir /gunicorn-src + +# Install other requirements +COPY tests/docker/asgi_framework_compat/frameworks/litestar_app/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY app.py . +COPY tests/docker/asgi_framework_compat/frameworks/litestar_app/app.py . EXPOSE 8000 diff --git a/tests/docker/asgi_framework_compat/frameworks/litestar_app/requirements.txt b/tests/docker/asgi_framework_compat/frameworks/litestar_app/requirements.txt index f07b970a..4c027cab 100644 --- a/tests/docker/asgi_framework_compat/frameworks/litestar_app/requirements.txt +++ b/tests/docker/asgi_framework_compat/frameworks/litestar_app/requirements.txt @@ -1,4 +1,4 @@ -gunicorn @ git+https://github.com/benoitc/gunicorn.git@master +# gunicorn is installed from local source in Dockerfile litestar>=2.7.0 uvloop>=0.19.0 websockets>=12.0 diff --git a/tests/docker/asgi_framework_compat/frameworks/quart_app/Dockerfile b/tests/docker/asgi_framework_compat/frameworks/quart_app/Dockerfile index a5e7a1f0..07d6dcf5 100644 --- a/tests/docker/asgi_framework_compat/frameworks/quart_app/Dockerfile +++ b/tests/docker/asgi_framework_compat/frameworks/quart_app/Dockerfile @@ -2,13 +2,20 @@ FROM python:3.12-slim WORKDIR /app -# Install curl for healthcheck and git for pip install from git -RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* +# Install curl for healthcheck +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Copy gunicorn source and install from local +COPY gunicorn /gunicorn-src/gunicorn +COPY pyproject.toml /gunicorn-src/ +COPY README.md /gunicorn-src/ +RUN pip install --no-cache-dir /gunicorn-src + +# Install other requirements +COPY tests/docker/asgi_framework_compat/frameworks/quart_app/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY app.py . +COPY tests/docker/asgi_framework_compat/frameworks/quart_app/app.py . EXPOSE 8000 diff --git a/tests/docker/asgi_framework_compat/frameworks/quart_app/requirements.txt b/tests/docker/asgi_framework_compat/frameworks/quart_app/requirements.txt index 1f50a34f..28385937 100644 --- a/tests/docker/asgi_framework_compat/frameworks/quart_app/requirements.txt +++ b/tests/docker/asgi_framework_compat/frameworks/quart_app/requirements.txt @@ -1,4 +1,4 @@ -gunicorn @ git+https://github.com/benoitc/gunicorn.git@master +# gunicorn is installed from local source in Dockerfile quart>=0.19.0 uvloop>=0.19.0 websockets>=12.0 diff --git a/tests/docker/asgi_framework_compat/frameworks/starlette_app/Dockerfile b/tests/docker/asgi_framework_compat/frameworks/starlette_app/Dockerfile index a5e7a1f0..16ce4d9f 100644 --- a/tests/docker/asgi_framework_compat/frameworks/starlette_app/Dockerfile +++ b/tests/docker/asgi_framework_compat/frameworks/starlette_app/Dockerfile @@ -2,13 +2,20 @@ FROM python:3.12-slim WORKDIR /app -# Install curl for healthcheck and git for pip install from git -RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* +# Install curl for healthcheck +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Copy gunicorn source and install from local +COPY gunicorn /gunicorn-src/gunicorn +COPY pyproject.toml /gunicorn-src/ +COPY README.md /gunicorn-src/ +RUN pip install --no-cache-dir /gunicorn-src + +# Install other requirements +COPY tests/docker/asgi_framework_compat/frameworks/starlette_app/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY app.py . +COPY tests/docker/asgi_framework_compat/frameworks/starlette_app/app.py . EXPOSE 8000 diff --git a/tests/docker/asgi_framework_compat/frameworks/starlette_app/requirements.txt b/tests/docker/asgi_framework_compat/frameworks/starlette_app/requirements.txt index 6bf1eecc..a4db846d 100644 --- a/tests/docker/asgi_framework_compat/frameworks/starlette_app/requirements.txt +++ b/tests/docker/asgi_framework_compat/frameworks/starlette_app/requirements.txt @@ -1,4 +1,4 @@ -gunicorn @ git+https://github.com/benoitc/gunicorn.git@master +# gunicorn is installed from local source in Dockerfile starlette>=0.37.0 uvloop>=0.19.0 websockets>=12.0 diff --git a/tests/docker/asgi_framework_compat/results/compatibility_grid.json b/tests/docker/asgi_framework_compat/results/compatibility_grid.json index 796c50c0..d90f3b26 100644 --- a/tests/docker/asgi_framework_compat/results/compatibility_grid.json +++ b/tests/docker/asgi_framework_compat/results/compatibility_grid.json @@ -1,5 +1,5 @@ { - "generated": "2026-04-03T11:06:45.300191", + "generated": "2026-04-03T21:49:07.476946", "worker": "gunicorn.workers.gasgi.ASGIWorker", "frameworks": { "django": { @@ -16,8 +16,8 @@ "total": 19 }, "websocket": { - "passed": 13, - "failed": 6, + "passed": 19, + "failed": 0, "total": 19 }, "lifespan": { @@ -31,7 +31,7 @@ "total": 9 } }, - "total_passed": 66, + "total_passed": 72, "total_tests": 74 }, "fastapi": { @@ -186,12 +186,12 @@ "total": 8 }, "streaming": { - "passed": 1, - "failed": 8, + "passed": 9, + "failed": 0, "total": 9 } }, - "total_passed": 65, + "total_passed": 73, "total_tests": 74 } } diff --git a/tests/docker/asgi_framework_compat/results/compatibility_grid.md b/tests/docker/asgi_framework_compat/results/compatibility_grid.md index 31bf8860..50c3778f 100644 --- a/tests/docker/asgi_framework_compat/results/compatibility_grid.md +++ b/tests/docker/asgi_framework_compat/results/compatibility_grid.md @@ -1,6 +1,6 @@ # ASGI Framework Compatibility Grid -**Generated:** 2026-04-03 11:06:45 +**Generated:** 2026-04-03 21:49:07 **Worker:** gunicorn ASGI worker (`-k asgi`) **Event Loop:** auto (uvloop if available) @@ -8,13 +8,13 @@ | Framework | HTTP Scope | HTTP Messages | WebSocket | Lifespan | Streaming | Total | |-----------|---------|---------|---------|---------|---------|-------| -| Django + Channels | 19/19 | **18/19** | **13/19** | **7/8** | 9/9 | **66/74** | +| Django + Channels | 19/19 | **18/19** | 19/19 | **7/8** | 9/9 | **72/74** | | FastAPI | 19/19 | **18/19** | 19/19 | 8/8 | 9/9 | **73/74** | | Starlette | 19/19 | **18/19** | 19/19 | 8/8 | 9/9 | **73/74** | | Quart | **18/19** | **17/19** | **11/19** | 8/8 | 9/9 | **63/74** | | Litestar | **18/19** | **11/19** | **17/19** | 8/8 | 9/9 | **63/74** | -| BlackSheep | 19/19 | **18/19** | 19/19 | 8/8 | **1/9** | **65/74** | +| BlackSheep | 19/19 | **18/19** | 19/19 | 8/8 | 9/9 | **73/74** | *Bold indicates failures* -**Overall:** 403/444 tests passed (90%) +**Overall:** 417/444 tests passed (93%)