2013-12-22 11:26:32 +01:00
|
|
|
/*
|
2014-03-30 12:09:21 +02:00
|
|
|
* nghttp2 - HTTP/2 C Library
|
2013-12-22 11:26:32 +01:00
|
|
|
*
|
|
|
|
* Copyright (c) 2013 Tatsuhiro Tsujikawa
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be
|
|
|
|
* included in all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
*/
|
2014-11-26 16:12:42 +01:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2014-10-19 15:19:33 +02:00
|
|
|
#include <config.h>
|
2015-05-13 15:30:35 +02:00
|
|
|
#endif /* HAVE_CONFIG_H */
|
2014-11-26 16:12:42 +01:00
|
|
|
|
|
|
|
#include <sys/types.h>
|
2015-05-13 15:30:35 +02:00
|
|
|
#ifdef HAVE_UNISTD_H
|
2013-12-22 11:26:32 +01:00
|
|
|
#include <unistd.h>
|
2015-05-13 15:30:35 +02:00
|
|
|
#endif /* HAVE_UNISTD_H */
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
2014-01-12 13:43:55 +01:00
|
|
|
#include <sys/socket.h>
|
2015-05-13 15:30:35 +02:00
|
|
|
#endif /* HAVE_SYS_SOCKET_H */
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
2014-01-12 13:43:55 +01:00
|
|
|
#include <netinet/in.h>
|
2015-05-13 15:30:35 +02:00
|
|
|
#endif /* HAVE_NETINET_IN_H */
|
2013-12-23 16:01:06 +01:00
|
|
|
#include <netinet/tcp.h>
|
2013-12-22 11:26:32 +01:00
|
|
|
#include <err.h>
|
|
|
|
#include <signal.h>
|
|
|
|
|
|
|
|
#include <openssl/ssl.h>
|
|
|
|
#include <openssl/err.h>
|
2014-08-02 03:11:45 +02:00
|
|
|
#include <openssl/conf.h>
|
2013-12-22 11:26:32 +01:00
|
|
|
|
|
|
|
#include <event.h>
|
|
|
|
#include <event2/event.h>
|
|
|
|
#include <event2/bufferevent_ssl.h>
|
|
|
|
#include <event2/dns.h>
|
|
|
|
|
|
|
|
#include <nghttp2/nghttp2.h>
|
|
|
|
|
|
|
|
#include "http-parser/http_parser.h"
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
|
2013-12-22 11:26:32 +01:00
|
|
|
|
|
|
|
typedef struct {
|
2015-05-12 16:24:18 +02:00
|
|
|
/* The NULL-terminated URI string to retrieve. */
|
2013-12-22 11:26:32 +01:00
|
|
|
const char *uri;
|
2013-12-24 11:55:26 +01:00
|
|
|
/* Parsed result of the |uri| */
|
2013-12-22 11:26:32 +01:00
|
|
|
struct http_parser_url *u;
|
2015-05-12 16:24:18 +02:00
|
|
|
/* The authority portion of the |uri|, not NULL-terminated */
|
2013-12-22 11:26:32 +01:00
|
|
|
char *authority;
|
2013-12-24 13:30:49 +01:00
|
|
|
/* The path portion of the |uri|, including query, not
|
|
|
|
NULL-terminated */
|
2013-12-22 11:26:32 +01:00
|
|
|
char *path;
|
2013-12-24 11:55:26 +01:00
|
|
|
/* The length of the |authority| */
|
2013-12-22 11:26:32 +01:00
|
|
|
size_t authoritylen;
|
2013-12-24 11:55:26 +01:00
|
|
|
/* The length of the |path| */
|
2013-12-22 11:26:32 +01:00
|
|
|
size_t pathlen;
|
2013-12-24 11:55:26 +01:00
|
|
|
/* The stream ID of this stream */
|
2013-12-22 11:26:32 +01:00
|
|
|
int32_t stream_id;
|
2013-12-24 11:55:26 +01:00
|
|
|
} http2_stream_data;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
nghttp2_session *session;
|
|
|
|
struct evdns_base *dnsbase;
|
|
|
|
struct bufferevent *bev;
|
|
|
|
http2_stream_data *stream_data;
|
2013-12-22 11:26:32 +01:00
|
|
|
} http2_session_data;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
static http2_stream_data *create_http2_stream_data(const char *uri,
|
|
|
|
struct http_parser_url *u) {
|
2014-01-17 17:13:04 +01:00
|
|
|
/* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
|
|
|
|
size_t extra = 7;
|
2013-12-24 11:55:26 +01:00
|
|
|
http2_stream_data *stream_data = malloc(sizeof(http2_stream_data));
|
|
|
|
|
|
|
|
stream_data->uri = uri;
|
|
|
|
stream_data->u = u;
|
|
|
|
stream_data->stream_id = -1;
|
|
|
|
|
|
|
|
stream_data->authoritylen = u->field_data[UF_HOST].len;
|
2014-01-17 17:13:04 +01:00
|
|
|
stream_data->authority = malloc(stream_data->authoritylen + extra);
|
2014-11-27 15:39:04 +01:00
|
|
|
memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off],
|
|
|
|
u->field_data[UF_HOST].len);
|
|
|
|
if (u->field_set & (1 << UF_PORT)) {
|
2013-12-24 11:55:26 +01:00
|
|
|
stream_data->authoritylen +=
|
2014-11-27 15:39:04 +01:00
|
|
|
snprintf(stream_data->authority + u->field_data[UF_HOST].len, extra,
|
|
|
|
":%u", u->port);
|
2013-12-24 11:55:26 +01:00
|
|
|
}
|
|
|
|
|
2015-05-31 17:22:35 +02:00
|
|
|
/* If we don't have path in URI, we use "/" as path. */
|
|
|
|
stream_data->pathlen = 1;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (u->field_set & (1 << UF_PATH)) {
|
2013-12-24 11:55:26 +01:00
|
|
|
stream_data->pathlen = u->field_data[UF_PATH].len;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (u->field_set & (1 << UF_QUERY)) {
|
2013-12-24 11:55:26 +01:00
|
|
|
/* +1 for '?' character */
|
|
|
|
stream_data->pathlen += u->field_data[UF_QUERY].len + 1;
|
|
|
|
}
|
2015-05-31 17:22:35 +02:00
|
|
|
|
|
|
|
stream_data->path = malloc(stream_data->pathlen);
|
|
|
|
if (u->field_set & (1 << UF_PATH)) {
|
|
|
|
memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off],
|
|
|
|
u->field_data[UF_PATH].len);
|
2013-12-24 13:30:49 +01:00
|
|
|
} else {
|
2015-05-31 17:22:35 +02:00
|
|
|
stream_data->path[0] = '/';
|
2013-12-24 13:30:49 +01:00
|
|
|
}
|
2015-05-31 17:22:35 +02:00
|
|
|
if (u->field_set & (1 << UF_QUERY)) {
|
|
|
|
stream_data->path[stream_data->pathlen - u->field_data[UF_QUERY].len - 1] =
|
|
|
|
'?';
|
|
|
|
memcpy(stream_data->path + stream_data->pathlen -
|
|
|
|
u->field_data[UF_QUERY].len,
|
|
|
|
&uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
|
|
|
|
}
|
|
|
|
|
2013-12-24 11:55:26 +01:00
|
|
|
return stream_data;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
static void delete_http2_stream_data(http2_stream_data *stream_data) {
|
2013-12-24 11:55:26 +01:00
|
|
|
free(stream_data->path);
|
|
|
|
free(stream_data->authority);
|
|
|
|
free(stream_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initializes |session_data| */
|
2014-11-27 15:39:04 +01:00
|
|
|
static http2_session_data *
|
|
|
|
create_http2_session_data(struct event_base *evbase) {
|
2013-12-24 11:55:26 +01:00
|
|
|
http2_session_data *session_data = malloc(sizeof(http2_session_data));
|
|
|
|
|
|
|
|
memset(session_data, 0, sizeof(http2_session_data));
|
|
|
|
session_data->dnsbase = evdns_base_new(evbase, 1);
|
|
|
|
return session_data;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
static void delete_http2_session_data(http2_session_data *session_data) {
|
2013-12-24 11:55:26 +01:00
|
|
|
SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl) {
|
2013-12-24 11:55:26 +01:00
|
|
|
SSL_shutdown(ssl);
|
|
|
|
}
|
2013-12-22 11:26:32 +01:00
|
|
|
bufferevent_free(session_data->bev);
|
|
|
|
session_data->bev = NULL;
|
|
|
|
evdns_base_free(session_data->dnsbase, 1);
|
|
|
|
session_data->dnsbase = NULL;
|
|
|
|
nghttp2_session_del(session_data->session);
|
|
|
|
session_data->session = NULL;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (session_data->stream_data) {
|
2013-12-24 11:55:26 +01:00
|
|
|
delete_http2_stream_data(session_data->stream_data);
|
|
|
|
session_data->stream_data = NULL;
|
|
|
|
}
|
2013-12-24 16:29:36 +01:00
|
|
|
free(session_data);
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
static void print_header(FILE *f, const uint8_t *name, size_t namelen,
|
|
|
|
const uint8_t *value, size_t valuelen) {
|
2014-01-17 16:06:24 +01:00
|
|
|
fwrite(name, namelen, 1, f);
|
|
|
|
fprintf(f, ": ");
|
|
|
|
fwrite(value, valuelen, 1, f);
|
|
|
|
fprintf(f, "\n");
|
|
|
|
}
|
|
|
|
|
2013-12-22 11:26:32 +01:00
|
|
|
/* Print HTTP headers to |f|. Please note that this function does not
|
|
|
|
take into account that header name and value are sequence of
|
|
|
|
octets, therefore they may contain non-printable characters. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) {
|
2013-12-22 11:26:32 +01:00
|
|
|
size_t i;
|
2014-11-27 15:39:04 +01:00
|
|
|
for (i = 0; i < nvlen; ++i) {
|
|
|
|
print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
2014-01-17 16:06:24 +01:00
|
|
|
fprintf(f, "\n");
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* nghttp2_send_callback. Here we transmit the |data|, |length| bytes,
|
|
|
|
to the network. Because we are using libevent bufferevent, we just
|
|
|
|
write those bytes into bufferevent buffer. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
|
|
|
|
size_t length, int flags _U_, void *user_data) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)user_data;
|
2013-12-22 11:26:32 +01:00
|
|
|
struct bufferevent *bev = session_data->bev;
|
|
|
|
bufferevent_write(bev, data, length);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2014-01-17 16:06:24 +01:00
|
|
|
/* nghttp2_on_header_callback: Called when nghttp2 library emits
|
|
|
|
single header name/value pair. */
|
2014-10-19 15:19:33 +02:00
|
|
|
static int on_header_callback(nghttp2_session *session _U_,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, const uint8_t *name,
|
|
|
|
size_t namelen, const uint8_t *value,
|
|
|
|
size_t valuelen, uint8_t flags _U_,
|
|
|
|
void *user_data) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)user_data;
|
|
|
|
switch (frame->hd.type) {
|
2014-01-17 16:06:24 +01:00
|
|
|
case NGHTTP2_HEADERS:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
|
|
|
|
session_data->stream_data->stream_id == frame->hd.stream_id) {
|
2014-01-17 16:06:24 +01:00
|
|
|
/* Print response headers for the initiated request. */
|
|
|
|
print_header(stderr, name, namelen, value, valuelen);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-01-29 13:23:13 +01:00
|
|
|
/* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets
|
|
|
|
started to receive header block. */
|
2014-10-19 15:19:33 +02:00
|
|
|
static int on_begin_headers_callback(nghttp2_session *session _U_,
|
2014-01-29 13:23:13 +01:00
|
|
|
const nghttp2_frame *frame,
|
2014-11-27 15:39:04 +01:00
|
|
|
void *user_data) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)user_data;
|
|
|
|
switch (frame->hd.type) {
|
2014-01-17 16:06:24 +01:00
|
|
|
case NGHTTP2_HEADERS:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
|
|
|
|
session_data->stream_data->stream_id == frame->hd.stream_id) {
|
2014-01-29 13:23:13 +01:00
|
|
|
fprintf(stderr, "Response headers for stream ID=%d:\n",
|
|
|
|
frame->hd.stream_id);
|
2014-01-17 16:06:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-12-22 11:26:32 +01:00
|
|
|
/* nghttp2_on_frame_recv_callback: Called when nghttp2 library
|
2014-01-29 13:23:13 +01:00
|
|
|
received a complete frame from the remote peer. */
|
2014-10-19 15:19:33 +02:00
|
|
|
static int on_frame_recv_callback(nghttp2_session *session _U_,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, void *user_data) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)user_data;
|
|
|
|
switch (frame->hd.type) {
|
2013-12-22 11:26:32 +01:00
|
|
|
case NGHTTP2_HEADERS:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
|
|
|
|
session_data->stream_data->stream_id == frame->hd.stream_id) {
|
2014-01-29 13:23:13 +01:00
|
|
|
fprintf(stderr, "All headers received\n");
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is
|
|
|
|
received from the remote peer. In this implementation, if the frame
|
|
|
|
is meant to the stream we initiated, print the received data in
|
|
|
|
stdout, so that the user can redirect its output to the file
|
|
|
|
easily. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
|
|
|
|
uint8_t flags _U_, int32_t stream_id,
|
2013-12-22 11:26:32 +01:00
|
|
|
const uint8_t *data, size_t len,
|
2014-11-27 15:39:04 +01:00
|
|
|
void *user_data) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)user_data;
|
|
|
|
if (session_data->stream_data->stream_id == stream_id) {
|
2013-12-22 11:26:32 +01:00
|
|
|
fwrite(data, len, 1, stdout);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* nghttp2_on_stream_close_callback: Called when a stream is about to
|
|
|
|
closed. This example program only deals with 1 HTTP request (1
|
|
|
|
stream), if it is closed, we send GOAWAY and tear down the
|
|
|
|
session */
|
2014-11-27 15:39:04 +01:00
|
|
|
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
2015-04-23 17:16:14 +02:00
|
|
|
uint32_t error_code, void *user_data) {
|
2014-11-27 15:39:04 +01:00
|
|
|
http2_session_data *session_data = (http2_session_data *)user_data;
|
2013-12-24 13:30:49 +01:00
|
|
|
int rv;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (session_data->stream_data->stream_id == stream_id) {
|
|
|
|
fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
|
|
|
|
error_code);
|
2013-12-25 16:23:07 +01:00
|
|
|
rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-12-24 13:30:49 +01:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* NPN TLS extension client callback. We check that server advertised
|
2014-03-30 12:09:21 +02:00
|
|
|
the HTTP/2 protocol the nghttp2 library supports. If not, exit
|
2013-12-22 11:26:32 +01:00
|
|
|
the program. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
|
|
|
|
unsigned char *outlen, const unsigned char *in,
|
|
|
|
unsigned int inlen, void *arg _U_) {
|
|
|
|
if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
|
2013-12-22 11:26:32 +01:00
|
|
|
errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
|
|
|
|
}
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create SSL_CTX. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static SSL_CTX *create_ssl_ctx(void) {
|
2013-12-22 11:26:32 +01:00
|
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl_ctx) {
|
2013-12-22 11:26:32 +01:00
|
|
|
errx(1, "Could not create SSL/TLS context: %s",
|
|
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
}
|
|
|
|
SSL_CTX_set_options(ssl_ctx,
|
2014-11-30 13:09:23 +01:00
|
|
|
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
|
|
|
|
SSL_OP_NO_COMPRESSION |
|
2014-11-27 15:39:04 +01:00
|
|
|
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
2013-12-22 11:26:32 +01:00
|
|
|
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
|
|
|
|
return ssl_ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create SSL object */
|
2014-11-27 15:39:04 +01:00
|
|
|
static SSL *create_ssl(SSL_CTX *ssl_ctx) {
|
2013-12-22 11:26:32 +01:00
|
|
|
SSL *ssl;
|
|
|
|
ssl = SSL_new(ssl_ctx);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl) {
|
2013-12-22 11:26:32 +01:00
|
|
|
errx(1, "Could not create SSL/TLS session object: %s",
|
|
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
}
|
|
|
|
return ssl;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
static void initialize_nghttp2_session(http2_session_data *session_data) {
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks *callbacks;
|
2013-12-22 11:26:32 +01:00
|
|
|
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks_new(&callbacks);
|
2014-05-01 03:46:08 +02:00
|
|
|
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
|
|
|
|
on_frame_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
|
|
|
|
callbacks, on_data_chunk_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_stream_close_callback(
|
|
|
|
callbacks, on_stream_close_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
|
|
|
on_header_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_begin_headers_callback(
|
|
|
|
callbacks, on_begin_headers_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
|
|
|
nghttp2_session_client_new(&session_data->session, callbacks, session_data);
|
|
|
|
|
|
|
|
nghttp2_session_callbacks_del(callbacks);
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
static void send_client_connection_header(http2_session_data *session_data) {
|
2013-12-22 11:26:32 +01:00
|
|
|
nghttp2_settings_entry iv[1] = {
|
2014-11-27 15:39:04 +01:00
|
|
|
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
|
2013-12-24 13:30:49 +01:00
|
|
|
int rv;
|
|
|
|
|
2015-04-05 15:35:40 +02:00
|
|
|
/* client 24 bytes magic string will be sent by nghttp2 library */
|
2014-11-27 15:39:04 +01:00
|
|
|
rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
|
|
|
|
ARRLEN(iv));
|
|
|
|
if (rv != 0) {
|
2013-12-24 13:30:49 +01:00
|
|
|
errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
|
|
|
|
}
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
#define MAKE_NV(NAME, VALUE, VALUELEN) \
|
|
|
|
{ \
|
|
|
|
(uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \
|
|
|
|
NGHTTP2_NV_FLAG_NONE \
|
|
|
|
}
|
2013-12-22 11:26:32 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
#define MAKE_NV2(NAME, VALUE) \
|
|
|
|
{ \
|
|
|
|
(uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
|
|
|
|
NGHTTP2_NV_FLAG_NONE \
|
|
|
|
}
|
2013-12-22 11:26:32 +01:00
|
|
|
|
|
|
|
/* Send HTTP request to the remote peer */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void submit_request(http2_session_data *session_data) {
|
2014-05-07 16:24:07 +02:00
|
|
|
int32_t stream_id;
|
2013-12-24 11:55:26 +01:00
|
|
|
http2_stream_data *stream_data = session_data->stream_data;
|
|
|
|
const char *uri = stream_data->uri;
|
|
|
|
const struct http_parser_url *u = stream_data->u;
|
2013-12-22 11:26:32 +01:00
|
|
|
nghttp2_nv hdrs[] = {
|
2014-11-27 15:39:04 +01:00
|
|
|
MAKE_NV2(":method", "GET"),
|
|
|
|
MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
|
|
|
|
u->field_data[UF_SCHEMA].len),
|
|
|
|
MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
|
|
|
|
MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
|
2013-12-22 11:26:32 +01:00
|
|
|
fprintf(stderr, "Request headers:\n");
|
|
|
|
print_headers(stderr, hdrs, ARRLEN(hdrs));
|
2014-11-27 15:39:04 +01:00
|
|
|
stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
|
|
|
|
ARRLEN(hdrs), NULL, stream_data);
|
|
|
|
if (stream_id < 0) {
|
2014-05-07 16:24:07 +02:00
|
|
|
errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
|
2013-12-24 13:30:49 +01:00
|
|
|
}
|
2014-05-07 16:24:07 +02:00
|
|
|
|
|
|
|
stream_data->stream_id = stream_id;
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Serialize the frame and send (or buffer) the data to
|
|
|
|
bufferevent. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static int session_send(http2_session_data *session_data) {
|
2013-12-24 13:30:49 +01:00
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = nghttp2_session_send(session_data->session);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-12-24 13:30:49 +01:00
|
|
|
warnx("Fatal error: %s", nghttp2_strerror(rv));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* readcb for bufferevent. Here we get the data from the input buffer
|
|
|
|
of bufferevent and feed them to nghttp2 library. This may invoke
|
|
|
|
nghttp2 callbacks. It may also queues the frame in nghttp2 session
|
|
|
|
context. To send them, we call session_send() in the end. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void readcb(struct bufferevent *bev, void *ptr) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)ptr;
|
2014-06-11 16:37:16 +02:00
|
|
|
ssize_t readlen;
|
2013-12-22 11:26:32 +01:00
|
|
|
struct evbuffer *input = bufferevent_get_input(bev);
|
|
|
|
size_t datalen = evbuffer_get_length(input);
|
|
|
|
unsigned char *data = evbuffer_pullup(input, -1);
|
2014-06-11 16:37:16 +02:00
|
|
|
|
|
|
|
readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (readlen < 0) {
|
2014-06-11 16:37:16 +02:00
|
|
|
warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
|
2013-12-24 11:55:26 +01:00
|
|
|
delete_http2_session_data(session_data);
|
2013-12-24 13:30:49 +01:00
|
|
|
return;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (evbuffer_drain(input, readlen) != 0) {
|
2014-06-18 06:06:05 +02:00
|
|
|
warnx("Fatal error: evbuffer_drain failed");
|
|
|
|
delete_http2_session_data(session_data);
|
|
|
|
return;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (session_send(session_data) != 0) {
|
2013-12-24 13:30:49 +01:00
|
|
|
delete_http2_session_data(session_data);
|
|
|
|
return;
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* writecb for bufferevent. To greaceful shutdown after sending or
|
|
|
|
receiving GOAWAY, we check the some conditions on the nghttp2
|
|
|
|
library and output buffer of bufferevent. If it indicates we have
|
|
|
|
no business to this session, tear down the connection. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void writecb(struct bufferevent *bev _U_, void *ptr) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)ptr;
|
|
|
|
if (nghttp2_session_want_read(session_data->session) == 0 &&
|
|
|
|
nghttp2_session_want_write(session_data->session) == 0 &&
|
|
|
|
evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
|
2013-12-24 11:55:26 +01:00
|
|
|
delete_http2_session_data(session_data);
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* eventcb for bufferevent. For the purpose of simplicity and
|
|
|
|
readability of the example program, we omitted the certificate and
|
|
|
|
peer verification. After SSL/TLS handshake is over, initialize
|
|
|
|
nghttp2 library session, and send client connection header. Then
|
|
|
|
send HTTP request. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void eventcb(struct bufferevent *bev, short events, void *ptr) {
|
|
|
|
http2_session_data *session_data = (http2_session_data *)ptr;
|
|
|
|
if (events & BEV_EVENT_CONNECTED) {
|
2013-12-23 16:01:06 +01:00
|
|
|
int fd = bufferevent_getfd(bev);
|
|
|
|
int val = 1;
|
2013-12-22 11:26:32 +01:00
|
|
|
fprintf(stderr, "Connected\n");
|
2013-12-23 16:01:06 +01:00
|
|
|
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
|
2013-12-22 11:26:32 +01:00
|
|
|
initialize_nghttp2_session(session_data);
|
|
|
|
send_client_connection_header(session_data);
|
|
|
|
submit_request(session_data);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (session_send(session_data) != 0) {
|
2013-12-24 13:30:49 +01:00
|
|
|
delete_http2_session_data(session_data);
|
|
|
|
}
|
2013-12-22 11:26:32 +01:00
|
|
|
return;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (events & BEV_EVENT_EOF) {
|
2013-12-22 11:26:32 +01:00
|
|
|
warnx("Disconnected from the remote host");
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if (events & BEV_EVENT_ERROR) {
|
2013-12-22 11:26:32 +01:00
|
|
|
warnx("Network error");
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if (events & BEV_EVENT_TIMEOUT) {
|
2013-12-22 11:26:32 +01:00
|
|
|
warnx("Timeout");
|
|
|
|
}
|
2013-12-24 11:55:26 +01:00
|
|
|
delete_http2_session_data(session_data);
|
2013-12-22 11:26:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Start connecting to the remote peer |host:port| */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
|
2013-12-24 11:55:26 +01:00
|
|
|
const char *host, uint16_t port,
|
2014-11-27 15:39:04 +01:00
|
|
|
http2_session_data *session_data) {
|
2013-12-22 11:26:32 +01:00
|
|
|
int rv;
|
|
|
|
struct bufferevent *bev;
|
2013-12-24 11:55:26 +01:00
|
|
|
SSL *ssl;
|
|
|
|
|
|
|
|
ssl = create_ssl(ssl_ctx);
|
2014-11-27 15:39:04 +01:00
|
|
|
bev = bufferevent_openssl_socket_new(
|
|
|
|
evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
|
|
|
|
BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
|
2013-12-22 11:26:32 +01:00
|
|
|
bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
|
|
|
|
rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
|
|
|
|
AF_UNSPEC, host, port);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-12-22 11:26:32 +01:00
|
|
|
errx(1, "Could not connect to the remote host %s", host);
|
|
|
|
}
|
|
|
|
session_data->bev = bev;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get resource denoted by the |uri|. The debug and error messages are
|
|
|
|
printed in stderr, while the response body is printed in stdout. */
|
2014-11-27 15:39:04 +01:00
|
|
|
static void run(const char *uri) {
|
2013-12-22 11:26:32 +01:00
|
|
|
struct http_parser_url u;
|
|
|
|
char *host;
|
|
|
|
uint16_t port;
|
|
|
|
int rv;
|
|
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
struct event_base *evbase;
|
2013-12-24 11:55:26 +01:00
|
|
|
http2_session_data *session_data;
|
2013-12-22 11:26:32 +01:00
|
|
|
|
|
|
|
/* Parse the |uri| and stores its components in |u| */
|
|
|
|
rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-12-22 11:26:32 +01:00
|
|
|
errx(1, "Could not parse URI %s", uri);
|
|
|
|
}
|
|
|
|
host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!(u.field_set & (1 << UF_PORT))) {
|
2013-12-22 11:26:32 +01:00
|
|
|
port = 443;
|
|
|
|
} else {
|
|
|
|
port = u.port;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssl_ctx = create_ssl_ctx();
|
|
|
|
|
|
|
|
evbase = event_base_new();
|
|
|
|
|
2013-12-24 11:55:26 +01:00
|
|
|
session_data = create_http2_session_data(evbase);
|
|
|
|
session_data->stream_data = create_http2_stream_data(uri, &u);
|
2013-12-22 11:26:32 +01:00
|
|
|
|
2013-12-24 11:55:26 +01:00
|
|
|
initiate_connection(evbase, ssl_ctx, host, port, session_data);
|
2013-12-22 11:26:32 +01:00
|
|
|
free(host);
|
|
|
|
host = NULL;
|
|
|
|
|
|
|
|
event_base_loop(evbase, 0);
|
|
|
|
|
|
|
|
event_base_free(evbase);
|
|
|
|
SSL_CTX_free(ssl_ctx);
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int main(int argc, char **argv) {
|
2013-12-22 11:26:32 +01:00
|
|
|
struct sigaction act;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (argc < 2) {
|
2013-12-22 11:26:32 +01:00
|
|
|
fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&act, 0, sizeof(struct sigaction));
|
|
|
|
act.sa_handler = SIG_IGN;
|
|
|
|
sigaction(SIGPIPE, &act, NULL);
|
|
|
|
|
|
|
|
SSL_load_error_strings();
|
|
|
|
SSL_library_init();
|
2015-05-22 17:23:38 +02:00
|
|
|
OpenSSL_add_all_algorithms();
|
|
|
|
OPENSSL_config(NULL);
|
2013-12-22 11:26:32 +01:00
|
|
|
|
|
|
|
run(argv[1]);
|
|
|
|
return 0;
|
|
|
|
}
|