Commit 4344fa92 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

http2: actually init nghttp2 and send HTTP2-Settings properly

parent 61672bde
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -21,8 +21,14 @@
 * 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,
+125 −8
Original line number Diff line number Diff line
@@ -30,6 +30,11 @@
#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,
  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);
  (void)conn;
                            "HTTP2-Settings: %s\r\n", base64);
  free(base64);

  return result;
}

+2 −1
Original line number Diff line number Diff line
@@ -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 */