From 4344fa926ae07bdd9530370b213505c894d10abb Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 7 Sep 2013 13:01:43 +0200 Subject: [PATCH] http2: actually init nghttp2 and send HTTP2-Settings properly --- lib/http.h | 12 +++++ lib/http2.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++--- lib/urldata.h | 3 +- 3 files changed, 139 insertions(+), 9 deletions(-) diff --git a/lib/http.h b/lib/http.h index ad2def81b..b0b37b6ae 100644 --- a/lib/http.h +++ b/lib/http.h @@ -21,8 +21,14 @@ * KIND, either express or implied. * ***************************************************************************/ +#include "curl_setup.h" + #ifndef CURL_DISABLE_HTTP +#ifdef USE_NGHTTP2 +#include +#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, diff --git a/lib/http2.c b/lib/http2.c index 1b4300aa9..2f58059a0 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -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, - "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; } diff --git a/lib/urldata.h b/lib/urldata.h index 7d850e1e4..ebaaf6ff4 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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 */