http2: actually init nghttp2 and send HTTP2-Settings properly
authorDaniel Stenberg <daniel@haxx.se>
Sat, 7 Sep 2013 11:01:43 +0000 (13:01 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 7 Sep 2013 11:01:43 +0000 (13:01 +0200)
lib/http.h
lib/http2.c
lib/urldata.h

index ad2def81bc7efd4811c0e7785de478581805670d..b0b37b6ae356d9d2177b639edf05b6d4d4397a87 100644 (file)
  * KIND, either express or implied.
  *
  ***************************************************************************/
+#include "curl_setup.h"
+
 #ifndef CURL_DISABLE_HTTP
 
+#ifdef USE_NGHTTP2
+#include <nghttp2/nghttp2.h>
+#endif
+
 extern const struct Curl_handler Curl_handler_http;
 
 #ifdef USE_SSL
@@ -142,6 +148,12 @@ struct HTTP {
                         points to an allocated send_buffer struct */
 };
 
+struct http_conn {
+#ifdef USE_NGHTTP2
+  nghttp2_session *h2;
+#endif
+};
+
 CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
                                      struct connectdata *conn,
                                      ssize_t *nread,
index 1b4300aa9fee00cdc8f67a1d65e7f2bde20a2f0c..2f58059a0f82951fd57a88bf1325708077e6260d 100644 (file)
 #include "urldata.h"
 #include "http2.h"
 #include "http.h"
+#include "sendf.h"
+#include "curl_base64.h"
+
+/* include memdebug.h last */
+#include "memdebug.h"
 
 /*
  * Store nghttp2 version info in this buffer, Prefix with a space.  Return
@@ -41,20 +46,132 @@ int Curl_http2_ver(char *p, size_t len)
   return snprintf(p, len, " nghttp2/%s", h2->version_str);
 }
 
+/*
+ * The implementation of nghttp2_send_callback type. Here we write |data| with
+ * size |length| to the network and return the number of bytes actually
+ * written. See the documentation of nghttp2_send_callback for the details.
+ */
+static ssize_t send_callback(nghttp2_session *h2,
+                             const uint8_t *data, size_t length, int flags,
+                             void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  ssize_t written;
+  CURLcode rc =
+    Curl_write(conn, conn->sock[0], data, length, &written);
+  (void)h2;
+  (void)flags;
+
+  if(rc) {
+    failf(conn->data, "Failed sending HTTP2 data");
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  else if(!written)
+    return NGHTTP2_ERR_WOULDBLOCK;
+
+  return written;
+}
+
+/*
+ * The implementation of nghttp2_recv_callback type. Here we read data from
+ * the network and write them in |buf|. The capacity of |buf| is |length|
+ * bytes. Returns the number of bytes stored in |buf|. See the documentation
+ * of nghttp2_recv_callback for the details.
+ */
+static ssize_t recv_callback(nghttp2_session *h2,
+                             uint8_t *buf, size_t length, int flags,
+                             void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  ssize_t nread;
+  CURLcode rc = Curl_read(conn, conn->sock[0], (char *)buf, length, &nread);
+  (void)h2;
+  (void)flags;
+
+  if(rc) {
+    failf(conn->data, "Failed recving HTTP2 data");
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  if(!nread)
+    return NGHTTP2_ERR_WOULDBLOCK;
+
+  return nread;
+}
+
+/*
+ * This is all callbacks nghttp2 calls
+ */
+static const nghttp2_session_callbacks callbacks = {
+  send_callback,
+  recv_callback,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+/*
+ * The HTTP2 settings we send in the Upgrade request
+ */
+static nghttp2_settings_entry settings[] = {
+  { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 },
+  { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, NGHTTP2_INITIAL_WINDOW_SIZE },
+};
+
 /*
  * Append headers to ask for a HTTP1.1 to HTTP2 upgrade.
  */
 CURLcode Curl_http2_request(Curl_send_buffer *req,
                             struct connectdata *conn)
 {
-  const char *base64="AABBCC"; /* a fake string to start with */
-  CURLcode result =
-    Curl_add_bufferf(req,
-                     "Connection: Upgrade, HTTP2-Settings\r\n"
-                     "Upgrade: HTTP/2.0\r\n"
-                     "HTTP2-Settings: %s\r\n",
-                     base64);
-  (void)conn;
+  uint8_t binsettings[80];
+  CURLcode result;
+  ssize_t binlen;
+  char *base64;
+  size_t blen;
+
+  if(!conn->proto.httpc.h2) {
+    /* The nghttp2 session is not yet setup, do it */
+    int rc = nghttp2_session_client_new(&conn->proto.httpc.h2,
+                                        &callbacks, &conn);
+    if(rc) {
+      failf(conn->data, "Couldn't initialize nghttp2!");
+      return CURLE_OUT_OF_MEMORY; /* most likely at least */
+    }
+  }
+
+  /* As long as we have a fixed set of settings, we don't have to dynamically
+   * figure out the base64 strings since it'll always be the same. However,
+   * the settings will likely not be fixed every time in the future.
+   */
+
+  /* this returns number of bytes it wrote */
+  binlen = nghttp2_pack_settings_payload(binsettings, settings,
+                                         sizeof(settings)/sizeof(settings[0]));
+  if(!binlen) {
+    failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload");
+    return CURLE_FAILED_INIT;
+  }
+
+  result = Curl_base64_encode(conn->data, (const char *)binsettings, binlen,
+                              &base64, &blen);
+  if(result)
+    return result;
+
+  result = Curl_add_bufferf(req,
+                            "Connection: Upgrade, HTTP2-Settings\r\n"
+                            "Upgrade: HTTP/2.0\r\n"
+                            "HTTP2-Settings: %s\r\n", base64);
+  free(base64);
+
   return result;
 }
 
index 7d850e1e49762609ae18453706af77a57437ed96..ebaaf6ff4f04f95cf01128946831f55e04f6c066 100644 (file)
@@ -1001,13 +1001,14 @@ struct connectdata {
 
   union {
     struct ftp_conn ftpc;
+    struct http_conn httpc;
     struct ssh_conn sshc;
     struct tftp_state_data *tftpc;
     struct imap_conn imapc;
     struct pop3_conn pop3c;
     struct smtp_conn smtpc;
     struct rtsp_conn rtspc;
-    void *generic;
+    void *generic; /* RTMP and LDAP use this */
   } proto;
 
   int cselect_bits; /* bitmask of socket events */