2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2013 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 # define errx(exitcode, format, args...) \
29 warnx(format, ##args); \
32 # define warnx(format, args...) fprintf(stderr, format "\n", ##args)
33 char *strndup(const char *s, size_t size);
38 #endif /* HAVE_CONFIG_H */
40 #include <sys/types.h>
43 #endif /* HAVE_UNISTD_H */
44 #ifdef HAVE_SYS_SOCKET_H
45 # include <sys/socket.h>
46 #endif /* HAVE_SYS_SOCKET_H */
47 #ifdef HAVE_NETINET_IN_H
48 # include <netinet/in.h>
49 #endif /* HAVE_NETINET_IN_H */
50 #include <netinet/tcp.h>
57 #include <openssl/ssl.h>
58 #include <openssl/err.h>
59 #include <openssl/conf.h>
62 #include <event2/event.h>
63 #include <event2/bufferevent_ssl.h>
64 #include <event2/dns.h>
66 #include <nghttp2/nghttp2.h>
68 #include "url-parser/url_parser.h"
70 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
73 /* The NULL-terminated URI string to retrieve. */
75 /* Parsed result of the |uri| */
76 struct http_parser_url *u;
77 /* The authority portion of the |uri|, not NULL-terminated */
79 /* The path portion of the |uri|, including query, not
82 /* The length of the |authority| */
84 /* The length of the |path| */
86 /* The stream ID of this stream */
91 nghttp2_session *session;
92 struct evdns_base *dnsbase;
93 struct bufferevent *bev;
94 http2_stream_data *stream_data;
97 static http2_stream_data *create_http2_stream_data(const char *uri,
98 struct http_parser_url *u) {
99 /* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
101 http2_stream_data *stream_data = malloc(sizeof(http2_stream_data));
103 stream_data->uri = uri;
105 stream_data->stream_id = -1;
107 stream_data->authoritylen = u->field_data[UF_HOST].len;
108 stream_data->authority = malloc(stream_data->authoritylen + extra);
109 memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off],
110 u->field_data[UF_HOST].len);
111 if (u->field_set & (1 << UF_PORT)) {
112 stream_data->authoritylen +=
113 (size_t)snprintf(stream_data->authority + u->field_data[UF_HOST].len,
114 extra, ":%u", u->port);
117 /* If we don't have path in URI, we use "/" as path. */
118 stream_data->pathlen = 1;
119 if (u->field_set & (1 << UF_PATH)) {
120 stream_data->pathlen = u->field_data[UF_PATH].len;
122 if (u->field_set & (1 << UF_QUERY)) {
123 /* +1 for '?' character */
124 stream_data->pathlen += (size_t)(u->field_data[UF_QUERY].len + 1);
127 stream_data->path = malloc(stream_data->pathlen);
128 if (u->field_set & (1 << UF_PATH)) {
129 memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off],
130 u->field_data[UF_PATH].len);
132 stream_data->path[0] = '/';
134 if (u->field_set & (1 << UF_QUERY)) {
135 stream_data->path[stream_data->pathlen - u->field_data[UF_QUERY].len - 1] =
137 memcpy(stream_data->path + stream_data->pathlen -
138 u->field_data[UF_QUERY].len,
139 &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
145 static void delete_http2_stream_data(http2_stream_data *stream_data) {
146 free(stream_data->path);
147 free(stream_data->authority);
151 /* Initializes |session_data| */
152 static http2_session_data *
153 create_http2_session_data(struct event_base *evbase) {
154 http2_session_data *session_data = malloc(sizeof(http2_session_data));
156 memset(session_data, 0, sizeof(http2_session_data));
157 session_data->dnsbase = evdns_base_new(evbase, 1);
161 static void delete_http2_session_data(http2_session_data *session_data) {
162 SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
167 bufferevent_free(session_data->bev);
168 session_data->bev = NULL;
169 evdns_base_free(session_data->dnsbase, 1);
170 session_data->dnsbase = NULL;
171 nghttp2_session_del(session_data->session);
172 session_data->session = NULL;
173 if (session_data->stream_data) {
174 delete_http2_stream_data(session_data->stream_data);
175 session_data->stream_data = NULL;
180 static void print_header(FILE *f, const uint8_t *name, size_t namelen,
181 const uint8_t *value, size_t valuelen) {
182 fwrite(name, 1, namelen, f);
184 fwrite(value, 1, valuelen, f);
188 /* Print HTTP headers to |f|. Please note that this function does not
189 take into account that header name and value are sequence of
190 octets, therefore they may contain non-printable characters. */
191 static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) {
193 for (i = 0; i < nvlen; ++i) {
194 print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
199 /* nghttp2_send_callback. Here we transmit the |data|, |length| bytes,
200 to the network. Because we are using libevent bufferevent, we just
201 write those bytes into bufferevent buffer. */
202 static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
203 size_t length, int flags, void *user_data) {
204 http2_session_data *session_data = (http2_session_data *)user_data;
205 struct bufferevent *bev = session_data->bev;
209 bufferevent_write(bev, data, length);
210 return (ssize_t)length;
213 /* nghttp2_on_header_callback: Called when nghttp2 library emits
214 single header name/value pair. */
215 static int on_header_callback(nghttp2_session *session,
216 const nghttp2_frame *frame, const uint8_t *name,
217 size_t namelen, const uint8_t *value,
218 size_t valuelen, uint8_t flags, void *user_data) {
219 http2_session_data *session_data = (http2_session_data *)user_data;
223 switch (frame->hd.type) {
224 case NGHTTP2_HEADERS:
225 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
226 session_data->stream_data->stream_id == frame->hd.stream_id) {
227 /* Print response headers for the initiated request. */
228 print_header(stderr, name, namelen, value, valuelen);
235 /* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets
236 started to receive header block. */
237 static int on_begin_headers_callback(nghttp2_session *session,
238 const nghttp2_frame *frame,
240 http2_session_data *session_data = (http2_session_data *)user_data;
243 switch (frame->hd.type) {
244 case NGHTTP2_HEADERS:
245 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
246 session_data->stream_data->stream_id == frame->hd.stream_id) {
247 fprintf(stderr, "Response headers for stream ID=%d:\n",
248 frame->hd.stream_id);
255 /* nghttp2_on_frame_recv_callback: Called when nghttp2 library
256 received a complete frame from the remote peer. */
257 static int on_frame_recv_callback(nghttp2_session *session,
258 const nghttp2_frame *frame, void *user_data) {
259 http2_session_data *session_data = (http2_session_data *)user_data;
262 switch (frame->hd.type) {
263 case NGHTTP2_HEADERS:
264 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
265 session_data->stream_data->stream_id == frame->hd.stream_id) {
266 fprintf(stderr, "All headers received\n");
273 /* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is
274 received from the remote peer. In this implementation, if the frame
275 is meant to the stream we initiated, print the received data in
276 stdout, so that the user can redirect its output to the file
278 static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
279 int32_t stream_id, const uint8_t *data,
280 size_t len, void *user_data) {
281 http2_session_data *session_data = (http2_session_data *)user_data;
285 if (session_data->stream_data->stream_id == stream_id) {
286 fwrite(data, 1, len, stdout);
291 /* nghttp2_on_stream_close_callback: Called when a stream is about to
292 closed. This example program only deals with 1 HTTP request (1
293 stream), if it is closed, we send GOAWAY and tear down the
295 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
296 uint32_t error_code, void *user_data) {
297 http2_session_data *session_data = (http2_session_data *)user_data;
300 if (session_data->stream_data->stream_id == stream_id) {
301 fprintf(stderr, "Stream %d closed with error_code=%u\n", stream_id,
303 rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
305 return NGHTTP2_ERR_CALLBACK_FAILURE;
311 #ifndef OPENSSL_NO_NEXTPROTONEG
312 /* NPN TLS extension client callback. We check that server advertised
313 the HTTP/2 protocol the nghttp2 library supports. If not, exit
315 static int select_next_proto_cb(SSL *ssl, unsigned char **out,
316 unsigned char *outlen, const unsigned char *in,
317 unsigned int inlen, void *arg) {
321 if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
322 errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
324 return SSL_TLSEXT_ERR_OK;
326 #endif /* !OPENSSL_NO_NEXTPROTONEG */
328 /* Create SSL_CTX. */
329 static SSL_CTX *create_ssl_ctx(void) {
331 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
333 errx(1, "Could not create SSL/TLS context: %s",
334 ERR_error_string(ERR_get_error(), NULL));
336 SSL_CTX_set_options(ssl_ctx,
337 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
338 SSL_OP_NO_COMPRESSION |
339 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
340 #ifndef OPENSSL_NO_NEXTPROTONEG
341 SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
342 #endif /* !OPENSSL_NO_NEXTPROTONEG */
344 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
345 SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
346 #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
351 /* Create SSL object */
352 static SSL *create_ssl(SSL_CTX *ssl_ctx) {
354 ssl = SSL_new(ssl_ctx);
356 errx(1, "Could not create SSL/TLS session object: %s",
357 ERR_error_string(ERR_get_error(), NULL));
362 static void initialize_nghttp2_session(http2_session_data *session_data) {
363 nghttp2_session_callbacks *callbacks;
365 nghttp2_session_callbacks_new(&callbacks);
367 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
369 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
370 on_frame_recv_callback);
372 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
373 callbacks, on_data_chunk_recv_callback);
375 nghttp2_session_callbacks_set_on_stream_close_callback(
376 callbacks, on_stream_close_callback);
378 nghttp2_session_callbacks_set_on_header_callback(callbacks,
381 nghttp2_session_callbacks_set_on_begin_headers_callback(
382 callbacks, on_begin_headers_callback);
384 nghttp2_session_client_new(&session_data->session, callbacks, session_data);
386 nghttp2_session_callbacks_del(callbacks);
389 static void send_client_connection_header(http2_session_data *session_data) {
390 nghttp2_settings_entry iv[1] = {
391 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
394 /* client 24 bytes magic string will be sent by nghttp2 library */
395 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
398 errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
402 #define MAKE_NV(NAME, VALUE, VALUELEN) \
404 (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \
405 NGHTTP2_NV_FLAG_NONE \
408 #define MAKE_NV2(NAME, VALUE) \
410 (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
411 NGHTTP2_NV_FLAG_NONE \
414 /* Send HTTP request to the remote peer */
415 static void submit_request(http2_session_data *session_data) {
417 http2_stream_data *stream_data = session_data->stream_data;
418 const char *uri = stream_data->uri;
419 const struct http_parser_url *u = stream_data->u;
420 nghttp2_nv hdrs[] = {
421 MAKE_NV2(":method", "GET"),
422 MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
423 u->field_data[UF_SCHEMA].len),
424 MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
425 MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
426 fprintf(stderr, "Request headers:\n");
427 print_headers(stderr, hdrs, ARRLEN(hdrs));
428 stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
429 ARRLEN(hdrs), NULL, stream_data);
431 errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
434 stream_data->stream_id = stream_id;
437 /* Serialize the frame and send (or buffer) the data to
439 static int session_send(http2_session_data *session_data) {
442 rv = nghttp2_session_send(session_data->session);
444 warnx("Fatal error: %s", nghttp2_strerror(rv));
450 /* readcb for bufferevent. Here we get the data from the input buffer
451 of bufferevent and feed them to nghttp2 library. This may invoke
452 nghttp2 callbacks. It may also queues the frame in nghttp2 session
453 context. To send them, we call session_send() in the end. */
454 static void readcb(struct bufferevent *bev, void *ptr) {
455 http2_session_data *session_data = (http2_session_data *)ptr;
457 struct evbuffer *input = bufferevent_get_input(bev);
458 size_t datalen = evbuffer_get_length(input);
459 unsigned char *data = evbuffer_pullup(input, -1);
461 readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
463 warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
464 delete_http2_session_data(session_data);
467 if (evbuffer_drain(input, (size_t)readlen) != 0) {
468 warnx("Fatal error: evbuffer_drain failed");
469 delete_http2_session_data(session_data);
472 if (session_send(session_data) != 0) {
473 delete_http2_session_data(session_data);
478 /* writecb for bufferevent. To greaceful shutdown after sending or
479 receiving GOAWAY, we check the some conditions on the nghttp2
480 library and output buffer of bufferevent. If it indicates we have
481 no business to this session, tear down the connection. */
482 static void writecb(struct bufferevent *bev, void *ptr) {
483 http2_session_data *session_data = (http2_session_data *)ptr;
486 if (nghttp2_session_want_read(session_data->session) == 0 &&
487 nghttp2_session_want_write(session_data->session) == 0 &&
488 evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
489 delete_http2_session_data(session_data);
493 /* eventcb for bufferevent. For the purpose of simplicity and
494 readability of the example program, we omitted the certificate and
495 peer verification. After SSL/TLS handshake is over, initialize
496 nghttp2 library session, and send client connection header. Then
497 send HTTP request. */
498 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
499 http2_session_data *session_data = (http2_session_data *)ptr;
500 if (events & BEV_EVENT_CONNECTED) {
501 int fd = bufferevent_getfd(bev);
503 const unsigned char *alpn = NULL;
504 unsigned int alpnlen = 0;
507 fprintf(stderr, "Connected\n");
509 ssl = bufferevent_openssl_get_ssl(session_data->bev);
511 #ifndef OPENSSL_NO_NEXTPROTONEG
512 SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen);
513 #endif /* !OPENSSL_NO_NEXTPROTONEG */
514 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
516 SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
518 #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
520 if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
521 fprintf(stderr, "h2 is not negotiated\n");
522 delete_http2_session_data(session_data);
526 setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
527 initialize_nghttp2_session(session_data);
528 send_client_connection_header(session_data);
529 submit_request(session_data);
530 if (session_send(session_data) != 0) {
531 delete_http2_session_data(session_data);
535 if (events & BEV_EVENT_EOF) {
536 warnx("Disconnected from the remote host");
537 } else if (events & BEV_EVENT_ERROR) {
538 warnx("Network error");
539 } else if (events & BEV_EVENT_TIMEOUT) {
542 delete_http2_session_data(session_data);
545 /* Start connecting to the remote peer |host:port| */
546 static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
547 const char *host, uint16_t port,
548 http2_session_data *session_data) {
550 struct bufferevent *bev;
553 ssl = create_ssl(ssl_ctx);
554 bev = bufferevent_openssl_socket_new(
555 evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
556 BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
557 bufferevent_enable(bev, EV_READ | EV_WRITE);
558 bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
559 rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
560 AF_UNSPEC, host, port);
563 errx(1, "Could not connect to the remote host %s", host);
565 session_data->bev = bev;
568 /* Get resource denoted by the |uri|. The debug and error messages are
569 printed in stderr, while the response body is printed in stdout. */
570 static void run(const char *uri) {
571 struct http_parser_url u;
576 struct event_base *evbase;
577 http2_session_data *session_data;
579 /* Parse the |uri| and stores its components in |u| */
580 rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
582 errx(1, "Could not parse URI %s", uri);
584 host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
585 if (!(u.field_set & (1 << UF_PORT))) {
591 ssl_ctx = create_ssl_ctx();
593 evbase = event_base_new();
595 session_data = create_http2_session_data(evbase);
596 session_data->stream_data = create_http2_stream_data(uri, &u);
598 initiate_connection(evbase, ssl_ctx, host, port, session_data);
602 event_base_loop(evbase, 0);
604 event_base_free(evbase);
605 SSL_CTX_free(ssl_ctx);
608 int main(int argc, char **argv) {
609 struct sigaction act;
612 fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
616 memset(&act, 0, sizeof(struct sigaction));
617 act.sa_handler = SIG_IGN;
618 sigaction(SIGPIPE, &act, NULL);
620 SSL_load_error_strings();