]> granicus.if.org Git - curl/commitdiff
http2: setup the new pushed stream properly
authorDaniel Stenberg <daniel@haxx.se>
Mon, 1 Jun 2015 12:20:57 +0000 (14:20 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 24 Jun 2015 21:44:42 +0000 (23:44 +0200)
lib/http.c
lib/http.h
lib/http2.c
lib/http2.h
lib/multi.c
lib/multiif.h

index e06c798e9bb10f004890f423ae7a97c5275dc221..d307eabd55f5cecccd7ee6142e0d3752eebac234 100644 (file)
@@ -164,6 +164,7 @@ CURLcode Curl_http_setup_conn(struct connectdata *conn)
   conn->data->req.protop = http;
 
   Curl_http2_setup_conn(conn);
+  Curl_http2_setup_req(conn->data);
 
   return CURLE_OK;
 }
index 415be39e1d2f0bde897e3af0d11cc470a2dc078b..80ec68303d4aec2511405e72206eb9489240db5b 100644 (file)
@@ -176,6 +176,7 @@ struct HTTP {
   const uint8_t *upload_mem; /* points to a buffer to read from */
   size_t upload_len; /* size of the buffer 'upload_mem' points to */
   curl_off_t upload_left; /* number of bytes left to upload */
+  Curl_send_buffer *push_recvbuf; /* store incoming push headers */
 #endif
 };
 
index ae8afa480447fe02e6315962653f84cccab64ad3..8f5b6930b69eeae44eb1e002f305f9faec1fac7d 100644 (file)
@@ -95,12 +95,9 @@ static CURLcode http2_disconnect(struct connectdata *conn,
 }
 
 /* called from Curl_http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn)
+void Curl_http2_setup_req(struct SessionHandle *data)
 {
-  struct HTTP *http = conn->data->req.protop;
-
-  conn->proto.httpc.settings.max_concurrent_streams =
-    DEFAULT_MAX_CONCURRENT_STREAMS;
+  struct HTTP *http = data->req.protop;
 
   http->nread_header_recvbuf = 0;
   http->bodystarted = FALSE;
@@ -109,13 +106,18 @@ void Curl_http2_setup_conn(struct connectdata *conn)
   http->pauselen = 0;
   http->error_code = NGHTTP2_NO_ERROR;
   http->closed = FALSE;
-
-  /* where to store incoming data for this stream and how big the buffer is */
-  http->mem = conn->data->state.buffer;
+  http->mem = data->state.buffer;
   http->len = BUFSIZE;
   http->memlen = 0;
 }
 
+/* called from Curl_http_setup_conn */
+void Curl_http2_setup_conn(struct connectdata *conn)
+{
+  conn->proto.httpc.settings.max_concurrent_streams =
+    DEFAULT_MAX_CONCURRENT_STREAMS;
+}
+
 /*
  * HTTP2 handler interface. This isn't added to the general list of protocols
  * but will be used at run-time when the protocol is dynamically switched from
@@ -228,46 +230,98 @@ struct curl_headerpair *curl_pushheader_bynum(struct curl_pushheaders *h,
   return NULL;
 }
 
+static CURL *duphandle(struct SessionHandle *data)
+{
+  struct SessionHandle *second = curl_easy_duphandle(data);
+  if(second) {
+    /* setup the request struct */
+    struct HTTP *http = calloc(1, sizeof(struct HTTP));
+    if(!http) {
+      (void)Curl_close(second);
+      second = NULL;
+    }
+    else {
+      second->req.protop = http;
+      http->header_recvbuf = Curl_add_buffer_init();
+      if(!http->header_recvbuf) {
+        free(http);
+        (void)Curl_close(second);
+        second = NULL;
+      }
+      else
+        Curl_http2_setup_req(second);
+    }
+  }
+  return second;
+}
+
+
 static int push_promise(struct SessionHandle *data,
+                        struct connectdata *conn,
                         const nghttp2_push_promise *frame)
 {
   int rv;
+  DEBUGF(infof(data, "PUSH_PROMISE received, stream %u!\n",
+               frame->promised_stream_id));
   if(data->multi->push_cb) {
+    struct HTTP *stream;
+    struct curl_pushheaders heads;
+    CURLMcode rc;
+    struct http_conn *httpc;
     /* clone the parent */
-    CURL *newhandle = curl_easy_duphandle(data);
+    CURL *newhandle = duphandle(data);
     if(!newhandle) {
       infof(data, "failed to duplicate handle\n");
       rv = 1; /* FAIL HARD */
+      goto fail;
     }
-    else {
-      struct curl_pushheaders heads;
-      heads.data = data;
-      heads.frame = frame;
-      /* ask the application */
-      DEBUGF(infof(data, "Got PUSH_PROMISE, ask application!\n"));
-      rv = data->multi->push_cb(data, newhandle,
-                                frame->nvlen, &heads,
-                                data->multi->push_userp);
-      if(rv)
-        /* denied, kill off the new handle again */
-        (void)Curl_close(newhandle);
-      else {
-        /* approved, add to the multi handle */
-        CURLMcode rc = curl_multi_add_handle(data->multi, newhandle);
-        if(rc) {
-          infof(data, "failed to add handle to multi\n");
-          Curl_close(newhandle);
-          rv = 1;
-        }
-        else
-          rv = 0;
-      }
+
+    heads.data = data;
+    heads.frame = frame;
+    /* ask the application */
+    DEBUGF(infof(data, "Got PUSH_PROMISE, ask application!\n"));
+
+    stream = data->req.protop;
+
+#ifdef CURLDEBUG
+    fprintf(stderr, "PUSHHDR %s\n", stream->push_recvbuf->buffer);
+#endif
+
+    rv = data->multi->push_cb(data, newhandle,
+                              frame->nvlen, &heads,
+                              data->multi->push_userp);
+    if(rv) {
+      /* denied, kill off the new handle again */
+      (void)Curl_close(newhandle);
+      goto fail;
+    }
+
+    /* approved, add to the multi handle and immediately switch to PERFORM
+       state with the given connection !*/
+    rc = Curl_multi_add_perform(data->multi, newhandle, conn);
+    if(rc) {
+      infof(data, "failed to add handle to multi\n");
+      Curl_close(newhandle);
+      rv = 1;
+      goto fail;
     }
+
+    httpc = &conn->proto.httpc;
+    /* put the newhandle in the hash with the stream id as key */
+    if(!Curl_hash_add(&httpc->streamsh,
+                      (size_t *)&frame->promised_stream_id,
+                      sizeof(frame->promised_stream_id), newhandle)) {
+      failf(conn->data, "Couldn't add stream to hash!");
+      rv = 1;
+    }
+    else
+      rv = 0;
   }
   else {
     DEBUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n"));
     rv = 1;
   }
+  fail:
   return rv;
 }
 
@@ -358,7 +412,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
     Curl_expire(data_s, 1);
     break;
   case NGHTTP2_PUSH_PROMISE:
-    rv = push_promise(data_s, &frame->push_promise);
+    rv = push_promise(data_s, conn, &frame->push_promise);
     if(rv) { /* deny! */
       rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
                                      frame->push_promise.promised_stream_id,
@@ -591,11 +645,6 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
   (void)frame;
   (void)flags;
 
-  /* Ignore PUSH_PROMISE for now */
-  if(frame->hd.type != NGHTTP2_HEADERS) {
-    return 0;
-  }
-
   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
 
   /* get the stream from the hash based on Stream ID */
@@ -615,6 +664,21 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
        consequence is handled in on_frame_recv(). */
     return 0;
 
+  /* Store received PUSH_PROMISE headers to be used when the subsequent
+     PUSH_PROMISE callback comes */
+  if(frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+    fprintf(stderr, "*** PUSH_PROMISE headers on stream %u for %u\n",
+            stream_id,
+            frame->push_promise.promised_stream_id);
+    if(!stream->push_recvbuf)
+      stream->push_recvbuf = Curl_add_buffer_init();
+    Curl_add_buffer(stream->push_recvbuf, name, namelen);
+    Curl_add_buffer(stream->push_recvbuf, ":", 1);
+    Curl_add_buffer(stream->push_recvbuf, value, valuelen);
+    Curl_add_buffer(stream->push_recvbuf, "\r\n", 2);
+    return 0;
+  }
+
   if(namelen == sizeof(":status") - 1 &&
      memcmp(":status", name, namelen) == 0) {
     /* nghttp2 guarantees :status is received first and only once, and
index 1614736d3ba5861707b1292f84c9335dbc58aa3d..bb7ad9c4ccfc3c9f51f94a73b150d996a81f6670 100644 (file)
@@ -46,6 +46,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
                              const char *data, size_t nread);
 /* called from Curl_http_setup_conn */
 void Curl_http2_setup_conn(struct connectdata *conn);
+void Curl_http2_setup_req(struct SessionHandle *data);
 #else /* USE_NGHTTP2 */
 #define Curl_http2_init(x) CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_http2_send_request(x) CURLE_UNSUPPORTED_PROTOCOL
@@ -53,6 +54,7 @@ void Curl_http2_setup_conn(struct connectdata *conn);
 #define Curl_http2_setup(x) CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_http2_setup_conn(x)
+#define Curl_http2_setup_req(x)
 #endif
 
 #endif /* HEADER_CURL_HTTP2_H */
index 33c03f299fda672c1ce0bbac67bf4d75340fc017..a17af5a217040273265f601d41aff770b4ffb5ef 100644 (file)
@@ -950,6 +950,21 @@ static bool multi_ischanged(struct Curl_multi *multi, bool clear)
   return retval;
 }
 
+CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
+                                 struct SessionHandle *data,
+                                 struct connectdata *conn)
+{
+  CURLMcode rc;
+
+  rc = curl_multi_add_handle(multi, data);
+  if(!rc) {
+    /* take this handle to the perform state right away */
+    multistate(data, CURLM_STATE_PERFORM);
+    data->easy_conn = conn;
+  }
+  return rc;
+}
+
 static CURLMcode multi_runsingle(struct Curl_multi *multi,
                                  struct timeval now,
                                  struct SessionHandle *data)
index 5052f65aea31c9cc14cdc91cd49bf674440cc079..e6323adf568135d30e5b8aeeff1b3e9330b85436 100644 (file)
@@ -88,4 +88,10 @@ void Curl_multi_connchanged(struct Curl_multi *multi);
 
 void Curl_multi_closed(struct connectdata *conn, curl_socket_t s);
 
+/*
+ * Add a handle and move it into PERFORM state at once. For pushed streams.
+ */
+CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
+                                 struct SessionHandle *data,
+                                 struct connectdata *conn);
 #endif /* HEADER_CURL_MULTIIF_H */