What’s new in Tornado 6.6.0
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, ands390xarchitectures 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.CancelledErrorin various parts of the framework.
Feature highlights
New feature
cross_origin_protectionprovides a new mechanism for protecting against XSRF and other cross-origin attacks. This feature is easier to use thanxsrf_cookiesand is expected to become the default in Tornado 6.7.
Deprecation notices
The
hostargument to theHTTPServerRequestconstructor is deprecated (since Tornado 6.5.2); useheaders["Host"]instead. It will be removed in Tornado 6.7.The
start_lineargument to theHTTPServerRequestconstructor will become required in Tornado 6.7. Themethod,uri, andversionarguments are deprecated and will be removed at the same time. All remaining arguments will become keyword-only. ADeprecationWarningis now emitted whenstart_lineis not provided or when thehost,method, oruriarguments are used.tornado.auth.OpenIdMixinis 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, andtornado.ioloop.TimeoutErrorare deprecated. They are aliases for the builtinTimeoutError, which should be used instead. (No warning is emitted when these aliases are used.)Calling
GzipDecompressor.decompresswithout amax_lengthargument (or withmax_length=0) is deprecated and emits aDeprecationWarning. In Tornado 7.0,max_lengthwill be required.The
xsrf_cookiesapplication setting is expected to be deprecated in Tornado 6.7 (with warnings) in favor ofcross_origin_protection. It will not be removed before Tornado 7.0.
Type annotation changes
tornado.gen.multinow has overload annotations to indicate that its return type depends on its inputs (list vs dict).HTTPServerRequest.methodanduriattributes are now typed asstrinstead ofstr | 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_callbackargument now fails with aTypeError. (Coroutine streaming callbacks are only supported bysimple_httpclient.)
tornado.gen
asyncio.CancelledErroris now handled correctly in coroutines,multi, andchain_future.WaitIteratornow supports multiple copies of the sameFutureobject.with_timeouthas a newmessageargument, which sets the message of theTimeoutError. It may be a string or a callable that returns a string (which is only called if the timeout expires).
tornado.http1connection
Invalid
Hostheaders 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\nis now reported as anHTTPInputErrorinstead of raisingAssertionError(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_httpclientand to requests received by a server withdecompress_request=True.
tornado.httputil
If
start_lineis not passed toHTTPServerRequest, missingmethodanduriarguments now default toGETand/(with aDeprecationWarning) instead of leaving these attributes asNone.HTTPHeaders.__delitem__no longer raisesKeyErrorat inappropriate times.
tornado.iostream
Fixed a socket leak when a timeout occurs during a TLS handshake, or when the connection of a
SSLIOStreamis 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_datenow correctly handles times in the future that are close to a multiple of 24 hours away.
tornado.locks
A
timeoutvalue of zero will either return or raise immediately. Previously, zero was treated as equivalent toNone(wait forever). This affectsCondition.wait,Event.wait,Semaphore.acquire, andLock.acquire.
tornado.netutil
The default
SSLContextobjects 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
CaresResolverclass (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
timeoutvalue of zero will either return or raise immediately. Previously, zero was treated as equivalent toNone(wait forever). This affectsQueue.get,Queue.put, andQueue.join.
tornado.simple_httpclient
Gzipped responses containing multiple members are now decompressed correctly, and malformed gzip data is now an error (see
tornado.http1connectionabove).Improved error messages for timeouts during the connection phase.
The
streaming_callbackargument toSimpleAsyncHTTPClient.fetchmay 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
GzipDecompressornow 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.flushnow raiseszlib.errorfor truncated input and always returns an empty byte string (sincedecompressreturns all of the output); it raisesValueErrorifunconsumed_tailhas not been passed back todecompress.
tornado.web
New feature
cross_origin_protectionprovides a new mechanism for protecting against XSRF and other cross-origin attacks. This feature is easier to use thanxsrf_cookiesand is expected to become the default in Tornado 6.7. Additional origins can be allowed with the newtrusted_originsapplication setting or by overriding the new methodRequestHandler.check_trusted_origin. See Cross-origin protection for details.asyncio.CancelledErroris 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
RuntimeErrorinstead ofAssertionError(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_ageargument toRequestHandler.set_cookiecan no longer be abused for attribute injection.RedirectHandlerno longer allows values substituted into itsurlto 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 theurl.The check that prevents
StaticFileHandlerfrom redirecting to a protocol-relative URL (whendefault_filenameis used) now also recognizes variants that use backslashes.RequestHandler.static_url(andStaticFileHandler.get_version) now raiseHTTPError(403) for paths outside ofstatic_pathinstead 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 newStaticFileHandler.MAX_VERSION_CONTENT_SIZE, get no version. Symlinks inside the static directory are still followed when computing versions; seeStaticFileHandlerfor details.static_urlis 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_VERSIONSentries.
tornado.websocket
WebSocketHandler.check_originis no longer called for requests that can be determined to be same-origin, using theSec-Fetch-Siteheader or (if that header is absent) by comparing theOriginandHostheaders. Its default implementation now callsRequestHandler.check_trusted_origin, so origins listed in thetrusted_originsapplication setting are allowed. Applications that overridecheck_originto restrict origins further than the default will no longer see same-origin requests.asyncio.CancelledErrorno 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_pingis 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-Originheader used by those versions is no longer consulted.