Is 100-Continue hop-by-hop?

Recap: Dave Morris has proposed a new "Expect" request-header
to declare a client's intention to wait for a 100 (Continue)
response.

Koen Holtman remarked that "Expect" therefore was a hop-by-hop
header, because the 100 (Continue) mechanism was hop-by-hop.
I initially agreed with Koen.

Dave replied
    I have just reviewed RFC 2068 and find no indication that
    100 (Continue) is a hop-hop mechanism.

Presumably, we are all basically talking about section 8.2,
"Message Transmission Requirements."

Dave writes:

    We use the term server when we don't choose to differentiate
    between proxies and servers.

As one of the primary authors of the text in section 8.2, I can
assure you that this is exactly what I intended, and almost certainly
what the other authors of this section intended.  The term
"origin server" does not appear in section 8.2, and I believe
our original conception was that this was a hop-by-hop mechanism.

However, section 8.2 is ambiguous as to whether the 100 (Continue)
mechanism should be end-to-end or hop-by-hop, when a proxy
is involved.  I'm not sure we ever really thought this through.

Note that section 8.2 does say:
   If an HTTP/1.1 client has not seen an HTTP/1.1 or later response from
   the server, it should assume that the server implements HTTP/1.0 or
   older and will not use the 100 (Continue) response.
Since the HTTP version number is most definitely a hop-by-hop
mechanism (see RFC2145), this strongly implies some sort of hop-by-hop
behavior for the two-phase mechanism.

After puzzling over this issue for a while, I've pretty much
convinced myself that
	(1) there are times when the 100 (Continue) mechanism
	must be interpreted on each hop, or it simply won't work.
	(2) there are times when an end-to-end two-phase
	mechanism is useful (maybe even necessary)
	(3) "Connection: expect" is unnecesary.

To elaborate:
(1) there are times when the 100 (Continue) mechanism must be
interpreted on each hop, or it simply won't work:

Consider the case of an HTTP/1.1 user-agent (C1) talking via an
HTTP/1.1 proxy (P1) to an HTTP/1.0 origin server (S0).  (Here, I use
"HTTP/1.1" to mean "compliant with our ultimate spec", not necessarily
"compliant with RFC2068".)

If the user-agent C1 is expecting an end-to-end 100 (Continue) response
from S0, it's going to be disappointed.  Therefore, proxy P1 cannot
blindly forward an "Expect: 100-continue" request-header to the origin
server S0.  It has to figure out whether S0 is going to honor "Expect:
100-continue" before it can forward it, or else we'll end up in a
deadlock (C1 waiting for 100, S0 waiting for the request body).

We may also find a situation where an HTTP/1.0 client (C0) is talking
via an HTTP/1.1 proxy (P1) to an HTTP/1.1 origin server (S1), and (for
whatever reason) the proxy designer wants to use a two-phase mechanism
(e.g., bandwidth between P1 and S1 is expensive).  In this case, P1
will be both generating "Expect: 100-continue" locally, and consuming
"100 (Continue)" responses locally.

So we would like Proxy P1 to be involved in the two-phase mechanism; it
should not blindly forward "Expect: 100-continue".

(2) there are times when an end-to-end two-phase mechanism is useful
(maybe even necessary):

Dave writes:
    In much of the recent discussion which resulted in my proposal, it
    seemed to be that the contributors were clearly thinking in terms
    of the 100 Continue mechanism as a pacing control between the
    client and the origin server.
    
I.e., a client (e.g., on a slow link) might want to be sure that the
ultimate origin server will accept the request headers before
it sends the request body.  E.g., if the Authorization on the
request could fail.

So we cannot simply say that the intervening proxy always locally
generates an immediate 100 Continue response to its client, before
finding out if the origin server will accept the request headers.

Dave points out that section 13.11 says:
	This does not prevent a cache from sending a 100 (Continue)
	response before the inbound server has replied.
As written, it clearly implies that this is a hop-by-hop mechanism, or
how else could a proxy cache send the 100 response before it gets it?
However, it's quite possible that this is a bug in RFC2068
(and I almost certainly wrote all of 13.11).  It's not clear
how useful the two-phase mechanism would be if any proxy could
arbitrarily short-circuit it.

(3) "Connection: expect" is unnecessary.

Suppose that the user-agent client does indeed want end-to-end behavior
for the two-phase mechanism.  Let's assume that there is a way to
specify this (e.g., "Expect: 100-end2end", although we may find that
this is what "Expect: 100-continue" really means).

Then, we have these possible situations:
	(1) the proxy is HTTP/1.0, and understands neither
	"Expect" nor 100.  It will blindly forward the Expect,
	will probably forward the "100", and might even drop the
	connection before the actual response arrives.  Section
	8.2 says to retry in this case, without waiting for 100.
	"Connection: expect" is irrelevant, since the proxy
	wouldn't obey it anyway.

	(2) the proxy complies with RFC2068.  If we insist on
	"Connection: expect", then it will not forward the
	Expect header, but it won't give any immediate error
	status.
	    (2A) The next-hop server is HTTP/1.1, and sends
	    the 100 response; the proxy forwards its, and
	    the user-agent sends the request body.  Success.
	    (2B) The next-hop server is HTTP/1.0, and will
	    wait for the request body.  Deadlock?
	If we did not insist on "Connection: expect", then
	the proxy would simply forward the Expect header.
	Either way, we would get the same 2A and 2B cases.

	(3) the proxy is HTTP/1.1, and understands both Expect
	and 100.  Although the "Connection: expect" tells the
	proxy to strip the Expect from the incoming message,
	the Proxy is certainly allowed to add an Expect header
	to the message it sends to the next-hop server.
	    (3A) the proxy knows that the next hop server
	    is HTTP/1.1, so it can honor the client's expectation,
	    and it simply sends along the request (after
	    restoring the Expect header).  Success.
	    (3B) the proxy knows that the next-hop server is
	    HTTP/1.0, and so it should respond to its client
	    with a failure status (i.e., the expectation cannot
	    be met).  Detected failure.
	    (3C) the proxy doesn't know the version number of
	    the next-hop server.  If it rejects the request,
	    then it might be preventing communication with
	    a perfectly good HTTP/1.1 server.  But if it
	    forwards the request, we might get a deadlock.
	If we did not insist on "Connection: expect", then
	the proxy would simply forward the Expect header.
	Either way, we would get the same 3A, 3B, and 3C cases.
	(But the proxy implementation might be simpler.)

It might seem reasonable to assume that the client should not be
sending "Expect: 100-end2end" if it doesn't know that the origin
server understands the two-phase mechanism.  However, because we
don't have an explicit end-to-end HTTP version number, I can't
see a formal way of doing this.  In particular, I can't see any
way for the *proxy* to know if its client knows whether the
origin server will send 100 or not.

However, it does seem reasonable for a user-agent to refrain from
sending "Expect: 100-end2end" (or "Expect: 100-hopbyhop") to
a next-hop proxy that it knows is HTTP/1.0, since in that case
it would be unlikely for this to work.  And the client probably
isn't going to be doing PUT/POST requests via a proxy it's never
used before (since it probably has already communicated via that
proxy to get the relevant HTML form, or whatever).

It also seems reasonable for a client that is expecting a 100
response from an origin server to use a relatively short timeout
on its first attempt to wait for such a response, and if the
timeout expires, just send the request body without waiting.

Putting this all together,
	(R1) All HTTP/1.1 proxies, and all clients that might
	use the two-phase mechanism, ought to keep a cache
	storing the HTTP version numbers received from the
	servers they have contacted recently.

	(R2) HTTP/1.1 user-agents that send "Expect: 100-end2end"
	should still use a relatively short timeout before going
	ahead with the request body, unless they have already
	seen a "100" response from the given origin server.
	This avoids deadlock, and means that in case 3C, the
	proxy *should* forward the request to a server whose
	version it doesn't know (which will be a rare situation,
	because of R1).

	(R3) HTTP/1.1 proxies that receive "Expect: 100-end2end"
	should respond with a failure status if the next-hop
	server is known to be HTTP/1.0.

	(R4) It doesn't seem to make much of a difference
	whether we insist on "Connection: expect" or not.
	(Unless I've blown the case analysis; would someone
	like to present a counterexample?).  Therefore, Expect
	should not be in the list of hop-by-hop headers.

	(R5) Section 8.2 clearly needs some more reworking.
	Section 13.11 probably needs to have an offending
	sentence removed.

-Jeff

P.S.: Do we really also need an "Expect: 100-hopbyhop"?  I'm not sure.
However, the main difference is that it would be much simpler to
implement.  The receiving server (proxy or otherwise) would could
simply send a 100 Continue response whenever it pleased, or it could
wait for the next-hop server to send the 100.  But I'm not sure
it would serve any real purpose.

Received on Monday, 7 July 1997 16:46:10 UTC