Subject: Push Promise Issues

From: James M Snell <[email-masked]>
Date: Wed, 24 Apr 2013 09:35:53 -0700

1. The process for rejecting a promised stream needs to be clarified.
Right now, section 3.7.5 says, "The PUSH_PROMISE allows the client an
opportunity to reject pushed resources." but does not say any thing
about how such rejection occurs, etc. It's not until section 4.3.2
that the CANCEL mechanism is briefly described. As an editorial
suggestion, including a forward reference to section 4.3.2. in section
3.7.5 would be helpful.

The rejection process described in section 4.3.2 is not very clear and
has a number of issues. The current spec language says:

  To cancel individual server push streams, the
  client can issue a stream error (Section 3.5.2)
  of type CANCEL.  Upon receipt, the server
  ceases transmission of the pushed data...

  To cancel all server push streams related to a
  request, the client may issue a stream error
  (Section 3.5.2) of type CANCEL on the
  associated-stream-id.  By cancelling that stream,
  the server MUST immediately stop sending frames
  for any streams with in-association-to for the original
  stream.

The way this is worded, the implication is that the client is
cancelling a promised stream that is already in the process of being
transmitted. It's not clear that this is also how a client is to
reject a promised stream *before* transmission begins. Also, it needs
to be made clear that the stream error needs to specify the identifier
of the promised stream and not the associated promising stream.

That is, for example, if stream 1 includes push_promises for streams 3
and 5, and the client wishes to reject only stream 3, it sends an
RST_STREAM for stream 3.

2. If the client is rejecting the promised stream, the error code used
in the RST_STREAM ought to be REFUSED_STREAM and not CANCEL.

3. How long should a client wait for the transmission of a promised
stream? Is there some reasonable timeout? Should the client simply
issue a CANCEL if it feels the stream hasn't been received in a
reasonable period of time? If so, that needs to be described.

4. In 4.3.1 it is mentioned that the PUSH_PROMISE must include the
:scheme, :host and :port headers. In section 4.3.2, it says :scheme,
:host and :path. I assume that's just a typo in section 4.3.1.

5. Section 4.3.1 says that the PUSH_PROMISE SHOULD include header
fields that allow a cache to determine if the resource is already
cached... It should specifically call out a number of headers such as
content-type, etag and last-modified.

6. The last paragraph in section 4.3.2 dealing with the mixing of
headers frames and data frames ought to be removed. This really has
nothing to do with Server push and the question of whether we should
allow intermingling of those frame types is still an open question.

7. Whether or not a client can send a PUSH_PROMISE to the server needs
to be determined. One can easily envision a use case where a client
wishes to send multiple simultaneous streams to a server. For
instance, sending a PUT or POST to a server that has multiple payloads
(uploading more than one picture or video at a time, for instance).
While such a use case is not currently covered by HTTP/1 semantics,
the new framing layer makes such cases possible and we need to decide
now whether or not we are going to allow it.

For example,

A. Client creates a stream to the server with :method=POST, includes
two PUSH_PROMISE frames for two images it wishes to upload...
B. Client sends each image in separate streams with their own HEADERS frame.

Another case, dealing with the age-old problem of uploading a media
resource and it's associated metadata in a single operation (currently
implemented in many apis as a clumsy MIME package):

A. Client creates a stream to the server with :method=PUT, includes
two PUSH_PROMISE frames, one for the image, the other for metadata
about the image.
B. Client sends each part in separate streams with their own HEADERS frame.

In my opinion, these are very compelling use cases that need to be
carefully considered. If clients are allowed to send PUSH_PROMISE
frames, we need to mention it.

Possibly more later...

- James

---

From: William Chan (陈智昌) <[email-masked]>
Date: Wed, 24 Apr 2013 10:25:55 -0700

I didn't comment on any editorial stuff because I suck at it, but fwiw I
agree with your points on all the areas that can/should be clarified.

> in the RST_STREAM ought to be REFUSED_STREAM and not CANCEL.

Can you explain why? It's not clear to me how a server would handle them
differently. I do see that the client may be able to indicate that it has
not processed the stream at all, but I'm not sure that matters. On the
other hand, I think it's useful in the opposite direction to use
REFUSED_STREAM to indicate that it's safe for the client to retry the
request. I think there's an editorial issue to clarify that in the spec (
https://github.com/http2/http2-spec/issues/57).

> reasonable period of time? If so, that needs to be described.

I'm not sure this is any different from how long a client would wait for a
SYN_REPLY (oops, I mean the HEADERS frame). My client (Chromium) will not
time out, but that's up to the client. Typically users will reload the page
which will trigger cancellations and resending of a HEADERS+PRIORITY frame.
We may also use PING frames for liveness detection, but that would lead us
to terminate the entire session.

> B. Client sends each image in separate streams with their own HEADERS

I'm sleep deprived and still confused this morning, but I don't grok this
one. Can you explain why a client would send a PUSH_PROMISE rather than a
SYN_STREAM (er, a HEADERS+PRIORITY, god my brain still needs to wrap around
these new names) frame? PUSH_PROMISE exists to prevent races (having the
peer request the resource at the same time that the sender is trying to
push it). I don't see how the race exists in the client=>server direction
with HTTP semantics, although I can imagine a non-HTTP semantic layering
atop the framing layer that might have this race.

---

From: James M Snell <[email-masked]>
Date: Wed, 24 Apr 2013 11:31:51 -0700

> On Wed, Apr 24, 2013 at 9:35 AM, James M Snell <[email-masked]> wrote:
[snip]
> not processed the stream at all, but I'm not sure that matters. On the other
> hand, I think it's useful in the opposite direction to use REFUSED_STREAM to
> indicate that it's safe for the client to retry the request. I think there's
> an editorial issue to clarify that in the spec
> (https://github.com/http2/http2-spec/issues/57).
>

The main reason is that there is a difference between a client no
longer needing a pushed stream vs. not wanting it at all. For
instance, if the client navigates away from a page, the stream being
pushed is CANCELed. If the client returns back to that page later,
then the server will likely attempt to push the resource again. If,
however, the client refuses a pushed stream because the pushed
resource is unacceptable in some manner, then the server might choose
to not attempt pushing the resource again later on (obviously there
are quite a few assumptions about managed state being made in that
example). The bottom line is that I do not think we should make too
many assumptions about how a server will handle the difference between
the two cases.

> to terminate the entire session.

I tend to agree, but it needs to be stated explicitly if this is the
case. Some simple statement along the lines of: A client can send a
CANCEL if the server takes too long to send a promised stream... etc

[snip]
> peer request the resource at the same time that the sender is trying to push
> it). I don't see how the race exists in the client=>server direction with
> HTTP semantics, although I can imagine a non-HTTP semantic layering atop the
> framing layer that might have this race.
>

The idea is simply sending multiple request payloads with a single
http operation.. a POST with N-number individual associated streams,
for instance. Uploading photos is one use case, adding items to an
Atom Publishing Collection is another. Right now much of this is done
using clunky multipart mime formats. It could be done more efficiently
with multiplexed streams.

- James

---

From: Martin Thomson <[email-masked]>
Date: Thu, 25 Apr 2013 11:48:21 -0700

OK, I have made a few clarifying edits for #1.

> many assumptions about how a server will handle the difference between

In this case, the distinction between error codes doesn't seem
especially important.  The method is idempotent, so regardless of what
the error code says, it's safe to try again.

The only reason I think that CANCEL makes marginally more sense is
that the server has, at the time of the PUSH_PROMISE, committed to
doing something.  If you model PUSH_PROMISE as "I synthesized this GET
request for you on this other stream", then clearly the stream hasn't
been refused by the server.

>>> now whether or not we are going to allow it.

I'm not going to touch this one.  Open a design issue if you think
this is important.

Actually, as a side note, I think its best to assume that all
unspecified behavior is prohibited.  We'll get around to closing
loopholes eventually, but using something without defined semantics is
inadvisable.

---

From: William Chan (陈智昌) <[email-masked]>
Date: Thu, 25 Apr 2013 19:13:19 -0700

On Wed, Apr 24, 2013 at 11:31 AM, James M Snell <[email-masked]> wrote:

> other
> push

I don't understand this. Go ahead and use multiple HTTP POSTs with multiple
streams. Why do you need a client initiated PUSH_PROMISE for that?