• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2013 Tatsuhiro Tsujikawa
5  *
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:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
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.
24  */
25 #ifdef __sgi
26 #  include <string.h>
27 #  define errx(exitcode, format, args...)                                      \
28     {                                                                          \
29       warnx(format, ##args);                                                   \
30       exit(exitcode);                                                          \
31     }
32 #  define warnx(format, args...) fprintf(stderr, format "\n", ##args)
33 char *strndup(const char *s, size_t size);
34 #endif
35 
36 #ifdef HAVE_CONFIG_H
37 #  include <config.h>
38 #endif /* HAVE_CONFIG_H */
39 
40 #include <sys/types.h>
41 #ifdef HAVE_UNISTD_H
42 #  include <unistd.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>
51 #ifndef __sgi
52 #  include <err.h>
53 #endif
54 #include <signal.h>
55 #include <string.h>
56 
57 #include <openssl/ssl.h>
58 #include <openssl/err.h>
59 #include <openssl/conf.h>
60 
61 #include <event.h>
62 #include <event2/event.h>
63 #include <event2/bufferevent_ssl.h>
64 #include <event2/dns.h>
65 
66 #define NGHTTP2_NO_SSIZE_T
67 #include <nghttp2/nghttp2.h>
68 
69 #include "url-parser/url_parser.h"
70 
71 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
72 
73 typedef struct {
74   /* The NULL-terminated URI string to retrieve. */
75   const char *uri;
76   /* Parsed result of the |uri| */
77   struct http_parser_url *u;
78   /* The authority portion of the |uri|, not NULL-terminated */
79   char *authority;
80   /* The path portion of the |uri|, including query, not
81      NULL-terminated */
82   char *path;
83   /* The length of the |authority| */
84   size_t authoritylen;
85   /* The length of the |path| */
86   size_t pathlen;
87   /* The stream ID of this stream */
88   int32_t stream_id;
89 } http2_stream_data;
90 
91 typedef struct {
92   nghttp2_session *session;
93   struct evdns_base *dnsbase;
94   struct bufferevent *bev;
95   http2_stream_data *stream_data;
96 } http2_session_data;
97 
create_http2_stream_data(const char * uri,struct http_parser_url * u)98 static http2_stream_data *create_http2_stream_data(const char *uri,
99                                                    struct http_parser_url *u) {
100   /* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
101   size_t extra = 7;
102   http2_stream_data *stream_data = malloc(sizeof(http2_stream_data));
103 
104   stream_data->uri = uri;
105   stream_data->u = u;
106   stream_data->stream_id = -1;
107 
108   stream_data->authoritylen = u->field_data[UF_HOST].len;
109   stream_data->authority = malloc(stream_data->authoritylen + extra);
110   memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off],
111          u->field_data[UF_HOST].len);
112   if (u->field_set & (1 << UF_PORT)) {
113     stream_data->authoritylen +=
114         (size_t)snprintf(stream_data->authority + u->field_data[UF_HOST].len,
115                          extra, ":%u", u->port);
116   }
117 
118   /* If we don't have path in URI, we use "/" as path. */
119   stream_data->pathlen = 1;
120   if (u->field_set & (1 << UF_PATH)) {
121     stream_data->pathlen = u->field_data[UF_PATH].len;
122   }
123   if (u->field_set & (1 << UF_QUERY)) {
124     /* +1 for '?' character */
125     stream_data->pathlen += (size_t)(u->field_data[UF_QUERY].len + 1);
126   }
127 
128   stream_data->path = malloc(stream_data->pathlen);
129   if (u->field_set & (1 << UF_PATH)) {
130     memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off],
131            u->field_data[UF_PATH].len);
132   } else {
133     stream_data->path[0] = '/';
134   }
135   if (u->field_set & (1 << UF_QUERY)) {
136     stream_data->path[stream_data->pathlen - u->field_data[UF_QUERY].len - 1] =
137         '?';
138     memcpy(stream_data->path + stream_data->pathlen -
139                u->field_data[UF_QUERY].len,
140            &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
141   }
142 
143   return stream_data;
144 }
145 
delete_http2_stream_data(http2_stream_data * stream_data)146 static void delete_http2_stream_data(http2_stream_data *stream_data) {
147   free(stream_data->path);
148   free(stream_data->authority);
149   free(stream_data);
150 }
151 
152 /* Initializes |session_data| */
153 static http2_session_data *
create_http2_session_data(struct event_base * evbase)154 create_http2_session_data(struct event_base *evbase) {
155   http2_session_data *session_data = malloc(sizeof(http2_session_data));
156 
157   memset(session_data, 0, sizeof(http2_session_data));
158   session_data->dnsbase = evdns_base_new(evbase, 1);
159   return session_data;
160 }
161 
delete_http2_session_data(http2_session_data * session_data)162 static void delete_http2_session_data(http2_session_data *session_data) {
163   SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
164 
165   if (ssl) {
166     SSL_shutdown(ssl);
167   }
168   bufferevent_free(session_data->bev);
169   session_data->bev = NULL;
170   evdns_base_free(session_data->dnsbase, 1);
171   session_data->dnsbase = NULL;
172   nghttp2_session_del(session_data->session);
173   session_data->session = NULL;
174   if (session_data->stream_data) {
175     delete_http2_stream_data(session_data->stream_data);
176     session_data->stream_data = NULL;
177   }
178   free(session_data);
179 }
180 
print_header(FILE * f,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen)181 static void print_header(FILE *f, const uint8_t *name, size_t namelen,
182                          const uint8_t *value, size_t valuelen) {
183   fwrite(name, 1, namelen, f);
184   fprintf(f, ": ");
185   fwrite(value, 1, valuelen, f);
186   fprintf(f, "\n");
187 }
188 
189 /* Print HTTP headers to |f|. Please note that this function does not
190    take into account that header name and value are sequence of
191    octets, therefore they may contain non-printable characters. */
print_headers(FILE * f,nghttp2_nv * nva,size_t nvlen)192 static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) {
193   size_t i;
194   for (i = 0; i < nvlen; ++i) {
195     print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
196   }
197   fprintf(f, "\n");
198 }
199 
200 /* nghttp2_send_callback2. Here we transmit the |data|, |length|
201    bytes, to the network. Because we are using libevent bufferevent,
202    we just write those bytes into bufferevent buffer. */
send_callback(nghttp2_session * session,const uint8_t * data,size_t length,int flags,void * user_data)203 static nghttp2_ssize send_callback(nghttp2_session *session,
204                                    const uint8_t *data, size_t length,
205                                    int flags, void *user_data) {
206   http2_session_data *session_data = (http2_session_data *)user_data;
207   struct bufferevent *bev = session_data->bev;
208   (void)session;
209   (void)flags;
210 
211   bufferevent_write(bev, data, length);
212   return (nghttp2_ssize)length;
213 }
214 
215 /* nghttp2_on_header_callback: Called when nghttp2 library emits
216    single header name/value pair. */
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)217 static int on_header_callback(nghttp2_session *session,
218                               const nghttp2_frame *frame, const uint8_t *name,
219                               size_t namelen, const uint8_t *value,
220                               size_t valuelen, uint8_t flags, void *user_data) {
221   http2_session_data *session_data = (http2_session_data *)user_data;
222   (void)session;
223   (void)flags;
224 
225   switch (frame->hd.type) {
226   case NGHTTP2_HEADERS:
227     if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
228         session_data->stream_data->stream_id == frame->hd.stream_id) {
229       /* Print response headers for the initiated request. */
230       print_header(stderr, name, namelen, value, valuelen);
231       break;
232     }
233   }
234   return 0;
235 }
236 
237 /* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets
238    started to receive header block. */
on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)239 static int on_begin_headers_callback(nghttp2_session *session,
240                                      const nghttp2_frame *frame,
241                                      void *user_data) {
242   http2_session_data *session_data = (http2_session_data *)user_data;
243   (void)session;
244 
245   switch (frame->hd.type) {
246   case NGHTTP2_HEADERS:
247     if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
248         session_data->stream_data->stream_id == frame->hd.stream_id) {
249       fprintf(stderr, "Response headers for stream ID=%d:\n",
250               frame->hd.stream_id);
251     }
252     break;
253   }
254   return 0;
255 }
256 
257 /* nghttp2_on_frame_recv_callback: Called when nghttp2 library
258    received a complete frame from the remote peer. */
on_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)259 static int on_frame_recv_callback(nghttp2_session *session,
260                                   const nghttp2_frame *frame, void *user_data) {
261   http2_session_data *session_data = (http2_session_data *)user_data;
262   (void)session;
263 
264   switch (frame->hd.type) {
265   case NGHTTP2_HEADERS:
266     if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
267         session_data->stream_data->stream_id == frame->hd.stream_id) {
268       fprintf(stderr, "All headers received\n");
269     }
270     break;
271   }
272   return 0;
273 }
274 
275 /* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is
276    received from the remote peer. In this implementation, if the frame
277    is meant to the stream we initiated, print the received data in
278    stdout, so that the user can redirect its output to the file
279    easily. */
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)280 static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
281                                        int32_t stream_id, const uint8_t *data,
282                                        size_t len, void *user_data) {
283   http2_session_data *session_data = (http2_session_data *)user_data;
284   (void)session;
285   (void)flags;
286 
287   if (session_data->stream_data->stream_id == stream_id) {
288     fwrite(data, 1, len, stdout);
289   }
290   return 0;
291 }
292 
293 /* nghttp2_on_stream_close_callback: Called when a stream is about to
294    closed. This example program only deals with 1 HTTP request (1
295    stream), if it is closed, we send GOAWAY and tear down the
296    session */
on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)297 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
298                                     uint32_t error_code, void *user_data) {
299   http2_session_data *session_data = (http2_session_data *)user_data;
300   int rv;
301 
302   if (session_data->stream_data->stream_id == stream_id) {
303     fprintf(stderr, "Stream %d closed with error_code=%u\n", stream_id,
304             error_code);
305     rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
306     if (rv != 0) {
307       return NGHTTP2_ERR_CALLBACK_FAILURE;
308     }
309   }
310   return 0;
311 }
312 
313 /* Create SSL_CTX. */
create_ssl_ctx(void)314 static SSL_CTX *create_ssl_ctx(void) {
315   SSL_CTX *ssl_ctx;
316   ssl_ctx = SSL_CTX_new(TLS_client_method());
317   if (!ssl_ctx) {
318     errx(1, "Could not create SSL/TLS context: %s",
319          ERR_error_string(ERR_get_error(), NULL));
320   }
321   SSL_CTX_set_options(ssl_ctx,
322                       SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
323                           SSL_OP_NO_COMPRESSION |
324                           SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
325 
326   SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
327 
328   return ssl_ctx;
329 }
330 
331 /* Create SSL object */
create_ssl(SSL_CTX * ssl_ctx)332 static SSL *create_ssl(SSL_CTX *ssl_ctx) {
333   SSL *ssl;
334   ssl = SSL_new(ssl_ctx);
335   if (!ssl) {
336     errx(1, "Could not create SSL/TLS session object: %s",
337          ERR_error_string(ERR_get_error(), NULL));
338   }
339   return ssl;
340 }
341 
initialize_nghttp2_session(http2_session_data * session_data)342 static void initialize_nghttp2_session(http2_session_data *session_data) {
343   nghttp2_session_callbacks *callbacks;
344 
345   nghttp2_session_callbacks_new(&callbacks);
346 
347   nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback);
348 
349   nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
350                                                        on_frame_recv_callback);
351 
352   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
353       callbacks, on_data_chunk_recv_callback);
354 
355   nghttp2_session_callbacks_set_on_stream_close_callback(
356       callbacks, on_stream_close_callback);
357 
358   nghttp2_session_callbacks_set_on_header_callback(callbacks,
359                                                    on_header_callback);
360 
361   nghttp2_session_callbacks_set_on_begin_headers_callback(
362       callbacks, on_begin_headers_callback);
363 
364   nghttp2_session_client_new(&session_data->session, callbacks, session_data);
365 
366   nghttp2_session_callbacks_del(callbacks);
367 }
368 
send_client_connection_header(http2_session_data * session_data)369 static void send_client_connection_header(http2_session_data *session_data) {
370   nghttp2_settings_entry iv[1] = {
371       {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
372   int rv;
373 
374   /* client 24 bytes magic string will be sent by nghttp2 library */
375   rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
376                                ARRLEN(iv));
377   if (rv != 0) {
378     errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
379   }
380 }
381 
382 #define MAKE_NV(NAME, VALUE, VALUELEN)                                         \
383   {                                                                            \
384     (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN,             \
385         NGHTTP2_NV_FLAG_NONE                                                   \
386   }
387 
388 #define MAKE_NV2(NAME, VALUE)                                                  \
389   {                                                                            \
390     (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1,    \
391         NGHTTP2_NV_FLAG_NONE                                                   \
392   }
393 
394 /* Send HTTP request to the remote peer */
submit_request(http2_session_data * session_data)395 static void submit_request(http2_session_data *session_data) {
396   int32_t stream_id;
397   http2_stream_data *stream_data = session_data->stream_data;
398   const char *uri = stream_data->uri;
399   const struct http_parser_url *u = stream_data->u;
400   nghttp2_nv hdrs[] = {
401       MAKE_NV2(":method", "GET"),
402       MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
403               u->field_data[UF_SCHEMA].len),
404       MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
405       MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
406   fprintf(stderr, "Request headers:\n");
407   print_headers(stderr, hdrs, ARRLEN(hdrs));
408   stream_id = nghttp2_submit_request2(session_data->session, NULL, hdrs,
409                                       ARRLEN(hdrs), NULL, stream_data);
410   if (stream_id < 0) {
411     errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
412   }
413 
414   stream_data->stream_id = stream_id;
415 }
416 
417 /* Serialize the frame and send (or buffer) the data to
418    bufferevent. */
session_send(http2_session_data * session_data)419 static int session_send(http2_session_data *session_data) {
420   int rv;
421 
422   rv = nghttp2_session_send(session_data->session);
423   if (rv != 0) {
424     warnx("Fatal error: %s", nghttp2_strerror(rv));
425     return -1;
426   }
427   return 0;
428 }
429 
430 /* readcb for bufferevent. Here we get the data from the input buffer
431    of bufferevent and feed them to nghttp2 library. This may invoke
432    nghttp2 callbacks. It may also queues the frame in nghttp2 session
433    context. To send them, we call session_send() in the end. */
readcb(struct bufferevent * bev,void * ptr)434 static void readcb(struct bufferevent *bev, void *ptr) {
435   http2_session_data *session_data = (http2_session_data *)ptr;
436   nghttp2_ssize readlen;
437   struct evbuffer *input = bufferevent_get_input(bev);
438   size_t datalen = evbuffer_get_length(input);
439   unsigned char *data = evbuffer_pullup(input, -1);
440 
441   readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen);
442   if (readlen < 0) {
443     warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
444     delete_http2_session_data(session_data);
445     return;
446   }
447   if (evbuffer_drain(input, (size_t)readlen) != 0) {
448     warnx("Fatal error: evbuffer_drain failed");
449     delete_http2_session_data(session_data);
450     return;
451   }
452   if (session_send(session_data) != 0) {
453     delete_http2_session_data(session_data);
454     return;
455   }
456 }
457 
458 /* writecb for bufferevent. To greaceful shutdown after sending or
459    receiving GOAWAY, we check the some conditions on the nghttp2
460    library and output buffer of bufferevent. If it indicates we have
461    no business to this session, tear down the connection. */
writecb(struct bufferevent * bev,void * ptr)462 static void writecb(struct bufferevent *bev, void *ptr) {
463   http2_session_data *session_data = (http2_session_data *)ptr;
464   (void)bev;
465 
466   if (nghttp2_session_want_read(session_data->session) == 0 &&
467       nghttp2_session_want_write(session_data->session) == 0 &&
468       evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
469     delete_http2_session_data(session_data);
470   }
471 }
472 
473 /* eventcb for bufferevent. For the purpose of simplicity and
474    readability of the example program, we omitted the certificate and
475    peer verification. After SSL/TLS handshake is over, initialize
476    nghttp2 library session, and send client connection header. Then
477    send HTTP request. */
eventcb(struct bufferevent * bev,short events,void * ptr)478 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
479   http2_session_data *session_data = (http2_session_data *)ptr;
480   if (events & BEV_EVENT_CONNECTED) {
481     int fd = bufferevent_getfd(bev);
482     int val = 1;
483     const unsigned char *alpn = NULL;
484     unsigned int alpnlen = 0;
485     SSL *ssl;
486 
487     fprintf(stderr, "Connected\n");
488 
489     ssl = bufferevent_openssl_get_ssl(session_data->bev);
490 
491     if (alpn == NULL) {
492       SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
493     }
494 
495     if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
496       fprintf(stderr, "h2 is not negotiated\n");
497       delete_http2_session_data(session_data);
498       return;
499     }
500 
501     setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
502     initialize_nghttp2_session(session_data);
503     send_client_connection_header(session_data);
504     submit_request(session_data);
505     if (session_send(session_data) != 0) {
506       delete_http2_session_data(session_data);
507     }
508     return;
509   }
510   if (events & BEV_EVENT_EOF) {
511     warnx("Disconnected from the remote host");
512   } else if (events & BEV_EVENT_ERROR) {
513     warnx("Network error");
514   } else if (events & BEV_EVENT_TIMEOUT) {
515     warnx("Timeout");
516   }
517   delete_http2_session_data(session_data);
518 }
519 
520 /* Start connecting to the remote peer |host:port| */
initiate_connection(struct event_base * evbase,SSL_CTX * ssl_ctx,const char * host,uint16_t port,http2_session_data * session_data)521 static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
522                                 const char *host, uint16_t port,
523                                 http2_session_data *session_data) {
524   int rv;
525   struct bufferevent *bev;
526   SSL *ssl;
527 
528   ssl = create_ssl(ssl_ctx);
529   bev = bufferevent_openssl_socket_new(
530       evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
531       BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
532   bufferevent_enable(bev, EV_READ | EV_WRITE);
533   bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
534   rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
535                                            AF_UNSPEC, host, port);
536 
537   if (rv != 0) {
538     errx(1, "Could not connect to the remote host %s", host);
539   }
540   session_data->bev = bev;
541 }
542 
543 /* Get resource denoted by the |uri|. The debug and error messages are
544    printed in stderr, while the response body is printed in stdout. */
run(const char * uri)545 static void run(const char *uri) {
546   struct http_parser_url u;
547   char *host;
548   uint16_t port;
549   int rv;
550   SSL_CTX *ssl_ctx;
551   struct event_base *evbase;
552   http2_session_data *session_data;
553 
554   /* Parse the |uri| and stores its components in |u| */
555   rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
556   if (rv != 0) {
557     errx(1, "Could not parse URI %s", uri);
558   }
559   host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
560   if (!(u.field_set & (1 << UF_PORT))) {
561     port = 443;
562   } else {
563     port = u.port;
564   }
565 
566   ssl_ctx = create_ssl_ctx();
567 
568   evbase = event_base_new();
569 
570   session_data = create_http2_session_data(evbase);
571   session_data->stream_data = create_http2_stream_data(uri, &u);
572 
573   initiate_connection(evbase, ssl_ctx, host, port, session_data);
574   free(host);
575   host = NULL;
576 
577   event_base_loop(evbase, 0);
578 
579   event_base_free(evbase);
580   SSL_CTX_free(ssl_ctx);
581 }
582 
main(int argc,char ** argv)583 int main(int argc, char **argv) {
584   struct sigaction act;
585 
586   if (argc < 2) {
587     fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
588     exit(EXIT_FAILURE);
589   }
590 
591   memset(&act, 0, sizeof(struct sigaction));
592   act.sa_handler = SIG_IGN;
593   sigaction(SIGPIPE, &act, NULL);
594 
595   run(argv[1]);
596   return 0;
597 }
598