This is incompatible with Tornado 2.x applications, but users should be able to update their code
without too much trouble.
Changes, in no particular order:
* TLS NPN means that one of many protocols can be selected after a TCP connection is established. A
layer of indirection was added to TCPServer to allow it to delegate handling of a TCP connection
to whichever protocol handler was negotiated. If the `npn_protocols` parameter (a list of
(name, handler) tuples in order of preference) was passed to the constructor, the connection is
over TLS, and NPN succeeded, the handler for the chosen name will be called. Otherwise, the
`protocol` constructor parameter will be called. For example, SPDYServer is essentially:
def __init__(self, request_callback):
http_protocol = HTTPServerProtocol(request_callback)
* TCPServer was moved from netutil to its own module, tcpserver.
* Since utilizing NPN support in Python 3.3 requires the `ssl.SSLContext` class, which isn't
available in Python 2.x, the wrap_socket() top-level function was added to `netutil` to abstract
away these details. In addition, the `SUPPORTS_NPN` constant was added as a convenience for
determining if the system supported NPN.
* Previously, TCP connection timeout, premature connection termination, and any HTTP parsing errors
were handled in the client by executing the request callback with a fake 599 response. The user
would then (if they remembered) call response.rethrow() to raise an exception. Separating the TCP
connection from response handling made this scheme impossible (aside from being un-Pythonic in the
first place). Instead, stack_context.wrap() was refactored to return a callable with a restore()
method that returned a context manager that restores the saved stack context within its block.
The above-mentioned errors are raised within this context. Top-level save() and switch_contexts()
functions were also added, in case someone finds them useful. This is the primary incompatibility
with current Tornado applications. `HTTPResponse.rethrow` has been removed, but the `error`
attribute has been converted to a boolean representing whether the status code isn't 2xx or 3xx.
* The SPDY standard is rapidly evolving. v3 has already been completed, and we're currently working
on v4. To shield the user from this, the spdy* modules are actually directories that place their
public interfaces in __init__.py, and their version-specific implementation in v2.py. This allows
users to `import spdyserver`, and spdy* modules to `import spdyserver.v2`. Once v3 support is
implemented, it will go in v3.py, and users won't notice the difference.
* Much of the functionality of SimpleAsyncHTTPClient was factored out into QueuedAsyncHTTPClient,
to allow SPDYClient to take advantage of its queuing logic as well. Subclasses pass a `handler`
argument to the `initialize` pseudo-constructor, which gets called when a request is ready to be
processed. If the handler determines that a new TCP connection is needed, it can call the
`_http_connect` method to open one and return a (conn, address) pair.
* Added push_callback, priority, force_connection, ssl_version attributes to httpclient.HTTPRequest.
* Added framing, associated_urls, associated_to_url attributes to httpclient.HTTPResponse. The
latter is necessary for notifying the client which URLs the server has promised to push, since
pushed streams can be finished after the original response stream has.
* Added framing, priority attributes to httpserver.HTTPRequest.
* Since SPDY sessions are long-lived and streams are bidirectional, logic common to both clients and
servers (stream management) has been factored out into the spdysession module. Each frame type,
stream finishing events, and stream adding events all provide hooks for subclasses to override
with client/server-specific functionality.
* Previously, `web.RequestHandler` formatted the HTTP response itself and wrote it directly to the
IOStream. To allow for SPDY framing, this responsibility has been moved to the
HTTPRequest.connection object, which must provide the write_preamble() and write() methods - the
former writes the response status line and headers, while the latter writes a chunk of the
response body. In addition, the SPDYConnection object provides the push() method, which returns
a new `SPDYConnection` that will push a resource to the client.
* Body argument-parsing code (application/x-www-form-urlencoded or multipart/form data) needed by
spdyserver, httpserver, and wsgi (and which was duplicated in the latter two) has been factored
out into httputil.parse_body_arguments.
* Although IOStream.connect() already takes a callback parameter, in SSLIOStream it's not called
until the SSL handshake is completed (which contains TLS NPN) - and TCPServer, which doesn't call
connect(), won't know which protocol handler to execute until that happens. To fix this, a
set_connect_callback method was added to IOStream.
* Since both SPDYSession and SimpleAsyncHTTPClient find a gzip decompressor useful, a
GzipDecompressor class was added to the util module.
* A new RequestHandler.push() method has been added that takes a relative URL as a parameter and
opens a new push stream for it the next time flush() is called. A new HTTPRequest is constructed
and passed to the RequestHandler's Application instance, just like normal - the only difference
is that the response will be written to the pushed stream. Since SPDY forbids servers from
pushing streams after any of the response body has been written to the network to avoid race
conditions, calling this method after flush() has been called results in an exception.
* The UIModule constructor now takes a `push_files` parameter that determines whether its
static_url() function provided to templates now takes an optional `push` parameter, which
likewise causes the resulting URL to be pushed. Since the default for both of these parameters is
easily overridden and users are aware that this patch introduces incompatibilities, I am
confident enough to have both of these parameters default to `True`, but could be convinced
* Gzip body encoding is automatically chosen if the `gzip` Application setting is `True` and the
connection framing is SPDY, regardless of the request's Accept-Encoding header. SPDY specifically
* The boolean `spdy` Application parameter was added to cause RequestHandler to add an
"Alternate-Protocol: 443:npn-spdy" header to all responses if the connection framing is not
* AsyncSSLTestCase has been added to the testing module as a base class for all tests that require
* Since the websocket module has been updated to use the new HTTPConnection interface, it should,
in theory, support SPDY framing. However, Chrome 19 crashes whenever I start it with
--enable-websocket-over-spdy, so we'll need to work with the Chromium team to get this fixed.
If there were unit tests for the websocket module, I would have updated them.
* The spdyutil module, the bulk of which is in its v2 submodule, provides data structures
representing SPDY frames (which can serialize themselves), as well as a non-blocking parse_frame
* Mark Nottingham's `c_zlib` module has been borrowed (stolen) from his old nbhttp
(https://github.com/mnot/nbhttp/tree/spdy/src) project, since the Python standard library's
`zlib` module lacks pre-set dictionary support, which SPDY uses for header compression.