Oct 21, 2026
------------
General changes
~~~~~~~~~~~~~~~
- Python 3.15 is now supported. Python 3.9 and 3.10 are no longer supported; 3.11 is the minimum
supported version.
- Free-threaded python interpreters are now supported; prebuilt wheels are available for 3.14t
and 3.15t.
- Windows on ARM is now supported and prebuilt wheels are available.
- Prebuilt wheels are now available for ``riscv64``, ``ppc64le``, and ``s390x`` architectures on
Linux. These architectures are experimental and have limited support and testing.
- PyPy 8.0 is now supported (for both Python 3.11 and 3.12).
- Error messages have been improved in many places.
- Improved handling of `asyncio.CancelledError` in various parts of the framework.
Feature highlights
~~~~~~~~~~~~~~~~~~
- New feature ``cross_origin_protection`` provides a new mechanism for protecting against
XSRF and other cross-origin attacks. This feature is easier to use than ``xsrf_cookies``
and is expected to become the default in Tornado 6.7.
Deprecation notices
~~~~~~~~~~~~~~~~~~~
- The ``host`` argument to the `.HTTPServerRequest` constructor is deprecated (since
Tornado 6.5.2); use ``headers["Host"]`` instead. It will be removed in Tornado 6.7.
- The ``start_line`` argument to the ``HTTPServerRequest`` constructor will become required in
Tornado 6.7. The ``method``, ``uri``, and ``version`` arguments are deprecated and will be
removed at the same time. All remaining arguments will become keyword-only. A
`DeprecationWarning` is now emitted when ``start_line`` is not provided or when the ``host``,
``method``, or ``uri`` arguments are used.
- `tornado.auth.OpenIdMixin` is deprecated and will be removed in Tornado 6.7 (this is a change
in the timeline; it was previously scheduled for removal in Tornado 7.0).
- ``tornado.util.TimeoutError``, ``tornado.gen.TimeoutError``, and ``tornado.ioloop.TimeoutError``
are deprecated. They are aliases for the builtin `TimeoutError`, which should be used instead.
(No warning is emitted when these aliases are used.)
- Calling `.GzipDecompressor.decompress` without a ``max_length`` argument (or with
``max_length=0``) is deprecated and emits a `DeprecationWarning`. In Tornado 7.0,
``max_length`` will be required.
- The ``xsrf_cookies`` application setting is expected to be deprecated in Tornado 6.7 (with
warnings) in favor of ``cross_origin_protection``. It will not be removed before Tornado 7.0.
Type annotation changes
~~~~~~~~~~~~~~~~~~~~~~~
- `tornado.gen.multi` now has overload annotations to indicate that its return type depends
on its inputs (list vs dict).
- `.HTTPServerRequest.method` and ``uri`` attributes are now typed as ``str`` instead of
``str | None``.
``tornado.curl_httpclient``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- The minimum supported version of libcurl is now 7.81 (which is the version shipped by
Ubuntu 22.04 LTS).
- Passing a coroutine as the ``streaming_callback`` argument now fails with a `TypeError`.
(Coroutine streaming callbacks are only supported by ``simple_httpclient``.)
`tornado.gen`
~~~~~~~~~~~~~
- `asyncio.CancelledError` is now handled correctly in coroutines, `.multi`, and `.chain_future`.
- `.WaitIterator` now supports multiple copies of the same `.Future` object.
- `.with_timeout` has a new ``message`` argument, which sets the message of the `TimeoutError`.
It may be a string or a callable that returns a string (which is only called if the timeout
expires).
``tornado.http1connection``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Invalid ``Host`` headers are now logged at the INFO level instead of as an uncaught exception.
- Line folding in HTTP headers is no longer supported. This was deprecated in Tornado 6.5 but is
being removed sooner than originally scheduled to avoid DoS risk.
- Reading a request or response body now yields to the event loop periodically, so a peer that
sends a body faster than it can be processed (especially with many tiny chunks in chunked
encoding) can no longer block the event loop for the duration of the body.
- Small chunks of a body using chunked encoding are now coalesced before being passed to
``data_received``, so the pieces seen by the application no longer correspond to the HTTP
chunk boundaries.
- A chunk in a chunked body that is not followed by ``\r\n`` is now reported as an
`.HTTPInputError` instead of raising ``AssertionError`` (or being silently accepted when
Python is run with ``-O``).
- Gzipped bodies containing multiple members are now decompressed correctly. Trailing garbage
at the end of a body or in between members is now an error instead of being silently ignored,
as is a body that is truncated partway through a member. This applies both to responses
received by ``simple_httpclient`` and to requests received by a server with
``decompress_request=True``.
`tornado.httputil`
~~~~~~~~~~~~~~~~~~
- If ``start_line`` is not passed to `.HTTPServerRequest`, missing ``method`` and ``uri``
arguments now default to ``GET`` and ``/`` (with a `DeprecationWarning`) instead of leaving
these attributes as ``None``.
- ``HTTPHeaders.__delitem__`` no longer raises `KeyError` at inappropriate times.
`tornado.iostream`
~~~~~~~~~~~~~~~~~~
- Fixed a socket leak when a timeout occurs during a TLS handshake, or when the connection
of a `.SSLIOStream` is cancelled.
- Fixed errors relating to "existing exports of data" on PyPy 8.0.
- Tests now pass when Python is built with AWS-LC or BoringCrypto instead of OpenSSL.
`tornado.locale`
~~~~~~~~~~~~~~~~
- `.format_date` now correctly handles times in the future that are close to a multiple of
24 hours away.
`tornado.locks`
~~~~~~~~~~~~~~~
- A ``timeout`` value of zero will either return or raise immediately. Previously, zero was
treated as equivalent to ``None`` (wait forever). This affects `.Condition.wait`, `.Event.wait`,
`.Semaphore.acquire`, and `.Lock.acquire`.
`tornado.netutil`
~~~~~~~~~~~~~~~~~
- The default ``SSLContext`` objects are now lazily created on first use rather than at import time.
`tornado.platform.asyncio`
~~~~~~~~~~~~~~~~~~~~~~~~~~
- The selector thread used on Windows now cleans up its sockets correctly on interpreter exit,
preventing logged warnings.
`tornado.platform.caresresolver`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- The ``CaresResolver`` class (which has been deprecated since Tornado 6.2) requires pycares
version 4.x; it will not be updated to support pycares 5.x.
`tornado.queues`
~~~~~~~~~~~~~~~~
- A ``timeout`` value of zero will either return or raise immediately. Previously, zero was
treated as equivalent to ``None`` (wait forever). This affects `.Queue.get`, `.Queue.put`, and
`.Queue.join`.
``tornado.simple_httpclient``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Gzipped responses containing multiple members are now decompressed correctly, and malformed
gzip data is now an error (see ``tornado.http1connection`` above).
- Improved error messages for timeouts during the connection phase.
- The ``streaming_callback`` argument to ``SimpleAsyncHTTPClient.fetch`` may now be a coroutine.
`tornado.tcpclient`
~~~~~~~~~~~~~~~~~~~
- When a connection attempt is cancelled, sockets are now closed immediately instead of waiting
for the connection to either complete or time out.
`tornado.util`
~~~~~~~~~~~~~~
- `.GzipDecompressor` now decompresses streams containing multiple gzip members in full;
previously everything after the first member was ignored. Data after the last member that is
not the start of another member is now an error, as is a stream that ends partway through a
member. `.GzipDecompressor.flush` now raises `zlib.error` for truncated input and always
returns an empty byte string (since `~.GzipDecompressor.decompress` returns all of the
output); it raises `ValueError` if `~.GzipDecompressor.unconsumed_tail` has not been passed
back to ``decompress``.
`tornado.web`
~~~~~~~~~~~~~
- New feature ``cross_origin_protection`` provides a new mechanism for protecting against
XSRF and other cross-origin attacks. This feature is easier to use than ``xsrf_cookies``
and is expected to become the default in Tornado 6.7. Additional origins can be allowed
with the new ``trusted_origins`` application setting or by overriding the new method
`.RequestHandler.check_trusted_origin`. See :ref:`cross_origin_protection` for details.
- `asyncio.CancelledError` is now handled correctly instead of leaving connections to hang.
- ``raise HTTPError(204)`` no longer attempts to incorrectly send a response body.
- Writing a response body with a status code that does not allow one (such as 204 or 304) now
raises `RuntimeError` instead of `AssertionError` (so the check is no longer skipped when
Python runs with ``-O``).
- Signed values with valid signatures but certain formatting errors are now ignored instead of
raising errors (consistent with historical behavior for other formatting errors).
- The ``max_age`` argument to `.RequestHandler.set_cookie` can no longer be abused for
attribute injection.
- `.RedirectHandler` no longer allows values substituted into its ``url`` to change the scheme or
netloc (user info, host, and port) of the redirect, which could be used as an open redirect.
This includes protocol-relative URLs (``//example.com``) and variants that browsers treat the
same way, such as ``/\example.com``. Such requests now fail with a 400 error. As a
consequence, substitutions can no longer be used in the scheme or netloc part of the ``url``.
- The check that prevents `.StaticFileHandler` from redirecting to a protocol-relative URL (when
``default_filename`` is used) now also recognizes variants that use backslashes.
- `.RequestHandler.static_url` (and `.StaticFileHandler.get_version`) now raise
`~tornado.web.HTTPError` (403) for paths outside of ``static_path`` instead of reading and
hashing files elsewhere on the filesystem. Files that are not regular files (such as devices or
FIFOs), and files larger than the new `.StaticFileHandler.MAX_VERSION_CONTENT_SIZE`, get no
version. Symlinks inside the static directory are still followed when computing versions; see
`.StaticFileHandler` for details. ``static_url`` is intended for an application's known static
files and should not be called with untrusted input.
- The cache of static file versions is now limited to
`.StaticFileHandler.MAX_CACHED_VERSIONS` entries.
`tornado.websocket`
~~~~~~~~~~~~~~~~~~~
- `.WebSocketHandler.check_origin` is no longer called for requests that can be determined
to be same-origin, using the ``Sec-Fetch-Site`` header or (if that header is absent) by
comparing the ``Origin`` and ``Host`` headers. Its default implementation now calls
`.RequestHandler.check_trusted_origin`, so origins listed in the ``trusted_origins``
application setting are allowed. Applications that override ``check_origin`` to restrict
origins further than the default will no longer see same-origin requests.
- `asyncio.CancelledError` no longer breaks the websocket loop without closing the connection.
- Receiving messages now yields to the event loop periodically, so a peer that sends many small
messages faster than they can be processed can no longer block the event loop indefinitely.
- When a ping is received while the pong for a previous ping has not yet been sent, only the
most recent ping is answered (as permitted by RFC 6455 section 5.5.3). Previously, a peer that
sent pings without reading the pongs could cause the server's memory usage to grow without
limit. ``on_ping`` is still called for every ping.
- Versions 7 and 8 of the WebSocket protocol (pre-standard drafts from 2011) are no longer
supported; only version 13 (:rfc:`6455`) is accepted. The ``Sec-WebSocket-Origin`` header
used by those versions is no longer consulted.