examples: Add libevent-client, requires libevent for examples
This commit is contained in:
parent
2c1739a24e
commit
dcfd9b22fc
|
@ -237,7 +237,8 @@ AM_CONDITIONAL([ENABLE_SRC], [ test "x${enable_src}" = "xyes" ])
|
|||
# The example programs depend on OpenSSL
|
||||
enable_examples=no
|
||||
if test "x${request_examples}" = "xyes" &&
|
||||
test "x${have_openssl}" = "xyes"; then
|
||||
test "x${have_openssl}" = "xyes" &&
|
||||
test "x${have_libevent_openssl}" = "xyes"; then
|
||||
enable_examples=yes
|
||||
fi
|
||||
|
||||
|
|
|
@ -24,14 +24,26 @@
|
|||
if ENABLE_EXAMPLES
|
||||
|
||||
AM_CFLAGS = -Wall
|
||||
AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \
|
||||
@OPENSSL_CFLAGS@ @DEFS@
|
||||
AM_LDFLAGS = @OPENSSL_LIBS@
|
||||
AM_CPPFLAGS = \
|
||||
-Wall \
|
||||
-I$(srcdir)/../lib/includes \
|
||||
-I$(builddir)/../lib/includes \
|
||||
-I$(top_srcdir)/src \
|
||||
@LIBEVENT_OPENSSL_CFLAGS@ \
|
||||
@OPENSSL_CFLAGS@ \
|
||||
@DEFS@
|
||||
AM_LDFLAGS = \
|
||||
@LIBEVENT_OPENSSL_LIBS@ \
|
||||
@OPENSSL_LIBS@
|
||||
LDADD = $(top_builddir)/lib/libnghttp2.la
|
||||
|
||||
noinst_PROGRAMS = client
|
||||
noinst_PROGRAMS = client libevent-client
|
||||
client_SOURCES = client.c
|
||||
|
||||
libevent_client_SOURCES = \
|
||||
libevent-client.c \
|
||||
$(top_srcdir)/src/http-parser/http_parser.c
|
||||
|
||||
endif # ENABLE_EXAMPLES
|
||||
|
||||
EXTRA_DIST = README.rst
|
||||
|
|
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <err.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#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"
|
||||
|
||||
#define ARRLEN(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
typedef struct {
|
||||
nghttp2_session *session;
|
||||
struct evdns_base *dnsbase;
|
||||
struct bufferevent *bev;
|
||||
const char *uri;
|
||||
struct http_parser_url *u;
|
||||
char *authority;
|
||||
char *path;
|
||||
size_t authoritylen;
|
||||
size_t pathlen;
|
||||
int32_t stream_id;
|
||||
} http2_session_data;
|
||||
|
||||
static void drop_connection(http2_session_data *session_data)
|
||||
{
|
||||
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;
|
||||
free(session_data->path);
|
||||
session_data->path = NULL;
|
||||
session_data->pathlen = 0;
|
||||
free(session_data->authority);
|
||||
session_data->authority = NULL;
|
||||
session_data->authoritylen = 0;
|
||||
session_data->stream_id = -1;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen)
|
||||
{
|
||||
size_t i;
|
||||
for(i = 0; i < nvlen; ++i) {
|
||||
fwrite(nva[i].name, nva[i].namelen, 1, stderr);
|
||||
fprintf(stderr, ": ");
|
||||
fwrite(nva[i].value, nva[i].valuelen, 1, stderr);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static ssize_t send_callback(nghttp2_session *session,
|
||||
const uint8_t *data, size_t length,
|
||||
int flags, void *user_data)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)user_data;
|
||||
struct bufferevent *bev = session_data->bev;
|
||||
bufferevent_write(bev, data, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/* nghttp2_before_frame_send_callback: Called when nghttp2 library is
|
||||
about to send a frame. We use this callback to get stream ID of new
|
||||
stream. Since HEADERS in HTTP/2.0 has several roles, we check that
|
||||
it is a HTTP request HEADERS. */
|
||||
static int before_frame_send_callback
|
||||
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)user_data;
|
||||
if(frame->hd.type == NGHTTP2_HEADERS &&
|
||||
frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
||||
session_data->stream_id = frame->hd.stream_id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* nghttp2_on_frame_recv_callback: Called when nghttp2 library
|
||||
received a frame from the remote peer. */
|
||||
static int on_frame_recv_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)user_data;
|
||||
switch(frame->hd.type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
|
||||
session_data->stream_id == frame->hd.stream_id) {
|
||||
/* Print response headers for the initiated request. */
|
||||
fprintf(stderr, "Response headers:\n");
|
||||
print_headers(stderr, frame->headers.nva, frame->headers.nvlen);
|
||||
}
|
||||
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. */
|
||||
static int 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)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)user_data;
|
||||
if(session_data->stream_id == stream_id) {
|
||||
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 */
|
||||
static int on_stream_close_callback(nghttp2_session *session,
|
||||
int32_t stream_id,
|
||||
nghttp2_error_code error_code,
|
||||
void *user_data)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)user_data;
|
||||
if(session_data->stream_id == stream_id) {
|
||||
fprintf(stderr, "Stream %d closed with error_code=%d\n",
|
||||
stream_id, error_code);
|
||||
nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, NGHTTP2_NO_ERROR,
|
||||
NULL, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NPN TLS extension client callback. We check that server advertised
|
||||
the HTTP/2.0 protocol the nghttp2 library supports. If not, exit
|
||||
the program. */
|
||||
static int select_next_proto_cb(SSL* ssl,
|
||||
unsigned char **out, unsigned char *outlen,
|
||||
const unsigned char *in, unsigned int inlen,
|
||||
void *arg)
|
||||
{
|
||||
if(nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
|
||||
errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
|
||||
}
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
/* Create SSL_CTX. */
|
||||
static SSL_CTX* create_ssl_ctx(void)
|
||||
{
|
||||
SSL_CTX *ssl_ctx;
|
||||
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
if(!ssl_ctx) {
|
||||
errx(1, "Could not create SSL/TLS context: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
}
|
||||
SSL_CTX_set_options(ssl_ctx,
|
||||
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
||||
/* Create SSL object */
|
||||
static SSL* create_ssl(SSL_CTX *ssl_ctx)
|
||||
{
|
||||
SSL *ssl;
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if(!ssl) {
|
||||
errx(1, "Could not create SSL/TLS session object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
}
|
||||
return ssl;
|
||||
}
|
||||
|
||||
static void initialize_nghttp2_session(http2_session_data *session_data)
|
||||
{
|
||||
nghttp2_session_callbacks callbacks = {0};
|
||||
|
||||
callbacks.send_callback = send_callback;
|
||||
callbacks.before_frame_send_callback = before_frame_send_callback;
|
||||
callbacks.on_frame_recv_callback = on_frame_recv_callback;
|
||||
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
|
||||
callbacks.on_stream_close_callback = on_stream_close_callback;
|
||||
nghttp2_session_client_new(&session_data->session, &callbacks, session_data);
|
||||
}
|
||||
|
||||
static void send_client_connection_header(http2_session_data *session_data)
|
||||
{
|
||||
nghttp2_settings_entry iv[1] = {
|
||||
{ NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 }
|
||||
};
|
||||
bufferevent_write(session_data->bev,
|
||||
NGHTTP2_CLIENT_CONNECTION_HEADER,
|
||||
NGHTTP2_CLIENT_CONNECTION_HEADER_LEN);
|
||||
nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE,
|
||||
iv, ARRLEN(iv));
|
||||
}
|
||||
|
||||
#define MAKE_NV(NAME, VALUE, VALUELEN) \
|
||||
{ (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN }
|
||||
|
||||
#define MAKE_NV2(NAME, VALUE) \
|
||||
{ (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1 }
|
||||
|
||||
/* Send HTTP request to the remote peer */
|
||||
static void submit_request(http2_session_data *session_data)
|
||||
{
|
||||
const char *uri = session_data->uri;
|
||||
const struct http_parser_url *u = session_data->u;
|
||||
nghttp2_nv hdrs[] = {
|
||||
MAKE_NV2(":method", "GET"),
|
||||
MAKE_NV(":scheme",
|
||||
&uri[u->field_data[UF_SCHEMA].off], u->field_data[UF_SCHEMA].len),
|
||||
MAKE_NV(":authority", session_data->authority, session_data->authoritylen),
|
||||
MAKE_NV(":path", session_data->path, session_data->pathlen)
|
||||
};
|
||||
fprintf(stderr, "Request headers:\n");
|
||||
print_headers(stderr, hdrs, ARRLEN(hdrs));
|
||||
nghttp2_submit_request(session_data->session, NGHTTP2_PRI_DEFAULT,
|
||||
hdrs, ARRLEN(hdrs), NULL, NULL);
|
||||
}
|
||||
|
||||
/* Serialize the frame and send (or buffer) the data to
|
||||
bufferevent. */
|
||||
static void session_send(http2_session_data *session_data)
|
||||
{
|
||||
nghttp2_session_send(session_data->session);
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void readcb(struct bufferevent *bev, void *ptr)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)ptr;
|
||||
int rv;
|
||||
struct evbuffer *input = bufferevent_get_input(bev);
|
||||
size_t datalen = evbuffer_get_length(input);
|
||||
unsigned char *data = evbuffer_pullup(input, -1);
|
||||
rv = nghttp2_session_mem_recv(session_data->session, data, datalen);
|
||||
if(rv < 0) {
|
||||
warnx("Fatal error: %s", nghttp2_strerror(rv));
|
||||
drop_connection(session_data);
|
||||
} else {
|
||||
evbuffer_drain(input, rv);
|
||||
}
|
||||
session_send(session_data);
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void writecb(struct bufferevent *bev, 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) {
|
||||
drop_connection(session_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void eventcb(struct bufferevent *bev, short events, void *ptr)
|
||||
{
|
||||
http2_session_data *session_data = (http2_session_data*)ptr;
|
||||
if(events & BEV_EVENT_CONNECTED) {
|
||||
fprintf(stderr, "Connected\n");
|
||||
initialize_nghttp2_session(session_data);
|
||||
send_client_connection_header(session_data);
|
||||
submit_request(session_data);
|
||||
session_send(session_data);
|
||||
return;
|
||||
}
|
||||
if(events & BEV_EVENT_EOF) {
|
||||
warnx("Disconnected from the remote host");
|
||||
} else if(events & BEV_EVENT_ERROR) {
|
||||
warnx("Network error");
|
||||
} else if(events & BEV_EVENT_TIMEOUT) {
|
||||
warnx("Timeout");
|
||||
}
|
||||
drop_connection(session_data);
|
||||
}
|
||||
|
||||
/* Start connecting to the remote peer |host:port| */
|
||||
static void initiate_connection(struct event_base *evbase,
|
||||
SSL *ssl, const char *host, uint16_t port,
|
||||
http2_session_data *session_data)
|
||||
{
|
||||
int rv;
|
||||
struct bufferevent *bev;
|
||||
bev = bufferevent_openssl_socket_new(evbase, -1, ssl,
|
||||
BUFFEREVENT_SSL_CONNECTING,
|
||||
BEV_OPT_DEFER_CALLBACKS |
|
||||
BEV_OPT_CLOSE_ON_FREE);
|
||||
bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
|
||||
rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
|
||||
AF_UNSPEC, host, port);
|
||||
|
||||
if(rv != 0) {
|
||||
errx(1, "Could not connect to the remote host %s", host);
|
||||
}
|
||||
session_data->bev = bev;
|
||||
}
|
||||
|
||||
/* Initializes |session_data| */
|
||||
static void initialize_session_data(http2_session_data *session_data,
|
||||
struct event_base *evbase,
|
||||
const char *uri,
|
||||
struct http_parser_url *u)
|
||||
{
|
||||
memset(session_data, 0, sizeof(http2_session_data));
|
||||
session_data->dnsbase = evdns_base_new(evbase, 1);
|
||||
session_data->uri = uri;
|
||||
session_data->u = u;
|
||||
session_data->stream_id = -1;
|
||||
|
||||
session_data->authoritylen = u->field_data[UF_HOST].len;
|
||||
if(u->field_set & (1 << UF_PORT)) {
|
||||
/* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
|
||||
size_t extra = 7;
|
||||
session_data->authority = malloc(session_data->authoritylen + extra);
|
||||
session_data->authoritylen +=
|
||||
snprintf(session_data->authority + u->field_data[UF_HOST].len, extra,
|
||||
":%u", u->port);
|
||||
}
|
||||
memcpy(session_data->authority,
|
||||
&uri[u->field_data[UF_HOST].off], u->field_data[UF_HOST].len);
|
||||
|
||||
if(u->field_set & (1 << UF_PATH)) {
|
||||
session_data->pathlen = u->field_data[UF_PATH].len;
|
||||
}
|
||||
if(u->field_set & (1 << UF_QUERY)) {
|
||||
/* +1 for '?' character */
|
||||
session_data->pathlen += u->field_data[UF_QUERY].len + 1;
|
||||
}
|
||||
session_data->path = malloc(session_data->pathlen);
|
||||
memcpy(session_data->path,
|
||||
&uri[u->field_data[UF_PATH].off], u->field_data[UF_PATH].len);
|
||||
memcpy(session_data->path + u->field_data[UF_PATH].len + 1,
|
||||
&uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
|
||||
}
|
||||
|
||||
/* Get resource denoted by the |uri|. The debug and error messages are
|
||||
printed in stderr, while the response body is printed in stdout. */
|
||||
static void run(const char *uri)
|
||||
{
|
||||
struct http_parser_url u;
|
||||
char *host;
|
||||
uint16_t port;
|
||||
int rv;
|
||||
SSL_CTX *ssl_ctx;
|
||||
SSL *ssl;
|
||||
struct event_base *evbase;
|
||||
http2_session_data session_data;
|
||||
|
||||
/* Parse the |uri| and stores its components in |u| */
|
||||
rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
|
||||
if(rv != 0) {
|
||||
errx(1, "Could not parse URI %s", uri);
|
||||
}
|
||||
host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
|
||||
if(!(u.field_set & (1 << UF_PORT))) {
|
||||
port = 443;
|
||||
} else {
|
||||
port = u.port;
|
||||
}
|
||||
|
||||
ssl_ctx = create_ssl_ctx();
|
||||
ssl = create_ssl(ssl_ctx);
|
||||
|
||||
evbase = event_base_new();
|
||||
|
||||
initialize_session_data(&session_data, evbase, uri, &u);
|
||||
|
||||
initiate_connection(evbase, ssl, host, port, &session_data);
|
||||
free(host);
|
||||
host = NULL;
|
||||
|
||||
event_base_loop(evbase, 0);
|
||||
|
||||
event_base_free(evbase);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct sigaction act;
|
||||
|
||||
if(argc < 2) {
|
||||
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();
|
||||
|
||||
run(argv[1]);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue