From a29f88ce6f2f9843bd6fcbbfce1390a2071965d6 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 4 Mar 2026 07:36:09 -0800 Subject: [PATCH] document that headers must be set before streaming --- docs/patterns/streaming.rst | 15 +++++++++++++++ docs/templating.rst | 10 ++++++++-- src/flask/ctx.py | 26 ++++++++++++++++++++------ src/flask/helpers.py | 12 ++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index 9842899a..b5b305c9 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -8,6 +8,21 @@ roundtrip to the filesystem? The answer is by using generators and direct responses. +HTTP Response Behavior +---------------------- + +**Headers cannot be changed after the streaming response starts.** + +When using streaming, it's important to be aware of the order than an HTTP +response is sent. All headers must be sent first, then the body. More headers +cannot be sent after the body has begun. Therefore, you must make sure all +headers are set before starting the response, outside the generator. + +In particular, if the generator will access ``session``, be sure to do so in the +view as well so that the ``Vary: cookie`` header will be set. Do not modify the +session in the generator, as the ``Set-Cookie`` header will already be sent. + + Basic Usage ----------- diff --git a/docs/templating.rst b/docs/templating.rst index c2f8db8a..cb92c352 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -225,5 +225,11 @@ functions to make this easier to use. return stream_template("timeline.html") These functions automatically apply the -:func:`~flask.stream_with_context` wrapper if a request is active, so -that it remains available in the template. +:func:`~flask.stream_with_context` wrapper if a request is active, so that +:data:`.request`, :data:`.session`, and :data:`.g` remain available in the +template. + +More headers cannot be sent after the body has begun. Therefore, you must +make sure all headers are set before starting the response. In particular, +if the template will access ``session``, be sure to do so in the view as +well so that the ``Vary: cookie`` header will be set. diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 5f7b1f1d..fd3e670d 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -153,13 +153,27 @@ F = t.TypeVar("F", bound=t.Callable[..., t.Any]) def copy_current_request_context(f: F) -> F: - """A helper function that decorates a function to retain the current - request context. This is useful when working with greenlets. The moment - the function is decorated a copy of the request context is created and - then pushed when the function is called. The current session is also - included in the copied request context. + """Decorate a function to run inside the current request context. This can + be used when starting a background task, otherwise it will not see the app + and request objects that were active in the parent. - Example:: + .. warning:: + + Due to the following caveats, it is often safer (and simpler) to pass + the data you need when starting the task, rather than using this and + relying on the context objects. + + In order to avoid execution switching partially though reading data, either + read the request body (access ``form``, ``json``, ``data``, etc) before + starting the task, or use a lock. This can be an issue when using threading, + but shouldn't be an issue when using greenlet/gevent or asyncio. + + If the task will access ``session``, be sure to do so in the parent as well + so that the ``Vary: cookie`` header will be set. Modifying ``session`` in + the task should be avoided, as it may execute after the response cookie has + already been written. + + .. code-block:: python import gevent from flask import copy_current_request_context diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 5d412c90..9580b501 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -68,6 +68,18 @@ def stream_with_context( available, even though at the point the generator runs the request context will typically have ended. + .. warning:: + + Due to the following caveat, it is often safer to pass the data you + need as arguments to the generator, rather than relying on the context + objects. + + More headers cannot be sent after the body has begun. Therefore, you must + make sure all headers are set before starting the response. In particular, + if the generator will access ``session``, be sure to do so in the view as + well so that the ``Vary: cookie`` header will be set. Do not modify the + session in the generator, as the ``Set-Cookie`` header will already be sent. + Use it as a decorator on a generator function: .. code-block:: python