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, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
322 SSL_OP_NO_COMPRESSION |
323 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
324
325 SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
326
327 return ssl_ctx;
328 }
329
330 /* Create SSL object */
create_ssl(SSL_CTX * ssl_ctx)331 static SSL *create_ssl(SSL_CTX *ssl_ctx) {
332 SSL *ssl;
333 ssl = SSL_new(ssl_ctx);
334 if (!ssl) {
335 errx(1, "Could not create SSL/TLS session object: %s",
336 ERR_error_string(ERR_get_error(), NULL));
337 }
338 return ssl;
339 }
340
initialize_nghttp2_session(http2_session_data * session_data)341 static void initialize_nghttp2_session(http2_session_data *session_data) {
342 nghttp2_session_callbacks *callbacks;
343
344 nghttp2_session_callbacks_new(&callbacks);
345
346 nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback);
347
348 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
349 on_frame_recv_callback);
350
351 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
352 callbacks, on_data_chunk_recv_callback);
353
354 nghttp2_session_callbacks_set_on_stream_close_callback(
355 callbacks, on_stream_close_callback);
356
357 nghttp2_session_callbacks_set_on_header_callback(callbacks,
358 on_header_callback);
359
360 nghttp2_session_callbacks_set_on_begin_headers_callback(
361 callbacks, on_begin_headers_callback);
362
363 nghttp2_session_client_new(&session_data->session, callbacks, session_data);
364
365 nghttp2_session_callbacks_del(callbacks);
366 }
367
send_client_connection_header(http2_session_data * session_data)368 static void send_client_connection_header(http2_session_data *session_data) {
369 nghttp2_settings_entry iv[1] = {
370 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
371 int rv;
372
373 /* client 24 bytes magic string will be sent by nghttp2 library */
374 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
375 ARRLEN(iv));
376 if (rv != 0) {
377 errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
378 }
379 }
380
381 #define MAKE_NV(NAME, VALUE, VALUELEN) \
382 { \
383 (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, \
384 VALUELEN, NGHTTP2_NV_FLAG_NONE, \
385 }
386
387 #define MAKE_NV2(NAME, VALUE) \
388 { \
389 (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, \
390 sizeof(VALUE) - 1, NGHTTP2_NV_FLAG_NONE, \
391 }
392
393 /* Send HTTP request to the remote peer */
submit_request(http2_session_data * session_data)394 static void submit_request(http2_session_data *session_data) {
395 int32_t stream_id;
396 http2_stream_data *stream_data = session_data->stream_data;
397 const char *uri = stream_data->uri;
398 const struct http_parser_url *u = stream_data->u;
399 nghttp2_nv hdrs[] = {
400 MAKE_NV2(":method", "GET"),
401 MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
402 u->field_data[UF_SCHEMA].len),
403 MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
404 MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
405 fprintf(stderr, "Request headers:\n");
406 print_headers(stderr, hdrs, ARRLEN(hdrs));
407 stream_id = nghttp2_submit_request2(session_data->session, NULL, hdrs,
408 ARRLEN(hdrs), NULL, stream_data);
409 if (stream_id < 0) {
410 errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
411 }
412
413 stream_data->stream_id = stream_id;
414 }
415
416 /* Serialize the frame and send (or buffer) the data to
417 bufferevent. */
session_send(http2_session_data * session_data)418 static int session_send(http2_session_data *session_data) {
419 int rv;
420
421 rv = nghttp2_session_send(session_data->session);
422 if (rv != 0) {
423 warnx("Fatal error: %s", nghttp2_strerror(rv));
424 return -1;
425 }
426 return 0;
427 }
428
429 /* readcb for bufferevent. Here we get the data from the input buffer
430 of bufferevent and feed them to nghttp2 library. This may invoke
431 nghttp2 callbacks. It may also queues the frame in nghttp2 session
432 context. To send them, we call session_send() in the end. */
readcb(struct bufferevent * bev,void * ptr)433 static void readcb(struct bufferevent *bev, void *ptr) {
434 http2_session_data *session_data = (http2_session_data *)ptr;
435 nghttp2_ssize readlen;
436 struct evbuffer *input = bufferevent_get_input(bev);
437 size_t datalen = evbuffer_get_length(input);
438 unsigned char *data = evbuffer_pullup(input, -1);
439
440 readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen);
441 if (readlen < 0) {
442 warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
443 delete_http2_session_data(session_data);
444 return;
445 }
446 if (evbuffer_drain(input, (size_t)readlen) != 0) {
447 warnx("Fatal error: evbuffer_drain failed");
448 delete_http2_session_data(session_data);
449 return;
450 }
451 if (session_send(session_data) != 0) {
452 delete_http2_session_data(session_data);
453 return;
454 }
455 }
456
457 /* writecb for bufferevent. To greaceful shutdown after sending or
458 receiving GOAWAY, we check the some conditions on the nghttp2
459 library and output buffer of bufferevent. If it indicates we have
460 no business to this session, tear down the connection. */
writecb(struct bufferevent * bev,void * ptr)461 static void writecb(struct bufferevent *bev, void *ptr) {
462 http2_session_data *session_data = (http2_session_data *)ptr;
463 (void)bev;
464
465 if (nghttp2_session_want_read(session_data->session) == 0 &&
466 nghttp2_session_want_write(session_data->session) == 0 &&
467 evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
468 delete_http2_session_data(session_data);
469 }
470 }
471
472 /* eventcb for bufferevent. For the purpose of simplicity and
473 readability of the example program, we omitted the certificate and
474 peer verification. After SSL/TLS handshake is over, initialize
475 nghttp2 library session, and send client connection header. Then
476 send HTTP request. */
eventcb(struct bufferevent * bev,short events,void * ptr)477 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
478 http2_session_data *session_data = (http2_session_data *)ptr;
479 if (events & BEV_EVENT_CONNECTED) {
480 int fd = bufferevent_getfd(bev);
481 int val = 1;
482 const unsigned char *alpn = NULL;
483 unsigned int alpnlen = 0;
484 SSL *ssl;
485
486 fprintf(stderr, "Connected\n");
487
488 ssl = bufferevent_openssl_get_ssl(session_data->bev);
489
490 if (alpn == NULL) {
491 SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
492 }
493
494 if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
495 fprintf(stderr, "h2 is not negotiated\n");
496 delete_http2_session_data(session_data);
497 return;
498 }
499
500 setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
501 initialize_nghttp2_session(session_data);
502 send_client_connection_header(session_data);
503 submit_request(session_data);
504 if (session_send(session_data) != 0) {
505 delete_http2_session_data(session_data);
506 }
507 return;
508 }
509 if (events & BEV_EVENT_EOF) {
510 warnx("Disconnected from the remote host");
511 } else if (events & BEV_EVENT_ERROR) {
512 warnx("Network error");
513 } else if (events & BEV_EVENT_TIMEOUT) {
514 warnx("Timeout");
515 }
516 delete_http2_session_data(session_data);
517 }
518
519 /* 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)520 static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
521 const char *host, uint16_t port,
522 http2_session_data *session_data) {
523 int rv;
524 struct bufferevent *bev;
525 SSL *ssl;
526
527 ssl = create_ssl(ssl_ctx);
528 bev = bufferevent_openssl_socket_new(
529 evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
530 BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
531 bufferevent_enable(bev, EV_READ | EV_WRITE);
532 bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
533 rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
534 AF_UNSPEC, host, port);
535
536 if (rv != 0) {
537 errx(1, "Could not connect to the remote host %s", host);
538 }
539 session_data->bev = bev;
540 }
541
542 /* Get resource denoted by the |uri|. The debug and error messages are
543 printed in stderr, while the response body is printed in stdout. */
run(const char * uri)544 static void run(const char *uri) {
545 struct http_parser_url u;
546 char *host;
547 uint16_t port;
548 int rv;
549 SSL_CTX *ssl_ctx;
550 struct event_base *evbase;
551 http2_session_data *session_data;
552
553 /* Parse the |uri| and stores its components in |u| */
554 rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
555 if (rv != 0) {
556 errx(1, "Could not parse URI %s", uri);
557 }
558 host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
559 if (!(u.field_set & (1 << UF_PORT))) {
560 port = 443;
561 } else {
562 port = u.port;
563 }
564
565 ssl_ctx = create_ssl_ctx();
566
567 evbase = event_base_new();
568
569 session_data = create_http2_session_data(evbase);
570 session_data->stream_data = create_http2_stream_data(uri, &u);
571
572 initiate_connection(evbase, ssl_ctx, host, port, session_data);
573 free(host);
574 host = NULL;
575
576 event_base_loop(evbase, 0);
577
578 event_base_free(evbase);
579 SSL_CTX_free(ssl_ctx);
580 }
581
main(int argc,char ** argv)582 int main(int argc, char **argv) {
583 struct sigaction act;
584
585 if (argc < 2) {
586 fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
587 exit(EXIT_FAILURE);
588 }
589
590 memset(&act, 0, sizeof(struct sigaction));
591 act.sa_handler = SIG_IGN;
592 sigaction(SIGPIPE, &act, NULL);
593
594 run(argv[1]);
595 return 0;
596 }
597