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 #endif /* HAVE_CONFIG_H */
29 #include <sys/types.h>
32 #endif /* HAVE_UNISTD_H */
33 #ifdef HAVE_SYS_SOCKET_H
34 #include <sys/socket.h>
35 #endif /* HAVE_SYS_SOCKET_H */
36 #ifdef HAVE_NETINET_IN_H
37 #include <netinet/in.h>
38 #endif /* HAVE_NETINET_IN_H */
39 #include <netinet/tcp.h>
43 #include <openssl/ssl.h>
44 #include <openssl/err.h>
45 #include <openssl/conf.h>
48 #include <event2/event.h>
49 #include <event2/bufferevent_ssl.h>
50 #include <event2/dns.h>
52 #include <nghttp2/nghttp2.h>
54 #include "http-parser/http_parser.h"
56 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
59 /* The NULL-terminated URI string to retrieve. */
61 /* Parsed result of the |uri| */
62 struct http_parser_url *u;
63 /* The authority portion of the |uri|, not NULL-terminated */
65 /* The path portion of the |uri|, including query, not
68 /* The length of the |authority| */
70 /* The length of the |path| */
72 /* The stream ID of this stream */
77 nghttp2_session *session;
78 struct evdns_base *dnsbase;
79 struct bufferevent *bev;
80 http2_stream_data *stream_data;
83 static http2_stream_data *create_http2_stream_data(const char *uri,
84 struct http_parser_url *u) {
85 /* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
87 http2_stream_data *stream_data = malloc(sizeof(http2_stream_data));
89 stream_data->uri = uri;
91 stream_data->stream_id = -1;
93 stream_data->authoritylen = u->field_data[UF_HOST].len;
94 stream_data->authority = malloc(stream_data->authoritylen + extra);
95 memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off],
96 u->field_data[UF_HOST].len);
97 if (u->field_set & (1 << UF_PORT)) {
98 stream_data->authoritylen +=
99 snprintf(stream_data->authority + u->field_data[UF_HOST].len, extra,
103 stream_data->pathlen = 0;
104 if (u->field_set & (1 << UF_PATH)) {
105 stream_data->pathlen = u->field_data[UF_PATH].len;
107 if (u->field_set & (1 << UF_QUERY)) {
108 /* +1 for '?' character */
109 stream_data->pathlen += u->field_data[UF_QUERY].len + 1;
111 if (stream_data->pathlen > 0) {
112 stream_data->path = malloc(stream_data->pathlen);
113 if (u->field_set & (1 << UF_PATH)) {
114 memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off],
115 u->field_data[UF_PATH].len);
117 if (u->field_set & (1 << UF_QUERY)) {
118 memcpy(stream_data->path + u->field_data[UF_PATH].len + 1,
119 &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
122 stream_data->path = NULL;
127 static void delete_http2_stream_data(http2_stream_data *stream_data) {
128 free(stream_data->path);
129 free(stream_data->authority);
133 /* Initializes |session_data| */
134 static http2_session_data *
135 create_http2_session_data(struct event_base *evbase) {
136 http2_session_data *session_data = malloc(sizeof(http2_session_data));
138 memset(session_data, 0, sizeof(http2_session_data));
139 session_data->dnsbase = evdns_base_new(evbase, 1);
143 static void delete_http2_session_data(http2_session_data *session_data) {
144 SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
149 bufferevent_free(session_data->bev);
150 session_data->bev = NULL;
151 evdns_base_free(session_data->dnsbase, 1);
152 session_data->dnsbase = NULL;
153 nghttp2_session_del(session_data->session);
154 session_data->session = NULL;
155 if (session_data->stream_data) {
156 delete_http2_stream_data(session_data->stream_data);
157 session_data->stream_data = NULL;
162 static void print_header(FILE *f, const uint8_t *name, size_t namelen,
163 const uint8_t *value, size_t valuelen) {
164 fwrite(name, namelen, 1, f);
166 fwrite(value, valuelen, 1, f);
170 /* Print HTTP headers to |f|. Please note that this function does not
171 take into account that header name and value are sequence of
172 octets, therefore they may contain non-printable characters. */
173 static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) {
175 for (i = 0; i < nvlen; ++i) {
176 print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
181 /* nghttp2_send_callback. Here we transmit the |data|, |length| bytes,
182 to the network. Because we are using libevent bufferevent, we just
183 write those bytes into bufferevent buffer. */
184 static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
185 size_t length, int flags _U_, void *user_data) {
186 http2_session_data *session_data = (http2_session_data *)user_data;
187 struct bufferevent *bev = session_data->bev;
188 bufferevent_write(bev, data, length);
192 /* nghttp2_on_header_callback: Called when nghttp2 library emits
193 single header name/value pair. */
194 static int on_header_callback(nghttp2_session *session _U_,
195 const nghttp2_frame *frame, const uint8_t *name,
196 size_t namelen, const uint8_t *value,
197 size_t valuelen, uint8_t flags _U_,
199 http2_session_data *session_data = (http2_session_data *)user_data;
200 switch (frame->hd.type) {
201 case NGHTTP2_HEADERS:
202 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
203 session_data->stream_data->stream_id == frame->hd.stream_id) {
204 /* Print response headers for the initiated request. */
205 print_header(stderr, name, namelen, value, valuelen);
212 /* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets
213 started to receive header block. */
214 static int on_begin_headers_callback(nghttp2_session *session _U_,
215 const nghttp2_frame *frame,
217 http2_session_data *session_data = (http2_session_data *)user_data;
218 switch (frame->hd.type) {
219 case NGHTTP2_HEADERS:
220 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
221 session_data->stream_data->stream_id == frame->hd.stream_id) {
222 fprintf(stderr, "Response headers for stream ID=%d:\n",
223 frame->hd.stream_id);
230 /* nghttp2_on_frame_recv_callback: Called when nghttp2 library
231 received a complete frame from the remote peer. */
232 static int on_frame_recv_callback(nghttp2_session *session _U_,
233 const nghttp2_frame *frame, void *user_data) {
234 http2_session_data *session_data = (http2_session_data *)user_data;
235 switch (frame->hd.type) {
236 case NGHTTP2_HEADERS:
237 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
238 session_data->stream_data->stream_id == frame->hd.stream_id) {
239 fprintf(stderr, "All headers received\n");
246 /* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is
247 received from the remote peer. In this implementation, if the frame
248 is meant to the stream we initiated, print the received data in
249 stdout, so that the user can redirect its output to the file
251 static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
252 uint8_t flags _U_, int32_t stream_id,
253 const uint8_t *data, size_t len,
255 http2_session_data *session_data = (http2_session_data *)user_data;
256 if (session_data->stream_data->stream_id == stream_id) {
257 fwrite(data, len, 1, stdout);
262 /* nghttp2_on_stream_close_callback: Called when a stream is about to
263 closed. This example program only deals with 1 HTTP request (1
264 stream), if it is closed, we send GOAWAY and tear down the
266 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
267 uint32_t error_code, void *user_data) {
268 http2_session_data *session_data = (http2_session_data *)user_data;
271 if (session_data->stream_data->stream_id == stream_id) {
272 fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
274 rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
276 return NGHTTP2_ERR_CALLBACK_FAILURE;
282 /* NPN TLS extension client callback. We check that server advertised
283 the HTTP/2 protocol the nghttp2 library supports. If not, exit
285 static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
286 unsigned char *outlen, const unsigned char *in,
287 unsigned int inlen, void *arg _U_) {
288 if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
289 errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
291 return SSL_TLSEXT_ERR_OK;
294 /* Create SSL_CTX. */
295 static SSL_CTX *create_ssl_ctx(void) {
297 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
299 errx(1, "Could not create SSL/TLS context: %s",
300 ERR_error_string(ERR_get_error(), NULL));
302 SSL_CTX_set_options(ssl_ctx,
303 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
304 SSL_OP_NO_COMPRESSION |
305 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
306 SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
310 /* Create SSL object */
311 static SSL *create_ssl(SSL_CTX *ssl_ctx) {
313 ssl = SSL_new(ssl_ctx);
315 errx(1, "Could not create SSL/TLS session object: %s",
316 ERR_error_string(ERR_get_error(), NULL));
321 static void initialize_nghttp2_session(http2_session_data *session_data) {
322 nghttp2_session_callbacks *callbacks;
324 nghttp2_session_callbacks_new(&callbacks);
326 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
328 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
329 on_frame_recv_callback);
331 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
332 callbacks, on_data_chunk_recv_callback);
334 nghttp2_session_callbacks_set_on_stream_close_callback(
335 callbacks, on_stream_close_callback);
337 nghttp2_session_callbacks_set_on_header_callback(callbacks,
340 nghttp2_session_callbacks_set_on_begin_headers_callback(
341 callbacks, on_begin_headers_callback);
343 nghttp2_session_client_new(&session_data->session, callbacks, session_data);
345 nghttp2_session_callbacks_del(callbacks);
348 static void send_client_connection_header(http2_session_data *session_data) {
349 nghttp2_settings_entry iv[1] = {
350 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
353 /* client 24 bytes magic string will be sent by nghttp2 library */
354 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
357 errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
361 #define MAKE_NV(NAME, VALUE, VALUELEN) \
363 (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \
364 NGHTTP2_NV_FLAG_NONE \
367 #define MAKE_NV2(NAME, VALUE) \
369 (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
370 NGHTTP2_NV_FLAG_NONE \
373 /* Send HTTP request to the remote peer */
374 static void submit_request(http2_session_data *session_data) {
376 http2_stream_data *stream_data = session_data->stream_data;
377 const char *uri = stream_data->uri;
378 const struct http_parser_url *u = stream_data->u;
379 nghttp2_nv hdrs[] = {
380 MAKE_NV2(":method", "GET"),
381 MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
382 u->field_data[UF_SCHEMA].len),
383 MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
384 MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
385 fprintf(stderr, "Request headers:\n");
386 print_headers(stderr, hdrs, ARRLEN(hdrs));
387 stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
388 ARRLEN(hdrs), NULL, stream_data);
390 errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
393 stream_data->stream_id = stream_id;
396 /* Serialize the frame and send (or buffer) the data to
398 static int session_send(http2_session_data *session_data) {
401 rv = nghttp2_session_send(session_data->session);
403 warnx("Fatal error: %s", nghttp2_strerror(rv));
409 /* readcb for bufferevent. Here we get the data from the input buffer
410 of bufferevent and feed them to nghttp2 library. This may invoke
411 nghttp2 callbacks. It may also queues the frame in nghttp2 session
412 context. To send them, we call session_send() in the end. */
413 static void readcb(struct bufferevent *bev, void *ptr) {
414 http2_session_data *session_data = (http2_session_data *)ptr;
416 struct evbuffer *input = bufferevent_get_input(bev);
417 size_t datalen = evbuffer_get_length(input);
418 unsigned char *data = evbuffer_pullup(input, -1);
420 readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
422 warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
423 delete_http2_session_data(session_data);
426 if (evbuffer_drain(input, readlen) != 0) {
427 warnx("Fatal error: evbuffer_drain failed");
428 delete_http2_session_data(session_data);
431 if (session_send(session_data) != 0) {
432 delete_http2_session_data(session_data);
437 /* writecb for bufferevent. To greaceful shutdown after sending or
438 receiving GOAWAY, we check the some conditions on the nghttp2
439 library and output buffer of bufferevent. If it indicates we have
440 no business to this session, tear down the connection. */
441 static void writecb(struct bufferevent *bev _U_, void *ptr) {
442 http2_session_data *session_data = (http2_session_data *)ptr;
443 if (nghttp2_session_want_read(session_data->session) == 0 &&
444 nghttp2_session_want_write(session_data->session) == 0 &&
445 evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
446 delete_http2_session_data(session_data);
450 /* eventcb for bufferevent. For the purpose of simplicity and
451 readability of the example program, we omitted the certificate and
452 peer verification. After SSL/TLS handshake is over, initialize
453 nghttp2 library session, and send client connection header. Then
454 send HTTP request. */
455 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
456 http2_session_data *session_data = (http2_session_data *)ptr;
457 if (events & BEV_EVENT_CONNECTED) {
458 int fd = bufferevent_getfd(bev);
460 fprintf(stderr, "Connected\n");
461 setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
462 initialize_nghttp2_session(session_data);
463 send_client_connection_header(session_data);
464 submit_request(session_data);
465 if (session_send(session_data) != 0) {
466 delete_http2_session_data(session_data);
470 if (events & BEV_EVENT_EOF) {
471 warnx("Disconnected from the remote host");
472 } else if (events & BEV_EVENT_ERROR) {
473 warnx("Network error");
474 } else if (events & BEV_EVENT_TIMEOUT) {
477 delete_http2_session_data(session_data);
480 /* Start connecting to the remote peer |host:port| */
481 static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
482 const char *host, uint16_t port,
483 http2_session_data *session_data) {
485 struct bufferevent *bev;
488 ssl = create_ssl(ssl_ctx);
489 bev = bufferevent_openssl_socket_new(
490 evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
491 BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
492 bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
493 rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
494 AF_UNSPEC, host, port);
497 errx(1, "Could not connect to the remote host %s", host);
499 session_data->bev = bev;
502 /* Get resource denoted by the |uri|. The debug and error messages are
503 printed in stderr, while the response body is printed in stdout. */
504 static void run(const char *uri) {
505 struct http_parser_url u;
510 struct event_base *evbase;
511 http2_session_data *session_data;
513 /* Parse the |uri| and stores its components in |u| */
514 rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
516 errx(1, "Could not parse URI %s", uri);
518 host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
519 if (!(u.field_set & (1 << UF_PORT))) {
525 ssl_ctx = create_ssl_ctx();
527 evbase = event_base_new();
529 session_data = create_http2_session_data(evbase);
530 session_data->stream_data = create_http2_stream_data(uri, &u);
532 initiate_connection(evbase, ssl_ctx, host, port, session_data);
536 event_base_loop(evbase, 0);
538 event_base_free(evbase);
539 SSL_CTX_free(ssl_ctx);
542 int main(int argc, char **argv) {
543 struct sigaction act;
546 fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
550 memset(&act, 0, sizeof(struct sigaction));
551 act.sa_handler = SIG_IGN;
552 sigaction(SIGPIPE, &act, NULL);
554 OPENSSL_config(NULL);
555 OpenSSL_add_all_algorithms();
556 SSL_load_error_strings();