Robert Brewer fumanchu at amor.org
Fri Mar 30 19:32:19 CEST 2007

RFC 2145 says:

  "An implementation of HTTP/x.b sending a message to a
   recipient whose version is known to be HTTP/x.a, a < b,
   MUST NOT depend on the recipient understanding a header
   not defined in the specification for HTTP/x.a.  For example,
   HTTP/1.0 clients cannot be expected to understand chunked
   encodings, and so an HTTP/1.1 server must never send
   "Transfer-Encoding: chunked" in response to an HTTP/1.0

In specific cases, implementations can choose to send some HTTP/1.1
headers to HTTP/1.0 clients, but in the general case, the solution is
usually to downgrade the entire HTTP response to 1.0 features only.

Under WSGI, "an implementation of HTTP/x.b" is an emergent property of
the entire stack; servers, middleware, and applications all share this
responsibility to downgrade the entire response to HTTP/1.0 features if
any of the other components is not HTTP/1.1 compliant.

Unfortunately, the WSGI 1.0 spec doesn't require WSGI servers to tell
WSGI applications what version of HTTP they support. If a WSGI origin
server "fails to satisfy one or more of the MUST or REQUIRED level
requirements for the protocols it implements" (as too many WSGI servers
do!), WSGI applications have no standardized way of knowing this, and
may output headers which contradict the version number output by the
WSGI server.

CherryPy hacks around this by having the origin server send a custom
entry in the WSGI environ called "ACTUAL_SERVER_PROTOCOL", which tells
the rest of the WSGI stack the version for which the origin server is at
least conditionally compliant:

    # Compare request and server HTTP protocol versions, in case our
    # server does not support the requested protocol. Limit our output
    # to min(req, server). We want the following output:
    #     request    server     actual written   supported response
    #     protocol   protocol  response protocol    feature set
    # a     1.0        1.0           1.0                1.0
    # b     1.0        1.1           1.1                1.0
    # c     1.1        1.0           1.0                1.0
    # d     1.1        1.1           1.1                1.1
    # Notice that, in (b), the response will be "HTTP/1.1" even though
    # the client only understands 1.0. RFC 2616 10.5.6 says we should
    # only return 505 if the _major_ version is different.
    rp = int(req_protocol[5]), int(req_protocol[7])
    sp = int(server.protocol[5]), int(server.protocol[7])
    if sp[0] != rp[0]:
        self.simple_response("505 HTTP Version Not Supported")
    # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
    environ["SERVER_PROTOCOL"] = req_protocol
    # set a non-standard environ entry so the WSGI app can know what
    # the *real* server protocol is (and what features to support).
    # See http://www.faqs.org/rfcs/rfc2145.html.
    environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol
    self.response_protocol = "HTTP/%s.%s" % min(rp, sp)

The "application-side" bits of CherryPy inspect this value (if present)
and perform the same min(rp, sp) calculation as the server in order to
determine which features to support.

WSGI 2 should, at the least, add a standard environ entry similar to
ACTUAL_SERVER_PROTOCOL. This would provide the minimum enforcement of
full-stack compliance, since WSGI origin servers tend to be the
least-compliant portions of any WSGI stack. As far as I am aware, the
CherryPy 3 wsgiserver is the only one currently claiming to be even
"conditionally compliant" with HTTP/1.1.

WSGI 2 might, in addition, require WSGI origin servers to perform the
min(rp, sp) calculation once and pass the result in a new
"RESPONSE_PROTOCOL_SUPPORT" environ entry. Note this is not necessarily
the same version number as what will be output in the response

  "An HTTP server SHOULD send a response version equal to
   the highest version for which the server is at least
   conditionally compliant, and whose major version is
   less than or equal to the one received in the request.
   An HTTP server MUST NOT send a version for which it is
   not at least conditionally compliant.  A server MAY
   send a 505 (HTTP Version Not Supported) response if [it]
   cannot send a response using the major version used
   in the client's request."

If a given WSGI application or middleware component is not at least
conditionally compliant with HTTP/1.1, the WSGI origin server should
downgrade the response version it emits in the Status-Line, but has no
standardized way to be informed of this state of affairs. Currently, the
burden tends to fall on those who compose WSGI stacks to manually
instruct the WSGI origin server to always output HTTP/1.0 if any WSGI
component is not conditionally compliant with HTTP/1.1. This issue may
need to be addressed in a separate spec covering the composition of WSGI

Robert Brewer
System Architect
Amor Ministries
fumanchu at amor.org

More information about the Web-SIG mailing list