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