Author: rhuijben
Date: Mon Nov  2 14:31:58 2015
New Revision: 1712013

URL: http://svn.apache.org/viewvc?rev=1712013&view=rev
Log:
In the http2 protocol handler: properly handle pushed requests, by setting
up all the stream state and then cleanly denying them.

* protocols/http2_protocol.c
  (http2_handle_promise): Add implementation. Setup future stream and delegate
    remaining handling to parent stream.

* protocols/http2_protocol.h
  (serf_http2_stream_t): Add new variable.

* protocols/http2_stream.c
  (serf_http2__stream_create): Tweak init code.
  (stream_promise_item,
   stream_promise_done): New functions.
  (serf_http2__stream_handle_hpack): Add specific implementation for
    promised streams.

Modified:
    serf/trunk/protocols/http2_protocol.c
    serf/trunk/protocols/http2_protocol.h
    serf/trunk/protocols/http2_stream.c

Modified: serf/trunk/protocols/http2_protocol.c
URL: 
http://svn.apache.org/viewvc/serf/trunk/protocols/http2_protocol.c?rev=1712013&r1=1712012&r2=1712013&view=diff
==============================================================================
--- serf/trunk/protocols/http2_protocol.c (original)
+++ serf/trunk/protocols/http2_protocol.c Mon Nov  2 14:31:58 2015
@@ -392,13 +392,62 @@ http2_handle_promise(void *baton,
                      const char *data,
                      apr_size_t len)
 {
-  serf_http2_stream_t *stream = baton;
+  serf_http2_stream_t *parent_stream = baton;
+  serf_http2_protocol_t *h2= parent_stream->h2;
+  serf_http2_stream_t *promised_stream;
+  apr_int32_t streamid;
+  const struct promise_t
+  {
+    unsigned char s3, s2, s1, s0;
+  } *promise;
 
   if (len != HTTP2_PROMISE_DATA_SIZE)
     return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR;
 
-  /* ### TODO: Prepare reading promise on stream */
-  SERF_H2_assert(stream->h2 != NULL);
+  SERF_H2_assert(h2 != NULL);
+
+  promise = (const void *)data;
+
+  /* Highest bit is reserved */
+  streamid = ((promise->s3 & 0x7F) << 24) | (promise->s2 << 16)
+             |(promise->s1 << 8) | promise->s0;
+
+  if (streamid == 0
+      || (streamid < h2->rl_next_streamid)
+      || (streamid & 0x01) != (h2->rl_next_streamid & 0x01))
+    {
+      /* The promised stream identifier MUST bet a valid choice for the
+         next stream sent by the sender */
+
+      /* A receiver MUST treat the receipt of a PUSH_PROMISE that promises an
+         illegal stream identifier (Section 5.1.1) as a connection error
+         (Section 5.4.1) of type PROTOCOL_ERROR.  Note that an illegal stream
+         identifier is an identifier for a stream that is not currently in the
+         "idle" state.*/
+
+      return SERF_ERROR_HTTP2_PROTOCOL_ERROR;
+    }
+  else if (parent_stream->status != H2S_OPEN
+           && parent_stream->status != H2S_HALFCLOSED_LOCAL)
+    {
+      /* PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that
+         is in either the "open" or "half-closed (remote)" state.  The stream
+         identifier of a PUSH_PROMISE frame indicates the stream it is
+         associated with.  If the stream identifier field specifies the value
+         0x0, a recipient MUST respond with a connection error (Section 5.4.1)
+         of type PROTOCOL_ERROR.*/
+
+      return SERF_ERROR_HTTP2_PROTOCOL_ERROR;
+    }
+
+  promised_stream = serf_http2__stream_get(h2, streamid, TRUE, FALSE);
+  if (!promised_stream || promised_stream->status != H2S_IDLE)
+    return SERF_ERROR_HTTP2_PROTOCOL_ERROR;
+
+  promised_stream->status = H2S_RESERVED_REMOTE;
+
+  /* Store data to allow stream to handle the promise */
+  parent_stream->new_reserved_stream = promised_stream;
 
   return APR_SUCCESS;
 }
@@ -933,8 +982,7 @@ http2_process(serf_http2_protocol_t *h2)
                                                      http2_handle_priority,
                                                      stream, h2->allocator);
                   }
-
-                if (frametype == HTTP2_FRAME_TYPE_PUSH_PROMISE)
+                else if (frametype == HTTP2_FRAME_TYPE_PUSH_PROMISE)
                   {
                     body = serf_bucket_prefix_create(body,
                                                      HTTP2_PROMISE_DATA_SIZE,
@@ -1026,8 +1074,9 @@ http2_process(serf_http2_protocol_t *h2)
                                           h2->hpack_tbl, h2->allocator);
                       }
                   }
-                else /* We have a data bucket */
+                else if (! reset_reason)
                   {
+                    /* We have a data bucket */
                     body = serf_http2__stream_handle_data(
                                         stream, body, frametype,
                                         (frameflags & HTTP2_FLAG_END_STREAM),
@@ -1442,7 +1491,6 @@ serf_http2__stream_get(serf_http2_protoc
   if (create_for_remote
       && (streamid & 0x01) == (h2->rl_next_streamid & 0x01))
     {
-      serf_http2_stream_t *rs;
       stream = serf_http2__stream_create(h2, streamid,
                                          h2->lr_default_window,
                                          h2->rl_default_window,

Modified: serf/trunk/protocols/http2_protocol.h
URL: 
http://svn.apache.org/viewvc/serf/trunk/protocols/http2_protocol.h?rev=1712013&r1=1712012&r2=1712013&view=diff
==============================================================================
--- serf/trunk/protocols/http2_protocol.h (original)
+++ serf/trunk/protocols/http2_protocol.h Mon Nov  2 14:31:58 2015
@@ -142,6 +142,9 @@ typedef struct serf_http2_stream_t
     H2S_CLOSED
   } status;
 
+  /* Used while receiving a promise stream */
+  struct serf_http2_stream_t *new_reserved_stream;
+
   /* TODO: Priority, etc. */
 } serf_http2_stream_t;
 

Modified: serf/trunk/protocols/http2_stream.c
URL: 
http://svn.apache.org/viewvc/serf/trunk/protocols/http2_stream.c?rev=1712013&r1=1712012&r2=1712013&view=diff
==============================================================================
--- serf/trunk/protocols/http2_stream.c (original)
+++ serf/trunk/protocols/http2_stream.c Mon Nov  2 14:31:58 2015
@@ -47,10 +47,10 @@ serf_http2__stream_create(serf_http2_pro
   stream->h2 = h2;
   stream->alloc = alloc;
 
-  stream->data = serf_bucket_mem_alloc(alloc, sizeof(*stream->data));
-
   stream->next = stream->prev = NULL;
 
+  /* Delay creating this? */
+  stream->data = serf_bucket_mem_alloc(alloc, sizeof(*stream->data));
   stream->data->request = NULL;
   stream->data->response_agg = NULL;
 
@@ -63,6 +63,7 @@ serf_http2__stream_create(serf_http2_pro
     stream->streamid = -1; /* Undetermined yet */
 
   stream->status = (streamid >= 0) ? H2S_IDLE : H2S_INIT;
+  stream->new_reserved_stream = NULL;
 
   return stream;
 }
@@ -209,6 +210,65 @@ stream_setup_response(serf_http2_stream_
   stream->data->response_agg = agg;
 }
 
+static apr_status_t
+stream_promise_item(void *baton,
+                    const char *key,
+                    apr_size_t key_sz,
+                    const char *value,
+                    apr_size_t value_sz)
+{
+  serf_http2_stream_t *parent_stream = baton;
+  serf_http2_stream_t *stream = parent_stream->new_reserved_stream;
+
+  SERF_H2_assert(stream != NULL);
+
+  /* TODO: Store key+value somewhere to allow asking the application
+           if it is interested in the promised stream.
+
+           Most likely it is not interested *yet* as the HTTP/2 spec
+           recommends pushing promised items *before* the stream that
+           references them.
+
+           So we probably want to store the request anyway, to allow
+           matching this against a later added outgoing request.
+   */
+  return APR_SUCCESS;
+}
+
+static apr_status_t
+stream_promise_done(void *baton,
+                    serf_bucket_t *done_agg)
+{
+  serf_http2_stream_t *parent_stream = baton;
+  serf_http2_stream_t *stream = parent_stream->new_reserved_stream;
+
+  SERF_H2_assert(stream != NULL);
+  SERF_H2_assert(stream->status == H2S_RESERVED_REMOTE);
+  parent_stream->new_reserved_stream = NULL; /* End of PUSH_PROMISE */
+
+  /* Anything else? */
+
+
+  /* ### Absolute minimal implementation.
+         Just sending that we are not interested in the initial SETTINGS
+         would be the easier approach. */
+  serf_http2__stream_reset(stream, SERF_ERROR_HTTP2_REFUSED_STREAM, TRUE);
+
+
+
+
+  /* Exit condition:
+     * Either we should accept the stream and are ready to receive
+       HEADERS and DATA on it.
+     * Or we aren't and reject the stream
+   */
+  SERF_H2_assert(stream->status == H2S_CLOSED
+                 || stream->data->request != NULL);
+
+  /* We must return a proper error or EOF here! */
+  return APR_EOF;
+}
+
 serf_bucket_t *
 serf_http2__stream_handle_hpack(serf_http2_stream_t *stream,
                                 serf_bucket_t *bucket,
@@ -219,23 +279,45 @@ serf_http2__stream_handle_hpack(serf_htt
                                 serf_config_t *config,
                                 serf_bucket_alloc_t *allocator)
 {
-  if (!stream->data->response_agg)
-    stream_setup_response(stream, config);
+  if (frametype == HTTP2_FRAME_TYPE_HEADERS)
+    {
+      if (!stream->data->response_agg)
+        stream_setup_response(stream, config);
 
-  bucket = serf__bucket_hpack_decode_create(bucket, NULL, NULL, max_entry_size,
-                                            hpack_tbl, allocator);
+      bucket = serf__bucket_hpack_decode_create(bucket, NULL, NULL, 
max_entry_size,
+                                                hpack_tbl, allocator);
 
-  serf_bucket_aggregate_append(stream->data->response_agg, bucket);
+      serf_bucket_aggregate_append(stream->data->response_agg, bucket);
 
-  if (end_stream)
-    {
-      if (stream->status == H2S_HALFCLOSED_LOCAL)
-        stream->status = H2S_CLOSED;
-      else
-        stream->status = H2S_HALFCLOSED_REMOTE;
+      if (end_stream)
+        {
+          if (stream->status == H2S_HALFCLOSED_LOCAL)
+            stream->status = H2S_CLOSED;
+          else
+            stream->status = H2S_HALFCLOSED_REMOTE;
+        }
+      return NULL; /* We want to drain the bucket ourselves */
     }
+  else
+    {
+      serf_bucket_t *agg;
+      SERF_H2_assert(frametype == HTTP2_FRAME_TYPE_PUSH_PROMISE);
+
+      /* First create the HPACK decoder as requested */
+      bucket = serf__bucket_hpack_decode_create(bucket,
+                                                stream_promise_item, stream,
+                                                max_entry_size,
+                                                hpack_tbl, allocator);
+
+      /* And now wrap around it the easiest way to get an EOF callback */
+      agg = serf_bucket_aggregate_create(allocator);
+      serf_bucket_aggregate_append(agg, bucket);
 
-  return NULL;
+      serf_bucket_aggregate_hold_open(agg, stream_promise_done, stream);
+
+      /* And return the aggregate, so the bucket will be drained for us */
+      return agg;
+    }
 }
 
 serf_bucket_t *


Reply via email to