summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/.gitignore5
-rw-r--r--examples/CMakeLists.txt37
-rw-r--r--examples/Makefile.am54
-rw-r--r--examples/client.c699
-rw-r--r--examples/deflate.c206
-rw-r--r--examples/libevent-client.c595
-rw-r--r--examples/libevent-server.c787
7 files changed, 2383 insertions, 0 deletions
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..16335a5
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,5 @@
+client
+libevent-client
+libevent-server
+deflate
+tiny-nghttpd
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..7a57bbf
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,37 @@
+if(ENABLE_EXAMPLES)
+ file(GLOB c_sources *.c)
+ set_source_files_properties(${c_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCFLAGS}")
+ file(GLOB cxx_sources *.cc)
+ set_source_files_properties(${cxx_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}")
+
+ include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include"
+
+ ${LIBEVENT_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ )
+
+ link_libraries(
+ nghttp2
+ ${LIBEVENT_OPENSSL_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${APP_LIBRARIES}
+ )
+
+ add_executable(client client.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(libevent-client libevent-client.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(libevent-server libevent-server.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(deflate deflate.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+endif()
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644
index 0000000..7b682ed
--- /dev/null
+++ b/examples/Makefile.am
@@ -0,0 +1,54 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+EXTRA_DIST = CMakeLists.txt
+
+if ENABLE_EXAMPLES
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/lib/includes \
+ -I$(top_builddir)/lib/includes \
+ -I$(top_srcdir)/third-party \
+ @LIBEVENT_OPENSSL_CFLAGS@ \
+ @OPENSSL_CFLAGS@ \
+ @DEFS@
+AM_LDFLAGS = @LIBTOOL_LDFLAGS@
+LDADD = $(top_builddir)/lib/libnghttp2.la \
+ $(top_builddir)/third-party/liburl-parser.la \
+ @LIBEVENT_OPENSSL_LIBS@ \
+ @OPENSSL_LIBS@ \
+ @APPLDFLAGS@
+
+noinst_PROGRAMS = client libevent-client libevent-server deflate
+
+client_SOURCES = client.c
+
+libevent_client_SOURCES = libevent-client.c
+
+libevent_server_SOURCES = libevent-server.c
+
+deflate_SOURCES = deflate.c
+
+endif # ENABLE_EXAMPLES
diff --git a/examples/client.c b/examples/client.c
new file mode 100644
index 0000000..ce8d1d0
--- /dev/null
+++ b/examples/client.c
@@ -0,0 +1,699 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * This program is written to show how to use nghttp2 API in C and
+ * intentionally made simple.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif /* HAVE_FCNTL_H */
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif /* HAVE_NETDB_H */
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+enum { IO_NONE, WANT_READ, WANT_WRITE };
+
+#define MAKE_NV(NAME, VALUE) \
+ { \
+ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+#define MAKE_NV_CS(NAME, VALUE) \
+ { \
+ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, strlen(VALUE), \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+struct Connection {
+ SSL *ssl;
+ nghttp2_session *session;
+ /* WANT_READ if SSL/TLS connection needs more input; or WANT_WRITE
+ if it needs more output; or IO_NONE. This is necessary because
+ SSL/TLS re-negotiation is possible at any time. nghttp2 API
+ offers similar functions like nghttp2_session_want_read() and
+ nghttp2_session_want_write() but they do not take into account
+ SSL/TSL connection. */
+ int want_io;
+};
+
+struct Request {
+ char *host;
+ /* In this program, path contains query component as well. */
+ char *path;
+ /* This is the concatenation of host and port with ":" in
+ between. */
+ char *hostport;
+ /* Stream ID for this request. */
+ int32_t stream_id;
+ uint16_t port;
+};
+
+struct URI {
+ const char *host;
+ /* In this program, path contains query component as well. */
+ const char *path;
+ size_t pathlen;
+ const char *hostport;
+ size_t hostlen;
+ size_t hostportlen;
+ uint16_t port;
+};
+
+/*
+ * Returns copy of string |s| with the length |len|. The returned
+ * string is NULL-terminated.
+ */
+static char *strcopy(const char *s, size_t len) {
+ char *dst;
+ dst = malloc(len + 1);
+ memcpy(dst, s, len);
+ dst[len] = '\0';
+ return dst;
+}
+
+/*
+ * Prints error message |msg| and exit.
+ */
+NGHTTP2_NORETURN
+static void die(const char *msg) {
+ fprintf(stderr, "FATAL: %s\n", msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and message |msg|
+ * and exit.
+ */
+NGHTTP2_NORETURN
+static void dief(const char *func, const char *msg) {
+ fprintf(stderr, "FATAL: %s: %s\n", func, msg);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and error code
+ * |error_code| and exit.
+ */
+NGHTTP2_NORETURN
+static void diec(const char *func, int error_code) {
+ fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code,
+ nghttp2_strerror(error_code));
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * 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 *session, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ struct Connection *connection;
+ int rv;
+ (void)session;
+ (void)flags;
+
+ connection = (struct Connection *)user_data;
+ connection->want_io = IO_NONE;
+ ERR_clear_error();
+ rv = SSL_write(connection->ssl, data, (int)length);
+ if (rv <= 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io =
+ (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
+ rv = NGHTTP2_ERR_WOULDBLOCK;
+ } else {
+ rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return rv;
+}
+
+/*
+ * 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 *session, uint8_t *buf,
+ size_t length, int flags, void *user_data) {
+ struct Connection *connection;
+ int rv;
+ (void)session;
+ (void)flags;
+
+ connection = (struct Connection *)user_data;
+ connection->want_io = IO_NONE;
+ ERR_clear_error();
+ rv = SSL_read(connection->ssl, buf, (int)length);
+ if (rv < 0) {
+ int err = SSL_get_error(connection->ssl, rv);
+ if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ connection->want_io =
+ (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
+ rv = NGHTTP2_ERR_WOULDBLOCK;
+ } else {
+ rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else if (rv == 0) {
+ rv = NGHTTP2_ERR_EOF;
+ }
+ return rv;
+}
+
+static int on_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ size_t i;
+ (void)user_data;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
+ const nghttp2_nv *nva = frame->headers.nva;
+ printf("[INFO] C ----------------------------> S (HEADERS)\n");
+ for (i = 0; i < frame->headers.nvlen; ++i) {
+ fwrite(nva[i].name, 1, nva[i].namelen, stdout);
+ printf(": ");
+ fwrite(nva[i].value, 1, nva[i].valuelen, stdout);
+ printf("\n");
+ }
+ }
+ break;
+ case NGHTTP2_RST_STREAM:
+ printf("[INFO] C ----------------------------> S (RST_STREAM)\n");
+ break;
+ case NGHTTP2_GOAWAY:
+ printf("[INFO] C ----------------------------> S (GOAWAY)\n");
+ break;
+ }
+ return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ size_t i;
+ (void)user_data;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
+ const nghttp2_nv *nva = frame->headers.nva;
+ struct Request *req;
+ req = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+ if (req) {
+ printf("[INFO] C <---------------------------- S (HEADERS)\n");
+ for (i = 0; i < frame->headers.nvlen; ++i) {
+ fwrite(nva[i].name, 1, nva[i].namelen, stdout);
+ printf(": ");
+ fwrite(nva[i].value, 1, nva[i].valuelen, stdout);
+ printf("\n");
+ }
+ }
+ }
+ break;
+ case NGHTTP2_RST_STREAM:
+ printf("[INFO] C <---------------------------- S (RST_STREAM)\n");
+ break;
+ case NGHTTP2_GOAWAY:
+ printf("[INFO] C <---------------------------- S (GOAWAY)\n");
+ break;
+ }
+ return 0;
+}
+
+/*
+ * The implementation of nghttp2_on_stream_close_callback type. We use
+ * this function to know the response is fully received. Since we just
+ * fetch 1 resource in this program, after reception of the response,
+ * we submit GOAWAY and close the session.
+ */
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ struct Request *req;
+ (void)error_code;
+ (void)user_data;
+
+ req = nghttp2_session_get_stream_user_data(session, stream_id);
+ if (req) {
+ int rv;
+ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+
+ if (rv != 0) {
+ diec("nghttp2_session_terminate_session", rv);
+ }
+ }
+ return 0;
+}
+
+/*
+ * The implementation of nghttp2_on_data_chunk_recv_callback type. We
+ * use this function to print the received response body.
+ */
+static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ struct Request *req;
+ (void)flags;
+ (void)user_data;
+
+ req = nghttp2_session_get_stream_user_data(session, stream_id);
+ if (req) {
+ printf("[INFO] C <---------------------------- S (DATA chunk)\n"
+ "%lu bytes\n",
+ (unsigned long int)len);
+ fwrite(data, 1, len, stdout);
+ printf("\n");
+ }
+ return 0;
+}
+
+/*
+ * Setup callback functions. nghttp2 API offers many callback
+ * functions, but most of them are optional. The send_callback is
+ * always required. Since we use nghttp2_session_recv(), the
+ * recv_callback is also required.
+ */
+static void setup_nghttp2_callbacks(nghttp2_session_callbacks *callbacks) {
+ nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+ nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
+
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+}
+
+/*
+ * Setup SSL/TLS context.
+ */
+static void init_ssl_ctx(SSL_CTX *ssl_ctx) {
+ /* Disable SSLv2 and enable all workarounds for buggy servers */
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
+}
+
+static void ssl_handshake(SSL *ssl, int fd) {
+ int rv;
+ if (SSL_set_fd(ssl, fd) == 0) {
+ dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL));
+ }
+ ERR_clear_error();
+ rv = SSL_connect(ssl);
+ if (rv <= 0) {
+ dief("SSL_connect", ERR_error_string(ERR_get_error(), NULL));
+ }
+}
+
+/*
+ * Connects to the host |host| and port |port|. This function returns
+ * the file descriptor of the client socket.
+ */
+static int connect_to(const char *host, uint16_t port) {
+ struct addrinfo hints;
+ int fd = -1;
+ int rv;
+ char service[NI_MAXSERV];
+ struct addrinfo *res, *rp;
+ snprintf(service, sizeof(service), "%u", port);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ rv = getaddrinfo(host, service, &hints, &res);
+ if (rv != 0) {
+ dief("getaddrinfo", gai_strerror(rv));
+ }
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ continue;
+ }
+ while ((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 &&
+ errno == EINTR)
+ ;
+ if (rv == 0) {
+ break;
+ }
+ close(fd);
+ fd = -1;
+ }
+ freeaddrinfo(res);
+ return fd;
+}
+
+static void make_non_block(int fd) {
+ int flags, rv;
+ while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
+ ;
+ if (flags == -1) {
+ dief("fcntl", strerror(errno));
+ }
+ while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
+ ;
+ if (rv == -1) {
+ dief("fcntl", strerror(errno));
+ }
+}
+
+static void set_tcp_nodelay(int fd) {
+ int val = 1;
+ int rv;
+ rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+ if (rv == -1) {
+ dief("setsockopt", strerror(errno));
+ }
+}
+
+/*
+ * Update |pollfd| based on the state of |connection|.
+ */
+static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) {
+ pollfd->events = 0;
+ if (nghttp2_session_want_read(connection->session) ||
+ connection->want_io == WANT_READ) {
+ pollfd->events |= POLLIN;
+ }
+ if (nghttp2_session_want_write(connection->session) ||
+ connection->want_io == WANT_WRITE) {
+ pollfd->events |= POLLOUT;
+ }
+}
+
+/*
+ * Submits the request |req| to the connection |connection|. This
+ * function does not send packets; just append the request to the
+ * internal queue in |connection->session|.
+ */
+static void submit_request(struct Connection *connection, struct Request *req) {
+ int32_t stream_id;
+ /* Make sure that the last item is NULL */
+ const nghttp2_nv nva[] = {MAKE_NV(":method", "GET"),
+ MAKE_NV_CS(":path", req->path),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV_CS(":authority", req->hostport),
+ MAKE_NV("accept", "*/*"),
+ MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)};
+
+ stream_id = nghttp2_submit_request(connection->session, NULL, nva,
+ sizeof(nva) / sizeof(nva[0]), NULL, req);
+
+ if (stream_id < 0) {
+ diec("nghttp2_submit_request", stream_id);
+ }
+
+ req->stream_id = stream_id;
+ printf("[INFO] Stream ID = %d\n", stream_id);
+}
+
+/*
+ * Performs the network I/O.
+ */
+static void exec_io(struct Connection *connection) {
+ int rv;
+ rv = nghttp2_session_recv(connection->session);
+ if (rv != 0) {
+ diec("nghttp2_session_recv", rv);
+ }
+ rv = nghttp2_session_send(connection->session);
+ if (rv != 0) {
+ diec("nghttp2_session_send", rv);
+ }
+}
+
+static void request_init(struct Request *req, const struct URI *uri) {
+ req->host = strcopy(uri->host, uri->hostlen);
+ req->port = uri->port;
+ req->path = strcopy(uri->path, uri->pathlen);
+ req->hostport = strcopy(uri->hostport, uri->hostportlen);
+ req->stream_id = -1;
+}
+
+static void request_free(struct Request *req) {
+ free(req->host);
+ free(req->path);
+ free(req->hostport);
+}
+
+/*
+ * Fetches the resource denoted by |uri|.
+ */
+static void fetch_uri(const struct URI *uri) {
+ nghttp2_session_callbacks *callbacks;
+ int fd;
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+ struct Request req;
+ struct Connection connection;
+ int rv;
+ nfds_t npollfds = 1;
+ struct pollfd pollfds[1];
+
+ request_init(&req, uri);
+
+ /* Establish connection and setup SSL */
+ fd = connect_to(req.host, req.port);
+ if (fd == -1) {
+ die("Could not open file descriptor");
+ }
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (ssl_ctx == NULL) {
+ dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+ init_ssl_ctx(ssl_ctx);
+ ssl = SSL_new(ssl_ctx);
+ if (ssl == NULL) {
+ dief("SSL_new", ERR_error_string(ERR_get_error(), NULL));
+ }
+ /* To simplify the program, we perform SSL/TLS handshake in blocking
+ I/O. */
+ ssl_handshake(ssl, fd);
+
+ connection.ssl = ssl;
+ connection.want_io = IO_NONE;
+
+ /* Here make file descriptor non-block */
+ make_non_block(fd);
+ set_tcp_nodelay(fd);
+
+ printf("[INFO] SSL/TLS handshake completed\n");
+
+ rv = nghttp2_session_callbacks_new(&callbacks);
+
+ if (rv != 0) {
+ diec("nghttp2_session_callbacks_new", rv);
+ }
+
+ setup_nghttp2_callbacks(callbacks);
+
+ rv = nghttp2_session_client_new(&connection.session, callbacks, &connection);
+
+ nghttp2_session_callbacks_del(callbacks);
+
+ if (rv != 0) {
+ diec("nghttp2_session_client_new", rv);
+ }
+
+ rv = nghttp2_submit_settings(connection.session, NGHTTP2_FLAG_NONE, NULL, 0);
+
+ if (rv != 0) {
+ diec("nghttp2_submit_settings", rv);
+ }
+
+ /* Submit the HTTP request to the outbound queue. */
+ submit_request(&connection, &req);
+
+ pollfds[0].fd = fd;
+ ctl_poll(pollfds, &connection);
+
+ /* Event loop */
+ while (nghttp2_session_want_read(connection.session) ||
+ nghttp2_session_want_write(connection.session)) {
+ int nfds = poll(pollfds, npollfds, -1);
+ if (nfds == -1) {
+ dief("poll", strerror(errno));
+ }
+ if (pollfds[0].revents & (POLLIN | POLLOUT)) {
+ exec_io(&connection);
+ }
+ if ((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) {
+ die("Connection error");
+ }
+ ctl_poll(pollfds, &connection);
+ }
+
+ /* Resource cleanup */
+ nghttp2_session_del(connection.session);
+ SSL_shutdown(ssl);
+ SSL_free(ssl);
+ SSL_CTX_free(ssl_ctx);
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ request_free(&req);
+}
+
+static int parse_uri(struct URI *res, const char *uri) {
+ /* We only interested in https */
+ size_t len, i, offset;
+ int ipv6addr = 0;
+ memset(res, 0, sizeof(struct URI));
+ len = strlen(uri);
+ if (len < 9 || memcmp("https://", uri, 8) != 0) {
+ return -1;
+ }
+ offset = 8;
+ res->host = res->hostport = &uri[offset];
+ res->hostlen = 0;
+ if (uri[offset] == '[') {
+ /* IPv6 literal address */
+ ++offset;
+ ++res->host;
+ ipv6addr = 1;
+ for (i = offset; i < len; ++i) {
+ if (uri[i] == ']') {
+ res->hostlen = i - offset;
+ offset = i + 1;
+ break;
+ }
+ }
+ } else {
+ const char delims[] = ":/?#";
+ for (i = offset; i < len; ++i) {
+ if (strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ }
+ res->hostlen = i - offset;
+ offset = i;
+ }
+ if (res->hostlen == 0) {
+ return -1;
+ }
+ /* Assuming https */
+ res->port = 443;
+ if (offset < len) {
+ if (uri[offset] == ':') {
+ /* port */
+ const char delims[] = "/?#";
+ int port = 0;
+ ++offset;
+ for (i = offset; i < len; ++i) {
+ if (strchr(delims, uri[i]) != NULL) {
+ break;
+ }
+ if ('0' <= uri[i] && uri[i] <= '9') {
+ port *= 10;
+ port += uri[i] - '0';
+ if (port > 65535) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+ if (port == 0) {
+ return -1;
+ }
+ offset = i;
+ res->port = (uint16_t)port;
+ }
+ }
+ res->hostportlen = (size_t)(uri + offset + ipv6addr - res->host);
+ for (i = offset; i < len; ++i) {
+ if (uri[i] == '#') {
+ break;
+ }
+ }
+ if (i - offset == 0) {
+ res->path = "/";
+ res->pathlen = 1;
+ } else {
+ res->path = &uri[offset];
+ res->pathlen = i - offset;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ struct URI uri;
+ struct sigaction act;
+ int rv;
+
+ if (argc < 2) {
+ die("Specify a https URI");
+ }
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, 0);
+
+ rv = parse_uri(&uri, argv[1]);
+ if (rv != 0) {
+ die("parse_uri failed");
+ }
+ fetch_uri(&uri);
+ return EXIT_SUCCESS;
+}
diff --git a/examples/deflate.c b/examples/deflate.c
new file mode 100644
index 0000000..df1cb92
--- /dev/null
+++ b/examples/deflate.c
@@ -0,0 +1,206 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* !HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <nghttp2/nghttp2.h>
+
+#define MAKE_NV(K, V) \
+ { \
+ (uint8_t *)K, (uint8_t *)V, sizeof(K) - 1, sizeof(V) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+static void deflate(nghttp2_hd_deflater *deflater,
+ nghttp2_hd_inflater *inflater, const nghttp2_nv *const nva,
+ size_t nvlen);
+
+static int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in,
+ size_t inlen, int final);
+
+int main(void) {
+ int rv;
+ nghttp2_hd_deflater *deflater;
+ nghttp2_hd_inflater *inflater;
+ /* Define 1st header set. This is looks like a HTTP request. */
+ nghttp2_nv nva1[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"),
+ MAKE_NV(":path", "/"), MAKE_NV("user-agent", "libnghttp2"),
+ MAKE_NV("accept-encoding", "gzip, deflate")};
+ /* Define 2nd header set */
+ nghttp2_nv nva2[] = {MAKE_NV(":scheme", "https"),
+ MAKE_NV(":authority", "example.org"),
+ MAKE_NV(":path", "/stylesheet/style.css"),
+ MAKE_NV("user-agent", "libnghttp2"),
+ MAKE_NV("accept-encoding", "gzip, deflate"),
+ MAKE_NV("referer", "https://example.org")};
+
+ rv = nghttp2_hd_deflate_new(&deflater, 4096);
+
+ if (rv != 0) {
+ fprintf(stderr, "nghttp2_hd_deflate_init failed with error: %s\n",
+ nghttp2_strerror(rv));
+ exit(EXIT_FAILURE);
+ }
+
+ rv = nghttp2_hd_inflate_new(&inflater);
+
+ if (rv != 0) {
+ fprintf(stderr, "nghttp2_hd_inflate_init failed with error: %s\n",
+ nghttp2_strerror(rv));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Encode and decode 1st header set */
+ deflate(deflater, inflater, nva1, sizeof(nva1) / sizeof(nva1[0]));
+
+ /* Encode and decode 2nd header set, using differential encoding
+ using state after encoding 1st header set. */
+ deflate(deflater, inflater, nva2, sizeof(nva2) / sizeof(nva2[0]));
+
+ nghttp2_hd_inflate_del(inflater);
+ nghttp2_hd_deflate_del(deflater);
+
+ return 0;
+}
+
+static void deflate(nghttp2_hd_deflater *deflater,
+ nghttp2_hd_inflater *inflater, const nghttp2_nv *const nva,
+ size_t nvlen) {
+ ssize_t rv;
+ uint8_t *buf;
+ size_t buflen;
+ size_t outlen;
+ size_t i;
+ size_t sum;
+
+ sum = 0;
+
+ for (i = 0; i < nvlen; ++i) {
+ sum += nva[i].namelen + nva[i].valuelen;
+ }
+
+ printf("Input (%zu byte(s)):\n\n", sum);
+
+ for (i = 0; i < nvlen; ++i) {
+ fwrite(nva[i].name, 1, nva[i].namelen, stdout);
+ printf(": ");
+ fwrite(nva[i].value, 1, nva[i].valuelen, stdout);
+ printf("\n");
+ }
+
+ buflen = nghttp2_hd_deflate_bound(deflater, nva, nvlen);
+ buf = malloc(buflen);
+
+ rv = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, nvlen);
+
+ if (rv < 0) {
+ fprintf(stderr, "nghttp2_hd_deflate_hd() failed with error: %s\n",
+ nghttp2_strerror((int)rv));
+
+ free(buf);
+
+ exit(EXIT_FAILURE);
+ }
+
+ outlen = (size_t)rv;
+
+ printf("\nDeflate (%zu byte(s), ratio %.02f):\n\n", outlen,
+ sum == 0 ? 0 : (double)outlen / (double)sum);
+
+ for (i = 0; i < outlen; ++i) {
+ if ((i & 0x0fu) == 0) {
+ printf("%08zX: ", i);
+ }
+
+ printf("%02X ", buf[i]);
+
+ if (((i + 1) & 0x0fu) == 0) {
+ printf("\n");
+ }
+ }
+
+ printf("\n\nInflate:\n\n");
+
+ /* We pass 1 to final parameter, because buf contains whole deflated
+ header data. */
+ rv = inflate_header_block(inflater, buf, outlen, 1);
+
+ if (rv != 0) {
+ free(buf);
+
+ exit(EXIT_FAILURE);
+ }
+
+ printf("\n-----------------------------------------------------------"
+ "--------------------\n");
+
+ free(buf);
+}
+
+int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in,
+ size_t inlen, int final) {
+ ssize_t rv;
+
+ for (;;) {
+ nghttp2_nv nv;
+ int inflate_flags = 0;
+ size_t proclen;
+
+ rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, in, inlen, final);
+
+ if (rv < 0) {
+ fprintf(stderr, "inflate failed with error code %zd", rv);
+ return -1;
+ }
+
+ proclen = (size_t)rv;
+
+ in += proclen;
+ inlen -= proclen;
+
+ if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ fwrite(nv.name, 1, nv.namelen, stderr);
+ fprintf(stderr, ": ");
+ fwrite(nv.value, 1, nv.valuelen, stderr);
+ fprintf(stderr, "\n");
+ }
+
+ if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+ nghttp2_hd_inflate_end_headers(inflater);
+ break;
+ }
+
+ if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) {
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/examples/libevent-client.c b/examples/libevent-client.c
new file mode 100644
index 0000000..0e1c14a
--- /dev/null
+++ b/examples/libevent-client.c
@@ -0,0 +1,595 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef __sgi
+# include <string.h>
+# define errx(exitcode, format, args...) \
+ { \
+ warnx(format, ##args); \
+ exit(exitcode); \
+ }
+# define warnx(format, args...) fprintf(stderr, format "\n", ##args)
+char *strndup(const char *s, size_t size);
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+#include <netinet/tcp.h>
+#ifndef __sgi
+# include <err.h>
+#endif
+#include <signal.h>
+#include <string.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#include <event.h>
+#include <event2/event.h>
+#include <event2/bufferevent_ssl.h>
+#include <event2/dns.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "url-parser/url_parser.h"
+
+#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
+
+typedef struct {
+ /* The NULL-terminated URI string to retrieve. */
+ const char *uri;
+ /* Parsed result of the |uri| */
+ struct http_parser_url *u;
+ /* The authority portion of the |uri|, not NULL-terminated */
+ char *authority;
+ /* The path portion of the |uri|, including query, not
+ NULL-terminated */
+ char *path;
+ /* The length of the |authority| */
+ size_t authoritylen;
+ /* The length of the |path| */
+ size_t pathlen;
+ /* The stream ID of this stream */
+ int32_t stream_id;
+} http2_stream_data;
+
+typedef struct {
+ nghttp2_session *session;
+ struct evdns_base *dnsbase;
+ struct bufferevent *bev;
+ http2_stream_data *stream_data;
+} http2_session_data;
+
+static http2_stream_data *create_http2_stream_data(const char *uri,
+ struct http_parser_url *u) {
+ /* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
+ size_t extra = 7;
+ http2_stream_data *stream_data = malloc(sizeof(http2_stream_data));
+
+ stream_data->uri = uri;
+ stream_data->u = u;
+ stream_data->stream_id = -1;
+
+ stream_data->authoritylen = u->field_data[UF_HOST].len;
+ stream_data->authority = malloc(stream_data->authoritylen + extra);
+ memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off],
+ u->field_data[UF_HOST].len);
+ if (u->field_set & (1 << UF_PORT)) {
+ stream_data->authoritylen +=
+ (size_t)snprintf(stream_data->authority + u->field_data[UF_HOST].len,
+ extra, ":%u", u->port);
+ }
+
+ /* If we don't have path in URI, we use "/" as path. */
+ stream_data->pathlen = 1;
+ if (u->field_set & (1 << UF_PATH)) {
+ stream_data->pathlen = u->field_data[UF_PATH].len;
+ }
+ if (u->field_set & (1 << UF_QUERY)) {
+ /* +1 for '?' character */
+ stream_data->pathlen += (size_t)(u->field_data[UF_QUERY].len + 1);
+ }
+
+ stream_data->path = malloc(stream_data->pathlen);
+ if (u->field_set & (1 << UF_PATH)) {
+ memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off],
+ u->field_data[UF_PATH].len);
+ } else {
+ stream_data->path[0] = '/';
+ }
+ if (u->field_set & (1 << UF_QUERY)) {
+ stream_data->path[stream_data->pathlen - u->field_data[UF_QUERY].len - 1] =
+ '?';
+ memcpy(stream_data->path + stream_data->pathlen -
+ u->field_data[UF_QUERY].len,
+ &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
+ }
+
+ return stream_data;
+}
+
+static void delete_http2_stream_data(http2_stream_data *stream_data) {
+ free(stream_data->path);
+ free(stream_data->authority);
+ free(stream_data);
+}
+
+/* Initializes |session_data| */
+static http2_session_data *
+create_http2_session_data(struct event_base *evbase) {
+ http2_session_data *session_data = malloc(sizeof(http2_session_data));
+
+ memset(session_data, 0, sizeof(http2_session_data));
+ session_data->dnsbase = evdns_base_new(evbase, 1);
+ return session_data;
+}
+
+static void delete_http2_session_data(http2_session_data *session_data) {
+ SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ if (ssl) {
+ SSL_shutdown(ssl);
+ }
+ bufferevent_free(session_data->bev);
+ session_data->bev = NULL;
+ evdns_base_free(session_data->dnsbase, 1);
+ session_data->dnsbase = NULL;
+ nghttp2_session_del(session_data->session);
+ session_data->session = NULL;
+ if (session_data->stream_data) {
+ delete_http2_stream_data(session_data->stream_data);
+ session_data->stream_data = NULL;
+ }
+ free(session_data);
+}
+
+static void print_header(FILE *f, const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen) {
+ fwrite(name, 1, namelen, f);
+ fprintf(f, ": ");
+ fwrite(value, 1, valuelen, f);
+ fprintf(f, "\n");
+}
+
+/* Print HTTP headers to |f|. Please note that this function does not
+ take into account that header name and value are sequence of
+ octets, therefore they may contain non-printable characters. */
+static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) {
+ size_t i;
+ for (i = 0; i < nvlen; ++i) {
+ print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
+ }
+ fprintf(f, "\n");
+}
+
+/* nghttp2_send_callback. Here we transmit the |data|, |length| bytes,
+ to the network. Because we are using libevent bufferevent, we just
+ write those bytes into bufferevent buffer. */
+static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ struct bufferevent *bev = session_data->bev;
+ (void)session;
+ (void)flags;
+
+ bufferevent_write(bev, data, length);
+ return (ssize_t)length;
+}
+
+/* nghttp2_on_header_callback: Called when nghttp2 library emits
+ single header name/value pair. */
+static int on_header_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, const uint8_t *name,
+ size_t namelen, const uint8_t *value,
+ size_t valuelen, uint8_t flags, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+ (void)flags;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+ session_data->stream_data->stream_id == frame->hd.stream_id) {
+ /* Print response headers for the initiated request. */
+ print_header(stderr, name, namelen, value, valuelen);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets
+ started to receive header block. */
+static int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+ session_data->stream_data->stream_id == frame->hd.stream_id) {
+ fprintf(stderr, "Response headers for stream ID=%d:\n",
+ frame->hd.stream_id);
+ }
+ break;
+ }
+ return 0;
+}
+
+/* nghttp2_on_frame_recv_callback: Called when nghttp2 library
+ received a complete frame from the remote peer. */
+static int on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+ session_data->stream_data->stream_id == frame->hd.stream_id) {
+ fprintf(stderr, "All headers received\n");
+ }
+ break;
+ }
+ return 0;
+}
+
+/* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is
+ received from the remote peer. In this implementation, if the frame
+ is meant to the stream we initiated, print the received data in
+ stdout, so that the user can redirect its output to the file
+ easily. */
+static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+ (void)flags;
+
+ if (session_data->stream_data->stream_id == stream_id) {
+ fwrite(data, 1, len, stdout);
+ }
+ return 0;
+}
+
+/* nghttp2_on_stream_close_callback: Called when a stream is about to
+ closed. This example program only deals with 1 HTTP request (1
+ stream), if it is closed, we send GOAWAY and tear down the
+ session */
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ int rv;
+
+ if (session_data->stream_data->stream_id == stream_id) {
+ fprintf(stderr, "Stream %d closed with error_code=%u\n", stream_id,
+ error_code);
+ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+ if (rv != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return 0;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ssl_ctx(void) {
+ SSL_CTX *ssl_ctx;
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx) {
+ errx(1, "Could not create SSL/TLS context: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ SSL_CTX_set_options(ssl_ctx,
+ SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
+
+ return ssl_ctx;
+}
+
+/* Create SSL object */
+static SSL *create_ssl(SSL_CTX *ssl_ctx) {
+ SSL *ssl;
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ errx(1, "Could not create SSL/TLS session object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ return ssl;
+}
+
+static void initialize_nghttp2_session(http2_session_data *session_data) {
+ nghttp2_session_callbacks *callbacks;
+
+ nghttp2_session_callbacks_new(&callbacks);
+
+ nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback(callbacks,
+ on_header_callback);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_client_new(&session_data->session, callbacks, session_data);
+
+ nghttp2_session_callbacks_del(callbacks);
+}
+
+static void send_client_connection_header(http2_session_data *session_data) {
+ nghttp2_settings_entry iv[1] = {
+ {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+ int rv;
+
+ /* client 24 bytes magic string will be sent by nghttp2 library */
+ rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
+ ARRLEN(iv));
+ if (rv != 0) {
+ errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
+ }
+}
+
+#define MAKE_NV(NAME, VALUE, VALUELEN) \
+ { \
+ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+#define MAKE_NV2(NAME, VALUE) \
+ { \
+ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+/* Send HTTP request to the remote peer */
+static void submit_request(http2_session_data *session_data) {
+ int32_t stream_id;
+ http2_stream_data *stream_data = session_data->stream_data;
+ const char *uri = stream_data->uri;
+ const struct http_parser_url *u = stream_data->u;
+ nghttp2_nv hdrs[] = {
+ MAKE_NV2(":method", "GET"),
+ MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
+ u->field_data[UF_SCHEMA].len),
+ MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
+ MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
+ fprintf(stderr, "Request headers:\n");
+ print_headers(stderr, hdrs, ARRLEN(hdrs));
+ stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
+ ARRLEN(hdrs), NULL, stream_data);
+ if (stream_id < 0) {
+ errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
+ }
+
+ stream_data->stream_id = stream_id;
+}
+
+/* Serialize the frame and send (or buffer) the data to
+ bufferevent. */
+static int session_send(http2_session_data *session_data) {
+ int rv;
+
+ rv = nghttp2_session_send(session_data->session);
+ if (rv != 0) {
+ warnx("Fatal error: %s", nghttp2_strerror(rv));
+ return -1;
+ }
+ return 0;
+}
+
+/* readcb for bufferevent. Here we get the data from the input buffer
+ of bufferevent and feed them to nghttp2 library. This may invoke
+ nghttp2 callbacks. It may also queues the frame in nghttp2 session
+ context. To send them, we call session_send() in the end. */
+static void readcb(struct bufferevent *bev, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ ssize_t readlen;
+ struct evbuffer *input = bufferevent_get_input(bev);
+ size_t datalen = evbuffer_get_length(input);
+ unsigned char *data = evbuffer_pullup(input, -1);
+
+ readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
+ if (readlen < 0) {
+ warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
+ delete_http2_session_data(session_data);
+ return;
+ }
+ if (evbuffer_drain(input, (size_t)readlen) != 0) {
+ warnx("Fatal error: evbuffer_drain failed");
+ delete_http2_session_data(session_data);
+ return;
+ }
+ if (session_send(session_data) != 0) {
+ delete_http2_session_data(session_data);
+ return;
+ }
+}
+
+/* writecb for bufferevent. To greaceful shutdown after sending or
+ receiving GOAWAY, we check the some conditions on the nghttp2
+ library and output buffer of bufferevent. If it indicates we have
+ no business to this session, tear down the connection. */
+static void writecb(struct bufferevent *bev, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ (void)bev;
+
+ if (nghttp2_session_want_read(session_data->session) == 0 &&
+ nghttp2_session_want_write(session_data->session) == 0 &&
+ evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
+ delete_http2_session_data(session_data);
+ }
+}
+
+/* eventcb for bufferevent. For the purpose of simplicity and
+ readability of the example program, we omitted the certificate and
+ peer verification. After SSL/TLS handshake is over, initialize
+ nghttp2 library session, and send client connection header. Then
+ send HTTP request. */
+static void eventcb(struct bufferevent *bev, short events, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ if (events & BEV_EVENT_CONNECTED) {
+ int fd = bufferevent_getfd(bev);
+ int val = 1;
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+ SSL *ssl;
+
+ fprintf(stderr, "Connected\n");
+
+ ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ if (alpn == NULL) {
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+ }
+
+ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+ fprintf(stderr, "h2 is not negotiated\n");
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+ initialize_nghttp2_session(session_data);
+ send_client_connection_header(session_data);
+ submit_request(session_data);
+ if (session_send(session_data) != 0) {
+ delete_http2_session_data(session_data);
+ }
+ return;
+ }
+ if (events & BEV_EVENT_EOF) {
+ warnx("Disconnected from the remote host");
+ } else if (events & BEV_EVENT_ERROR) {
+ warnx("Network error");
+ } else if (events & BEV_EVENT_TIMEOUT) {
+ warnx("Timeout");
+ }
+ delete_http2_session_data(session_data);
+}
+
+/* Start connecting to the remote peer |host:port| */
+static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
+ const char *host, uint16_t port,
+ http2_session_data *session_data) {
+ int rv;
+ struct bufferevent *bev;
+ SSL *ssl;
+
+ ssl = create_ssl(ssl_ctx);
+ bev = bufferevent_openssl_socket_new(
+ evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
+ BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
+ bufferevent_enable(bev, EV_READ | EV_WRITE);
+ bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
+ rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
+ AF_UNSPEC, host, port);
+
+ if (rv != 0) {
+ errx(1, "Could not connect to the remote host %s", host);
+ }
+ session_data->bev = bev;
+}
+
+/* Get resource denoted by the |uri|. The debug and error messages are
+ printed in stderr, while the response body is printed in stdout. */
+static void run(const char *uri) {
+ struct http_parser_url u;
+ char *host;
+ uint16_t port;
+ int rv;
+ SSL_CTX *ssl_ctx;
+ struct event_base *evbase;
+ http2_session_data *session_data;
+
+ /* Parse the |uri| and stores its components in |u| */
+ rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
+ if (rv != 0) {
+ errx(1, "Could not parse URI %s", uri);
+ }
+ host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
+ if (!(u.field_set & (1 << UF_PORT))) {
+ port = 443;
+ } else {
+ port = u.port;
+ }
+
+ ssl_ctx = create_ssl_ctx();
+
+ evbase = event_base_new();
+
+ session_data = create_http2_session_data(evbase);
+ session_data->stream_data = create_http2_stream_data(uri, &u);
+
+ initiate_connection(evbase, ssl_ctx, host, port, session_data);
+ free(host);
+ host = NULL;
+
+ event_base_loop(evbase, 0);
+
+ event_base_free(evbase);
+ SSL_CTX_free(ssl_ctx);
+}
+
+int main(int argc, char **argv) {
+ struct sigaction act;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, NULL);
+
+ run(argv[1]);
+ return 0;
+}
diff --git a/examples/libevent-server.c b/examples/libevent-server.c
new file mode 100644
index 0000000..fa45d37
--- /dev/null
+++ b/examples/libevent-server.c
@@ -0,0 +1,787 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef __sgi
+# define errx(exitcode, format, args...) \
+ { \
+ warnx(format, ##args); \
+ exit(exitcode); \
+ }
+# define warn(format, args...) warnx(format ": %s", ##args, strerror(errno))
+# define warnx(format, args...) fprintf(stderr, format "\n", ##args)
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif /* HAVE_NETDB_H */
+#include <signal.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif /* HAVE_FCNTL_H */
+#include <ctype.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+#include <netinet/tcp.h>
+#ifndef __sgi
+# include <err.h>
+#endif
+#include <string.h>
+#include <errno.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#include <event.h>
+#include <event2/event.h>
+#include <event2/bufferevent_ssl.h>
+#include <event2/listener.h>
+
+#include <nghttp2/nghttp2.h>
+
+#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16)
+
+#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
+
+#define MAKE_NV(NAME, VALUE) \
+ { \
+ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+struct app_context;
+typedef struct app_context app_context;
+
+typedef struct http2_stream_data {
+ struct http2_stream_data *prev, *next;
+ char *request_path;
+ int32_t stream_id;
+ int fd;
+} http2_stream_data;
+
+typedef struct http2_session_data {
+ struct http2_stream_data root;
+ struct bufferevent *bev;
+ app_context *app_ctx;
+ nghttp2_session *session;
+ char *client_addr;
+} http2_session_data;
+
+struct app_context {
+ SSL_CTX *ssl_ctx;
+ struct event_base *evbase;
+};
+
+static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ int rv;
+ (void)ssl;
+ (void)arg;
+
+ rv = nghttp2_select_alpn(out, outlen, in, inlen);
+
+ if (rv != 1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
+ SSL_CTX *ssl_ctx;
+
+ ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ errx(1, "Could not create SSL/TLS context: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ SSL_CTX_set_options(ssl_ctx,
+ SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
+ errx(1, "SSL_CTX_set1_curves_list failed: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+ {
+ EC_KEY *ecdh;
+ ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+ if (!ecdh) {
+ errx(1, "EC_KEY_new_by_curv_name failed: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+ EC_KEY_free(ecdh);
+ }
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) {
+ errx(1, "Could not read private key file %s", key_file);
+ }
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
+ errx(1, "Could not read certificate file %s", cert_file);
+ }
+
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL);
+
+ return ssl_ctx;
+}
+
+/* Create SSL object */
+static SSL *create_ssl(SSL_CTX *ssl_ctx) {
+ SSL *ssl;
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ errx(1, "Could not create SSL/TLS session object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ return ssl;
+}
+
+static void add_stream(http2_session_data *session_data,
+ http2_stream_data *stream_data) {
+ stream_data->next = session_data->root.next;
+ session_data->root.next = stream_data;
+ stream_data->prev = &session_data->root;
+ if (stream_data->next) {
+ stream_data->next->prev = stream_data;
+ }
+}
+
+static void remove_stream(http2_session_data *session_data,
+ http2_stream_data *stream_data) {
+ (void)session_data;
+
+ stream_data->prev->next = stream_data->next;
+ if (stream_data->next) {
+ stream_data->next->prev = stream_data->prev;
+ }
+}
+
+static http2_stream_data *
+create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) {
+ http2_stream_data *stream_data;
+ stream_data = malloc(sizeof(http2_stream_data));
+ memset(stream_data, 0, sizeof(http2_stream_data));
+ stream_data->stream_id = stream_id;
+ stream_data->fd = -1;
+
+ add_stream(session_data, stream_data);
+ return stream_data;
+}
+
+static void delete_http2_stream_data(http2_stream_data *stream_data) {
+ if (stream_data->fd != -1) {
+ close(stream_data->fd);
+ }
+ free(stream_data->request_path);
+ free(stream_data);
+}
+
+static http2_session_data *create_http2_session_data(app_context *app_ctx,
+ int fd,
+ struct sockaddr *addr,
+ int addrlen) {
+ int rv;
+ http2_session_data *session_data;
+ SSL *ssl;
+ char host[NI_MAXHOST];
+ int val = 1;
+
+ ssl = create_ssl(app_ctx->ssl_ctx);
+ session_data = malloc(sizeof(http2_session_data));
+ memset(session_data, 0, sizeof(http2_session_data));
+ session_data->app_ctx = app_ctx;
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+ session_data->bev = bufferevent_openssl_socket_new(
+ app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING,
+ BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
+ bufferevent_enable(session_data->bev, EV_READ | EV_WRITE);
+ rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ session_data->client_addr = strdup("(unknown)");
+ } else {
+ session_data->client_addr = strdup(host);
+ }
+
+ return session_data;
+}
+
+static void delete_http2_session_data(http2_session_data *session_data) {
+ http2_stream_data *stream_data;
+ SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
+ fprintf(stderr, "%s disconnected\n", session_data->client_addr);
+ if (ssl) {
+ SSL_shutdown(ssl);
+ }
+ bufferevent_free(session_data->bev);
+ nghttp2_session_del(session_data->session);
+ for (stream_data = session_data->root.next; stream_data;) {
+ http2_stream_data *next = stream_data->next;
+ delete_http2_stream_data(stream_data);
+ stream_data = next;
+ }
+ free(session_data->client_addr);
+ free(session_data);
+}
+
+/* Serialize the frame and send (or buffer) the data to
+ bufferevent. */
+static int session_send(http2_session_data *session_data) {
+ int rv;
+ rv = nghttp2_session_send(session_data->session);
+ if (rv != 0) {
+ warnx("Fatal error: %s", nghttp2_strerror(rv));
+ return -1;
+ }
+ return 0;
+}
+
+/* Read the data in the bufferevent and feed them into nghttp2 library
+ function. Invocation of nghttp2_session_mem_recv() may make
+ additional pending frames, so call session_send() at the end of the
+ function. */
+static int session_recv(http2_session_data *session_data) {
+ ssize_t readlen;
+ struct evbuffer *input = bufferevent_get_input(session_data->bev);
+ size_t datalen = evbuffer_get_length(input);
+ unsigned char *data = evbuffer_pullup(input, -1);
+
+ readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
+ if (readlen < 0) {
+ warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
+ return -1;
+ }
+ if (evbuffer_drain(input, (size_t)readlen) != 0) {
+ warnx("Fatal error: evbuffer_drain failed");
+ return -1;
+ }
+ if (session_send(session_data) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ struct bufferevent *bev = session_data->bev;
+ (void)session;
+ (void)flags;
+
+ /* Avoid excessive buffering in server side. */
+ if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >=
+ OUTPUT_WOULDBLOCK_THRESHOLD) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ bufferevent_write(bev, data, length);
+ return (ssize_t)length;
+}
+
+/* Returns nonzero if the string |s| ends with the substring |sub| */
+static int ends_with(const char *s, const char *sub) {
+ size_t slen = strlen(s);
+ size_t sublen = strlen(sub);
+ if (slen < sublen) {
+ return 0;
+ }
+ return memcmp(s + slen - sublen, sub, sublen) == 0;
+}
+
+/* Returns int value of hex string character |c| */
+static uint8_t hex_to_uint(uint8_t c) {
+ if ('0' <= c && c <= '9') {
+ return (uint8_t)(c - '0');
+ }
+ if ('A' <= c && c <= 'F') {
+ return (uint8_t)(c - 'A' + 10);
+ }
+ if ('a' <= c && c <= 'f') {
+ return (uint8_t)(c - 'a' + 10);
+ }
+ return 0;
+}
+
+/* Decodes percent-encoded byte string |value| with length |valuelen|
+ and returns the decoded byte string in allocated buffer. The return
+ value is NULL terminated. The caller must free the returned
+ string. */
+static char *percent_decode(const uint8_t *value, size_t valuelen) {
+ char *res;
+
+ res = malloc(valuelen + 1);
+ if (valuelen > 3) {
+ size_t i, j;
+ for (i = 0, j = 0; i < valuelen - 2;) {
+ if (value[i] != '%' || !isxdigit(value[i + 1]) ||
+ !isxdigit(value[i + 2])) {
+ res[j++] = (char)value[i++];
+ continue;
+ }
+ res[j++] =
+ (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2]));
+ i += 3;
+ }
+ memcpy(&res[j], &value[i], 2);
+ res[j + 2] = '\0';
+ } else {
+ memcpy(res, value, valuelen);
+ res[valuelen] = '\0';
+ }
+ return res;
+}
+
+static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *user_data) {
+ int fd = source->fd;
+ ssize_t r;
+ (void)session;
+ (void)stream_id;
+ (void)user_data;
+
+ while ((r = read(fd, buf, length)) == -1 && errno == EINTR)
+ ;
+ if (r == -1) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ if (r == 0) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ return r;
+}
+
+static int send_response(nghttp2_session *session, int32_t stream_id,
+ nghttp2_nv *nva, size_t nvlen, int fd) {
+ int rv;
+ nghttp2_data_provider data_prd;
+ data_prd.source.fd = fd;
+ data_prd.read_callback = file_read_callback;
+
+ rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd);
+ if (rv != 0) {
+ warnx("Fatal error: %s", nghttp2_strerror(rv));
+ return -1;
+ }
+ return 0;
+}
+
+static const char ERROR_HTML[] = "<html><head><title>404</title></head>"
+ "<body><h1>404 Not Found</h1></body></html>";
+
+static int error_reply(nghttp2_session *session,
+ http2_stream_data *stream_data) {
+ int rv;
+ ssize_t writelen;
+ int pipefd[2];
+ nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")};
+
+ rv = pipe(pipefd);
+ if (rv != 0) {
+ warn("Could not create pipe");
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ stream_data->stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (rv != 0) {
+ warnx("Fatal error: %s", nghttp2_strerror(rv));
+ return -1;
+ }
+ return 0;
+ }
+
+ writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1);
+ close(pipefd[1]);
+
+ if (writelen != sizeof(ERROR_HTML) - 1) {
+ close(pipefd[0]);
+ return -1;
+ }
+
+ stream_data->fd = pipefd[0];
+
+ if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs),
+ pipefd[0]) != 0) {
+ close(pipefd[0]);
+ return -1;
+ }
+ return 0;
+}
+
+/* nghttp2_on_header_callback: Called when nghttp2 library emits
+ single header name/value pair. */
+static int on_header_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, const uint8_t *name,
+ size_t namelen, const uint8_t *value,
+ size_t valuelen, uint8_t flags, void *user_data) {
+ http2_stream_data *stream_data;
+ const char PATH[] = ":path";
+ (void)flags;
+ (void)user_data;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ break;
+ }
+ stream_data =
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+ if (!stream_data || stream_data->request_path) {
+ break;
+ }
+ if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) {
+ size_t j;
+ for (j = 0; j < valuelen && value[j] != '?'; ++j)
+ ;
+ stream_data->request_path = percent_decode(value, j);
+ }
+ break;
+ }
+ return 0;
+}
+
+static int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ http2_stream_data *stream_data;
+
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+ stream_data = create_http2_stream_data(session_data, frame->hd.stream_id);
+ nghttp2_session_set_stream_user_data(session, frame->hd.stream_id,
+ stream_data);
+ return 0;
+}
+
+/* Minimum check for directory traversal. Returns nonzero if it is
+ safe. */
+static int check_path(const char *path) {
+ /* We don't like '\' in url. */
+ return path[0] && path[0] == '/' && strchr(path, '\\') == NULL &&
+ strstr(path, "/../") == NULL && strstr(path, "/./") == NULL &&
+ !ends_with(path, "/..") && !ends_with(path, "/.");
+}
+
+static int on_request_recv(nghttp2_session *session,
+ http2_session_data *session_data,
+ http2_stream_data *stream_data) {
+ int fd;
+ nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")};
+ char *rel_path;
+
+ if (!stream_data->request_path) {
+ if (error_reply(session, stream_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+ fprintf(stderr, "%s GET %s\n", session_data->client_addr,
+ stream_data->request_path);
+ if (!check_path(stream_data->request_path)) {
+ if (error_reply(session, stream_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+ for (rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path)
+ ;
+ fd = open(rel_path, O_RDONLY);
+ if (fd == -1) {
+ if (error_reply(session, stream_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+ stream_data->fd = fd;
+
+ if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) !=
+ 0) {
+ close(fd);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ http2_stream_data *stream_data;
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ case NGHTTP2_HEADERS:
+ /* Check that the client request has finished */
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ stream_data =
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+ /* For DATA and HEADERS frame, this callback may be called after
+ on_stream_close_callback. Check that stream still alive. */
+ if (!stream_data) {
+ return 0;
+ }
+ return on_request_recv(session, session_data, stream_data);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ http2_stream_data *stream_data;
+ (void)error_code;
+
+ stream_data = nghttp2_session_get_stream_user_data(session, stream_id);
+ if (!stream_data) {
+ return 0;
+ }
+ remove_stream(session_data, stream_data);
+ delete_http2_stream_data(stream_data);
+ return 0;
+}
+
+static void initialize_nghttp2_session(http2_session_data *session_data) {
+ nghttp2_session_callbacks *callbacks;
+
+ nghttp2_session_callbacks_new(&callbacks);
+
+ nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback(callbacks,
+ on_header_callback);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_server_new(&session_data->session, callbacks, session_data);
+
+ nghttp2_session_callbacks_del(callbacks);
+}
+
+/* Send HTTP/2 client connection header, which includes 24 bytes
+ magic octets and SETTINGS frame */
+static int send_server_connection_header(http2_session_data *session_data) {
+ nghttp2_settings_entry iv[1] = {
+ {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+ int rv;
+
+ rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
+ ARRLEN(iv));
+ if (rv != 0) {
+ warnx("Fatal error: %s", nghttp2_strerror(rv));
+ return -1;
+ }
+ return 0;
+}
+
+/* readcb for bufferevent after client connection header was
+ checked. */
+static void readcb(struct bufferevent *bev, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ (void)bev;
+
+ if (session_recv(session_data) != 0) {
+ delete_http2_session_data(session_data);
+ return;
+ }
+}
+
+/* writecb for bufferevent. To greaceful shutdown after sending or
+ receiving GOAWAY, we check the some conditions on the nghttp2
+ library and output buffer of bufferevent. If it indicates we have
+ no business to this session, tear down the connection. If the
+ connection is not going to shutdown, we call session_send() to
+ process pending data in the output buffer. This is necessary
+ because we have a threshold on the buffer size to avoid too much
+ buffering. See send_callback(). */
+static void writecb(struct bufferevent *bev, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
+ return;
+ }
+ if (nghttp2_session_want_read(session_data->session) == 0 &&
+ nghttp2_session_want_write(session_data->session) == 0) {
+ delete_http2_session_data(session_data);
+ return;
+ }
+ if (session_send(session_data) != 0) {
+ delete_http2_session_data(session_data);
+ return;
+ }
+}
+
+/* eventcb for bufferevent */
+static void eventcb(struct bufferevent *bev, short events, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ if (events & BEV_EVENT_CONNECTED) {
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+ SSL *ssl;
+ (void)bev;
+
+ fprintf(stderr, "%s connected\n", session_data->client_addr);
+
+ ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+
+ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+ fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr);
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ initialize_nghttp2_session(session_data);
+
+ if (send_server_connection_header(session_data) != 0 ||
+ session_send(session_data) != 0) {
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ return;
+ }
+ if (events & BEV_EVENT_EOF) {
+ fprintf(stderr, "%s EOF\n", session_data->client_addr);
+ } else if (events & BEV_EVENT_ERROR) {
+ fprintf(stderr, "%s network error\n", session_data->client_addr);
+ } else if (events & BEV_EVENT_TIMEOUT) {
+ fprintf(stderr, "%s timeout\n", session_data->client_addr);
+ }
+ delete_http2_session_data(session_data);
+}
+
+/* callback for evconnlistener */
+static void acceptcb(struct evconnlistener *listener, int fd,
+ struct sockaddr *addr, int addrlen, void *arg) {
+ app_context *app_ctx = (app_context *)arg;
+ http2_session_data *session_data;
+ (void)listener;
+
+ session_data = create_http2_session_data(app_ctx, fd, addr, addrlen);
+
+ bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data);
+}
+
+static void start_listen(struct event_base *evbase, const char *service,
+ app_context *app_ctx) {
+ int rv;
+ struct addrinfo hints;
+ struct addrinfo *res, *rp;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif /* AI_ADDRCONFIG */
+
+ rv = getaddrinfo(NULL, service, &hints, &res);
+ if (rv != 0) {
+ errx(1, "Could not resolve server address");
+ }
+ for (rp = res; rp; rp = rp->ai_next) {
+ struct evconnlistener *listener;
+ listener = evconnlistener_new_bind(
+ evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
+ 16, rp->ai_addr, (int)rp->ai_addrlen);
+ if (listener) {
+ freeaddrinfo(res);
+
+ return;
+ }
+ }
+ errx(1, "Could not start listener");
+}
+
+static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx,
+ struct event_base *evbase) {
+ memset(app_ctx, 0, sizeof(app_context));
+ app_ctx->ssl_ctx = ssl_ctx;
+ app_ctx->evbase = evbase;
+}
+
+static void run(const char *service, const char *key_file,
+ const char *cert_file) {
+ SSL_CTX *ssl_ctx;
+ app_context app_ctx;
+ struct event_base *evbase;
+
+ ssl_ctx = create_ssl_ctx(key_file, cert_file);
+ evbase = event_base_new();
+ initialize_app_context(&app_ctx, ssl_ctx, evbase);
+ start_listen(evbase, service, &app_ctx);
+
+ event_base_loop(evbase, 0);
+
+ event_base_free(evbase);
+ SSL_CTX_free(ssl_ctx);
+}
+
+int main(int argc, char **argv) {
+ struct sigaction act;
+
+ if (argc < 4) {
+ fprintf(stderr, "Usage: libevent-server PORT KEY_FILE CERT_FILE\n");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, NULL);
+
+ run(argv[1], argv[2], argv[3]);
+ return 0;
+}