Update tutorial

This commit is contained in:
Tatsuhiro Tsujikawa 2014-11-30 21:10:59 +09:00
parent 2b465ee65f
commit f2cd057e89
2 changed files with 187 additions and 228 deletions

View File

@ -22,12 +22,10 @@ protocol over the SSL/TLS transport. In this tutorial, we use
`nghttp2_select_next_protocol()` function to select the HTTP/2 `nghttp2_select_next_protocol()` function to select the HTTP/2
protocol the library supports:: protocol the library supports::
static int select_next_proto_cb(SSL* ssl, static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
unsigned char **out, unsigned char *outlen, unsigned char *outlen, const unsigned char *in,
const unsigned char *in, unsigned int inlen, unsigned int inlen, void *arg _U_) {
void *arg) if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
{
if(nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID); errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
} }
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
@ -36,16 +34,16 @@ protocol the library supports::
The callback is set to the SSL_CTX object using The callback is set to the SSL_CTX object using
``SSL_CTX_set_next_proto_select_cb()`` function:: ``SSL_CTX_set_next_proto_select_cb()`` function::
static SSL_CTX* create_ssl_ctx(void) static SSL_CTX *create_ssl_ctx(void) {
{
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if(!ssl_ctx) { if (!ssl_ctx) {
errx(1, "Could not create SSL/TLS context: %s", errx(1, "Could not create SSL/TLS context: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
} }
SSL_CTX_set_options(ssl_ctx, SSL_CTX_set_options(ssl_ctx,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION | SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
return ssl_ctx; return ssl_ctx;
@ -91,25 +89,22 @@ respectively.
Then we call function ``initiate_connection()`` to start connecting to Then we call function ``initiate_connection()`` to start connecting to
the remote server:: the remote server::
static void initiate_connection(struct event_base *evbase, static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
SSL_CTX *ssl_ctx,
const char *host, uint16_t port, const char *host, uint16_t port,
http2_session_data *session_data) http2_session_data *session_data) {
{
int rv; int rv;
struct bufferevent *bev; struct bufferevent *bev;
SSL *ssl; SSL *ssl;
ssl = create_ssl(ssl_ctx); ssl = create_ssl(ssl_ctx);
bev = bufferevent_openssl_socket_new(evbase, -1, ssl, bev = bufferevent_openssl_socket_new(
BUFFEREVENT_SSL_CONNECTING, evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
BEV_OPT_DEFER_CALLBACKS | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, readcb, writecb, eventcb, session_data); bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase, rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
AF_UNSPEC, host, port); AF_UNSPEC, host, port);
if(rv != 0) { if (rv != 0) {
errx(1, "Could not connect to the remote host %s", host); errx(1, "Could not connect to the remote host %s", host);
} }
session_data->bev = bev; session_data->bev = bev;
@ -122,10 +117,9 @@ The ``eventcb()`` is invoked by libevent event loop when an event
(e.g., connection has been established, timeout, etc) happens on the (e.g., connection has been established, timeout, etc) happens on the
underlying network socket:: underlying network socket::
static void eventcb(struct bufferevent *bev, short events, void *ptr) static void eventcb(struct bufferevent *bev, short events, void *ptr) {
{ http2_session_data *session_data = (http2_session_data *)ptr;
http2_session_data *session_data = (http2_session_data*)ptr; if (events & BEV_EVENT_CONNECTED) {
if(events & BEV_EVENT_CONNECTED) {
int fd = bufferevent_getfd(bev); int fd = bufferevent_getfd(bev);
int val = 1; int val = 1;
fprintf(stderr, "Connected\n"); fprintf(stderr, "Connected\n");
@ -133,16 +127,16 @@ underlying network socket::
initialize_nghttp2_session(session_data); initialize_nghttp2_session(session_data);
send_client_connection_header(session_data); send_client_connection_header(session_data);
submit_request(session_data); submit_request(session_data);
if(session_send(session_data) != 0) { if (session_send(session_data) != 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
} }
return; return;
} }
if(events & BEV_EVENT_EOF) { if (events & BEV_EVENT_EOF) {
warnx("Disconnected from the remote host"); warnx("Disconnected from the remote host");
} else if(events & BEV_EVENT_ERROR) { } else if (events & BEV_EVENT_ERROR) {
warnx("Network error"); warnx("Network error");
} else if(events & BEV_EVENT_TIMEOUT) { } else if (events & BEV_EVENT_TIMEOUT) {
warnx("Timeout"); warnx("Timeout");
} }
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
@ -154,28 +148,27 @@ event, we just simply tear down the connection. The
finished successfully. We first initialize nghttp2 session object in finished successfully. We first initialize nghttp2 session object in
``initialize_nghttp2_session()`` function:: ``initialize_nghttp2_session()`` function::
static void initialize_nghttp2_session(http2_session_data *session_data) static void initialize_nghttp2_session(http2_session_data *session_data) {
{
nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks); nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
(callbacks, on_frame_recv_callback); on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
(callbacks, on_data_chunk_recv_callback); callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback nghttp2_session_callbacks_set_on_stream_close_callback(
(callbacks, on_stream_close_callback); callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_header_callback nghttp2_session_callbacks_set_on_header_callback(callbacks,
(callbacks, on_header_callback); on_header_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback nghttp2_session_callbacks_set_on_begin_headers_callback(
(callbacks, on_begin_headers_callback); callbacks, on_begin_headers_callback);
nghttp2_session_client_new(&session_data->session, callbacks, session_data); nghttp2_session_client_new(&session_data->session, callbacks, session_data);
@ -196,19 +189,16 @@ which is 24 bytes magic byte sequence
transmission of client connection header is done in transmission of client connection header is done in
``send_client_connection_header()``:: ``send_client_connection_header()``::
static void send_client_connection_header(http2_session_data *session_data) static void send_client_connection_header(http2_session_data *session_data) {
{
nghttp2_settings_entry iv[1] = { nghttp2_settings_entry iv[1] = {
{ NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 } {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
};
int rv; int rv;
bufferevent_write(session_data->bev, bufferevent_write(session_data->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
NGHTTP2_CLIENT_CONNECTION_PREFACE,
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN); NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
iv, ARRLEN(iv)); ARRLEN(iv));
if(rv != 0) { if (rv != 0) {
errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
} }
} }
@ -225,24 +215,22 @@ used, which is described about later.
After the transmission of client connection header, we enqueue HTTP After the transmission of client connection header, we enqueue HTTP
request in ``submit_request()`` function:: request in ``submit_request()`` function::
static void submit_request(http2_session_data *session_data) static void submit_request(http2_session_data *session_data) {
{
int32_t stream_id; int32_t stream_id;
http2_stream_data *stream_data = session_data->stream_data; http2_stream_data *stream_data = session_data->stream_data;
const char *uri = stream_data->uri; const char *uri = stream_data->uri;
const struct http_parser_url *u = stream_data->u; const struct http_parser_url *u = stream_data->u;
nghttp2_nv hdrs[] = { nghttp2_nv hdrs[] = {
MAKE_NV2(":method", "GET"), MAKE_NV2(":method", "GET"),
MAKE_NV(":scheme", MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
&uri[u->field_data[UF_SCHEMA].off], u->field_data[UF_SCHEMA].len), u->field_data[UF_SCHEMA].len),
MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen), MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
MAKE_NV(":path", stream_data->path, stream_data->pathlen) MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
};
fprintf(stderr, "Request headers:\n"); fprintf(stderr, "Request headers:\n");
print_headers(stderr, hdrs, ARRLEN(hdrs)); print_headers(stderr, hdrs, ARRLEN(hdrs));
stream_id = nghttp2_submit_request(session_data->session, NULL, stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
hdrs, ARRLEN(hdrs), NULL, stream_data); ARRLEN(hdrs), NULL, stream_data);
if(stream_id < 0) { if (stream_id < 0) {
errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id)); errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
} }
@ -261,26 +249,25 @@ this request.
The next bufferevent callback is ``readcb()``, which is invoked when The next bufferevent callback is ``readcb()``, which is invoked when
data is available to read in the bufferevent input buffer:: data is available to read in the bufferevent input buffer::
static void readcb(struct bufferevent *bev, void *ptr) static void readcb(struct bufferevent *bev, void *ptr) {
{ http2_session_data *session_data = (http2_session_data *)ptr;
http2_session_data *session_data = (http2_session_data*)ptr;
ssize_t readlen; ssize_t readlen;
struct evbuffer *input = bufferevent_get_input(bev); struct evbuffer *input = bufferevent_get_input(bev);
size_t datalen = evbuffer_get_length(input); size_t datalen = evbuffer_get_length(input);
unsigned char *data = evbuffer_pullup(input, -1); unsigned char *data = evbuffer_pullup(input, -1);
readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
if(readlen < 0) { if (readlen < 0) {
warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
if(evbuffer_drain(input, readlen) != 0) { if (evbuffer_drain(input, readlen) != 0) {
warnx("Fatal error: evbuffer_drain failed"); warnx("Fatal error: evbuffer_drain failed");
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
if(session_send(session_data) != 0) { if (session_send(session_data) != 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
@ -293,12 +280,11 @@ invoke nghttp2 callbacks and also queue frames. Since there may be
pending frames, we call ``session_send()`` function to send those pending frames, we call ``session_send()`` function to send those
frames. The ``session_send()`` function is defined as follows:: frames. The ``session_send()`` function is defined as follows::
static int session_send(http2_session_data *session_data) static int session_send(http2_session_data *session_data) {
{
int rv; int rv;
rv = nghttp2_session_send(session_data->session); rv = nghttp2_session_send(session_data->session);
if(rv != 0) { if (rv != 0) {
warnx("Fatal error: %s", nghttp2_strerror(rv)); warnx("Fatal error: %s", nghttp2_strerror(rv));
return -1; return -1;
} }
@ -310,11 +296,9 @@ format and call ``send_callback()`` function of type
:type:`nghttp2_send_callback`. The ``send_callback()`` is defined as :type:`nghttp2_send_callback`. The ``send_callback()`` is defined as
follows:: follows::
static ssize_t send_callback(nghttp2_session *session, static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
const uint8_t *data, size_t length, size_t length, int flags _U_, void *user_data) {
int flags, void *user_data) http2_session_data *session_data = (http2_session_data *)user_data;
{
http2_session_data *session_data = (http2_session_data*)user_data;
struct bufferevent *bev = session_data->bev; struct bufferevent *bev = session_data->bev;
bufferevent_write(bev, data, length); bufferevent_write(bev, data, length);
return length; return length;
@ -336,10 +320,9 @@ buffered data, see the ``send_callback()`` in the server tutorial.
The third bufferevent callback is ``writecb()``, which is invoked when The third bufferevent callback is ``writecb()``, which is invoked when
all data written in the bufferevent output buffer have been sent:: all data written in the bufferevent output buffer have been sent::
static void writecb(struct bufferevent *bev, void *ptr) static void writecb(struct bufferevent *bev _U_, void *ptr) {
{ http2_session_data *session_data = (http2_session_data *)ptr;
http2_session_data *session_data = (http2_session_data*)ptr; if (nghttp2_session_want_read(session_data->session) == 0 &&
if(nghttp2_session_want_read(session_data->session) == 0 &&
nghttp2_session_want_write(session_data->session) == 0 && nghttp2_session_want_write(session_data->session) == 0 &&
evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) { evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
@ -367,17 +350,15 @@ Let's describe remaining nghttp2 callbacks we setup in
Each request header name/value pair is emitted via Each request header name/value pair is emitted via
``on_header_callback`` function:: ``on_header_callback`` function::
static int on_header_callback(nghttp2_session *session, static int on_header_callback(nghttp2_session *session _U_,
const nghttp2_frame *frame, const nghttp2_frame *frame, const uint8_t *name,
const uint8_t *name, size_t namelen, size_t namelen, const uint8_t *value,
const uint8_t *value, size_t valuelen, size_t valuelen, uint8_t flags _U_,
uint8_t flags, void *user_data) {
void *user_data) http2_session_data *session_data = (http2_session_data *)user_data;
{ switch (frame->hd.type) {
http2_session_data *session_data = (http2_session_data*)user_data;
switch(frame->hd.type) {
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
session_data->stream_data->stream_id == frame->hd.stream_id) { session_data->stream_data->stream_id == frame->hd.stream_id) {
/* Print response headers for the initiated request. */ /* Print response headers for the initiated request. */
print_header(stderr, name, namelen, value, valuelen); print_header(stderr, name, namelen, value, valuelen);
@ -392,13 +373,12 @@ In this tutorial, we just print the name/value pair.
After all name/value pairs are emitted for a frame, After all name/value pairs are emitted for a frame,
``on_frame_recv_callback`` function is called:: ``on_frame_recv_callback`` function is called::
static int on_frame_recv_callback(nghttp2_session *session, static int on_frame_recv_callback(nghttp2_session *session _U_,
const nghttp2_frame *frame, void *user_data) const nghttp2_frame *frame, void *user_data) {
{ http2_session_data *session_data = (http2_session_data *)user_data;
http2_session_data *session_data = (http2_session_data*)user_data; switch (frame->hd.type) {
switch(frame->hd.type) {
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
session_data->stream_data->stream_id == frame->hd.stream_id) { session_data->stream_data->stream_id == frame->hd.stream_id) {
fprintf(stderr, "All headers received\n"); fprintf(stderr, "All headers received\n");
} }
@ -415,13 +395,12 @@ its stream ID.
The ``on_data_chunk_recv_callback()`` function is invoked when a chunk The ``on_data_chunk_recv_callback()`` function is invoked when a chunk
of data is received from the remote peer:: of data is received from the remote peer::
static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
int32_t stream_id, uint8_t flags _U_, int32_t stream_id,
const uint8_t *data, size_t len, const uint8_t *data, size_t len,
void *user_data) void *user_data) {
{ http2_session_data *session_data = (http2_session_data *)user_data;
http2_session_data *session_data = (http2_session_data*)user_data; if (session_data->stream_data->stream_id == stream_id) {
if(session_data->stream_data->stream_id == stream_id) {
fwrite(data, len, 1, stdout); fwrite(data, len, 1, stdout);
} }
return 0; return 0;
@ -435,19 +414,17 @@ some binary data.
The ``on_stream_close_callback()`` function is invoked when the stream The ``on_stream_close_callback()`` function is invoked when the stream
is about to close:: is about to close::
static int on_stream_close_callback(nghttp2_session *session, static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
int32_t stream_id,
nghttp2_error_code error_code, nghttp2_error_code error_code,
void *user_data) void *user_data) {
{ http2_session_data *session_data = (http2_session_data *)user_data;
http2_session_data *session_data = (http2_session_data*)user_data;
int rv; int rv;
if(session_data->stream_data->stream_id == stream_id) { if (session_data->stream_data->stream_id == stream_id) {
fprintf(stderr, "Stream %d closed with error_code=%d\n", fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
stream_id, error_code); error_code);
rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
if(rv != 0) { if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
} }

View File

@ -29,17 +29,17 @@ time::
static unsigned char next_proto_list[256]; static unsigned char next_proto_list[256];
static size_t next_proto_list_len; static size_t next_proto_list_len;
static int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, static int next_proto_cb(SSL *s _U_, const unsigned char **data,
void *arg) unsigned int *len, void *arg _U_) {
{
*data = next_proto_list; *data = next_proto_list;
*len = (unsigned int)next_proto_list_len; *len = (unsigned int)next_proto_list_len;
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
static SSL_CTX* create_ssl_ctx(const char *key_file, const char *cert_file) static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
{
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
EC_KEY *ecdh;
ssl_ctx = SSL_CTX_new(SSLv23_server_method()); ssl_ctx = SSL_CTX_new(SSLv23_server_method());
... ...
@ -104,8 +104,7 @@ We first create a listener object to accept incoming connections. We use
libevent's ``struct evconnlistener`` for this purpose:: libevent's ``struct evconnlistener`` for this purpose::
static void start_listen(struct event_base *evbase, const char *service, static void start_listen(struct event_base *evbase, const char *service,
app_context *app_ctx) app_context *app_ctx) {
{
int rv; int rv;
struct addrinfo hints; struct addrinfo hints;
struct addrinfo *res, *rp; struct addrinfo *res, *rp;
@ -116,19 +115,20 @@ libevent's ``struct evconnlistener`` for this purpose::
hints.ai_flags = AI_PASSIVE; hints.ai_flags = AI_PASSIVE;
#ifdef AI_ADDRCONFIG #ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG; hints.ai_flags |= AI_ADDRCONFIG;
#endif // AI_ADDRCONFIG #endif /* AI_ADDRCONFIG */
rv = getaddrinfo(NULL, service, &hints, &res); rv = getaddrinfo(NULL, service, &hints, &res);
if(rv != 0) { if (rv != 0) {
errx(1, NULL); errx(1, NULL);
} }
for(rp = res; rp; rp = rp->ai_next) { for (rp = res; rp; rp = rp->ai_next) {
struct evconnlistener *listener; struct evconnlistener *listener;
listener = evconnlistener_new_bind(evbase, acceptcb, app_ctx, listener = evconnlistener_new_bind(
LEV_OPT_CLOSE_ON_FREE | evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
LEV_OPT_REUSEABLE, -1, 16, rp->ai_addr, rp->ai_addrlen);
rp->ai_addr, rp->ai_addrlen); if (listener) {
if(listener) { freeaddrinfo(res);
return; return;
} }
} }
@ -138,10 +138,9 @@ libevent's ``struct evconnlistener`` for this purpose::
We specify the ``acceptcb`` callback which is called when a new connection is We specify the ``acceptcb`` callback which is called when a new connection is
accepted:: accepted::
static void acceptcb(struct evconnlistener *listener, int fd, static void acceptcb(struct evconnlistener *listener _U_, int fd,
struct sockaddr *addr, int addrlen, void *arg) struct sockaddr *addr, int addrlen, void *arg) {
{ app_context *app_ctx = (app_context *)arg;
app_context *app_ctx = (app_context*)arg;
http2_session_data *session_data; http2_session_data *session_data;
session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); session_data = create_http2_session_data(app_ctx, fd, addr, addrlen);
@ -158,26 +157,25 @@ The ``eventcb()`` callback is invoked by the libevent event loop when an event
(e.g., connection has been established, timeout, etc) happens on the (e.g., connection has been established, timeout, etc) happens on the
underlying network socket:: underlying network socket::
static void eventcb(struct bufferevent *bev, short events, void *ptr) static void eventcb(struct bufferevent *bev _U_, short events, void *ptr) {
{ http2_session_data *session_data = (http2_session_data *)ptr;
http2_session_data *session_data = (http2_session_data*)ptr; if (events & BEV_EVENT_CONNECTED) {
if(events & BEV_EVENT_CONNECTED) {
fprintf(stderr, "%s connected\n", session_data->client_addr); fprintf(stderr, "%s connected\n", session_data->client_addr);
initialize_nghttp2_session(session_data); initialize_nghttp2_session(session_data);
if(send_server_connection_header(session_data) != 0) { if (send_server_connection_header(session_data) != 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
return; return;
} }
if(events & BEV_EVENT_EOF) { if (events & BEV_EVENT_EOF) {
fprintf(stderr, "%s EOF\n", session_data->client_addr); fprintf(stderr, "%s EOF\n", session_data->client_addr);
} else if(events & BEV_EVENT_ERROR) { } else if (events & BEV_EVENT_ERROR) {
fprintf(stderr, "%s network error\n", session_data->client_addr); fprintf(stderr, "%s network error\n", session_data->client_addr);
} else if(events & BEV_EVENT_TIMEOUT) { } else if (events & BEV_EVENT_TIMEOUT) {
fprintf(stderr, "%s timeout\n", session_data->client_addr); fprintf(stderr, "%s timeout\n", session_data->client_addr);
} }
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
@ -195,8 +193,7 @@ communication.
We initialize a nghttp2 session object which is done in We initialize a nghttp2 session object which is done in
``initialize_nghttp2_session()``:: ``initialize_nghttp2_session()``::
static void initialize_nghttp2_session(http2_session_data *session_data) static void initialize_nghttp2_session(http2_session_data *session_data) {
{
nghttp2_option *option; nghttp2_option *option;
nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
@ -210,19 +207,17 @@ We initialize a nghttp2 session object which is done in
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
(callbacks, on_frame_recv_callback); on_frame_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback nghttp2_session_callbacks_set_on_stream_close_callback(
(callbacks, on_stream_close_callback); callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_header_callback
(callbacks, on_header_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback
(callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
nghttp2_session_server_new2(&session_data->session, callbacks, session_data, nghttp2_session_server_new2(&session_data->session, callbacks, session_data,
option); option);
@ -242,16 +237,14 @@ saves some lines of application code.
After initialization of the nghttp2 session object, we are going to send After initialization of the nghttp2 session object, we are going to send
a server connection header in ``send_server_connection_header()``:: a server connection header in ``send_server_connection_header()``::
static int send_server_connection_header(http2_session_data *session_data) static int send_server_connection_header(http2_session_data *session_data) {
{
nghttp2_settings_entry iv[1] = { nghttp2_settings_entry iv[1] = {
{ NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 } {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
};
int rv; int rv;
rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
iv, ARRLEN(iv)); ARRLEN(iv));
if(rv != 0) { if (rv != 0) {
warnx("Fatal error: %s", nghttp2_strerror(rv)); warnx("Fatal error: %s", nghttp2_strerror(rv));
return -1; return -1;
} }
@ -272,23 +265,22 @@ have to process them here since libevent won't invoke callback functions for
this pending data. To process the received data, we call the this pending data. To process the received data, we call the
``session_recv()`` function:: ``session_recv()`` function::
static int session_recv(http2_session_data *session_data) static int session_recv(http2_session_data *session_data) {
{
ssize_t readlen; ssize_t readlen;
struct evbuffer *input = bufferevent_get_input(session_data->bev); struct evbuffer *input = bufferevent_get_input(session_data->bev);
size_t datalen = evbuffer_get_length(input); size_t datalen = evbuffer_get_length(input);
unsigned char *data = evbuffer_pullup(input, -1); unsigned char *data = evbuffer_pullup(input, -1);
readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
if(readlen < 0) { if (readlen < 0) {
warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
return -1; return -1;
} }
if(evbuffer_drain(input, readlen) != 0) { if (evbuffer_drain(input, readlen) != 0) {
warnx("Fatal error: evbuffer_drain failed"); warnx("Fatal error: evbuffer_drain failed");
return -1; return -1;
} }
if(session_send(session_data) != 0) { if (session_send(session_data) != 0) {
return -1; return -1;
} }
return 0; return 0;
@ -301,11 +293,10 @@ nghttp2 callbacks and also queue outgoing frames. Since there may be pending
outgoing frames, we call ``session_send()`` function to send off those outgoing frames, we call ``session_send()`` function to send off those
frames. The ``session_send()`` function is defined as follows:: frames. The ``session_send()`` function is defined as follows::
static int session_send(http2_session_data *session_data) static int session_send(http2_session_data *session_data) {
{
int rv; int rv;
rv = nghttp2_session_send(session_data->session); rv = nghttp2_session_send(session_data->session);
if(rv != 0) { if (rv != 0) {
warnx("Fatal error: %s", nghttp2_strerror(rv)); warnx("Fatal error: %s", nghttp2_strerror(rv));
return -1; return -1;
} }
@ -317,14 +308,12 @@ format and calls ``send_callback()`` of type
:type:`nghttp2_send_callback`. The ``send_callback()`` is defined as :type:`nghttp2_send_callback`. The ``send_callback()`` is defined as
follows:: follows::
static ssize_t send_callback(nghttp2_session *session, static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
const uint8_t *data, size_t length, size_t length, int flags _U_, void *user_data) {
int flags, void *user_data) http2_session_data *session_data = (http2_session_data *)user_data;
{
http2_session_data *session_data = (http2_session_data*)user_data;
struct bufferevent *bev = session_data->bev; struct bufferevent *bev = session_data->bev;
/* Avoid excessive buffering in server side. */ /* Avoid excessive buffering in server side. */
if(evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >=
OUTPUT_WOULDBLOCK_THRESHOLD) { OUTPUT_WOULDBLOCK_THRESHOLD) {
return NGHTTP2_ERR_WOULDBLOCK; return NGHTTP2_ERR_WOULDBLOCK;
} }
@ -350,10 +339,9 @@ calling send_callback.
The next bufferevent callback is ``readcb()``, which is invoked when The next bufferevent callback is ``readcb()``, which is invoked when
data is available to read in the bufferevent input buffer:: data is available to read in the bufferevent input buffer::
static void readcb(struct bufferevent *bev, void *ptr) static void readcb(struct bufferevent *bev _U_, void *ptr) {
{ http2_session_data *session_data = (http2_session_data *)ptr;
http2_session_data *session_data = (http2_session_data*)ptr; if (session_recv(session_data) != 0) {
if(session_recv(session_data) != 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
@ -365,18 +353,17 @@ data.
The third bufferevent callback is ``writecb()``, which is invoked when all The third bufferevent callback is ``writecb()``, which is invoked when all
data in the bufferevent output buffer has been sent:: data in the bufferevent output buffer has been sent::
static void writecb(struct bufferevent *bev, void *ptr) static void writecb(struct bufferevent *bev, void *ptr) {
{ http2_session_data *session_data = (http2_session_data *)ptr;
http2_session_data *session_data = (http2_session_data*)ptr; if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
if(evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
return; return;
} }
if(nghttp2_session_want_read(session_data->session) == 0 && if (nghttp2_session_want_read(session_data->session) == 0 &&
nghttp2_session_want_write(session_data->session) == 0) { nghttp2_session_want_write(session_data->session) == 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
if(session_send(session_data) != 0) { if (session_send(session_data) != 0) {
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
return; return;
} }
@ -407,12 +394,11 @@ a header block in HEADERS or PUSH_PROMISE frame is started::
static int on_begin_headers_callback(nghttp2_session *session, static int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
void *user_data) void *user_data) {
{ http2_session_data *session_data = (http2_session_data *)user_data;
http2_session_data *session_data = (http2_session_data*)user_data;
http2_stream_data *stream_data; http2_stream_data *stream_data;
if(frame->hd.type != NGHTTP2_HEADERS || if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) { frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0; return 0;
} }
@ -436,26 +422,26 @@ emitted via ``on_header_callback`` function, which is called after
``on_begin_headers_callback()``:: ``on_begin_headers_callback()``::
static int on_header_callback(nghttp2_session *session, static int on_header_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame, const uint8_t *name,
const uint8_t *name, size_t namelen, size_t namelen, const uint8_t *value,
const uint8_t *value, size_t valuelen, size_t valuelen, uint8_t flags _U_,
void *user_data) void *user_data _U_) {
{
http2_stream_data *stream_data; http2_stream_data *stream_data;
const char PATH[] = ":path"; const char PATH[] = ":path";
switch(frame->hd.type) { switch (frame->hd.type) {
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
break; break;
} }
stream_data = nghttp2_session_get_stream_user_data(session, stream_data =
frame->hd.stream_id); nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if(!stream_data || stream_data->request_path) { if (!stream_data || stream_data->request_path) {
break; break;
} }
if(namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) {
size_t j; size_t j;
for(j = 0; j < valuelen && value[j] != '?'; ++j); for (j = 0; j < valuelen && value[j] != '?'; ++j)
;
stream_data->request_path = percent_decode(value, j); stream_data->request_path = percent_decode(value, j);
} }
break; break;
@ -472,20 +458,19 @@ The ``on_frame_recv_callback()`` function is invoked when a frame is
fully received:: fully received::
static int on_frame_recv_callback(nghttp2_session *session, static int on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) const nghttp2_frame *frame, void *user_data) {
{ http2_session_data *session_data = (http2_session_data *)user_data;
http2_session_data *session_data = (http2_session_data*)user_data;
http2_stream_data *stream_data; http2_stream_data *stream_data;
switch(frame->hd.type) { switch (frame->hd.type) {
case NGHTTP2_DATA: case NGHTTP2_DATA:
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
/* Check that the client request has finished */ /* Check that the client request has finished */
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
stream_data = nghttp2_session_get_stream_user_data(session, stream_data =
frame->hd.stream_id); nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
/* For DATA and HEADERS frame, this callback may be called after /* For DATA and HEADERS frame, this callback may be called after
on_stream_close_callback. Check that stream still alive. */ on_stream_close_callback. Check that stream still alive. */
if(!stream_data) { if (!stream_data) {
return 0; return 0;
} }
return on_request_recv(session, session_data, stream_data); return on_request_recv(session, session_data, stream_data);
@ -508,15 +493,14 @@ header.
Sending the content of the file is done in ``send_response()`` function:: Sending the content of the file is done in ``send_response()`` function::
static int send_response(nghttp2_session *session, int32_t stream_id, static int send_response(nghttp2_session *session, int32_t stream_id,
nghttp2_nv *nva, size_t nvlen, int fd) nghttp2_nv *nva, size_t nvlen, int fd) {
{
int rv; int rv;
nghttp2_data_provider data_prd; nghttp2_data_provider data_prd;
data_prd.source.fd = fd; data_prd.source.fd = fd;
data_prd.read_callback = file_read_callback; data_prd.read_callback = file_read_callback;
rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd); rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd);
if(rv != 0) { if (rv != 0) {
warnx("Fatal error: %s", nghttp2_strerror(rv)); warnx("Fatal error: %s", nghttp2_strerror(rv));
return -1; return -1;
} }
@ -530,18 +514,19 @@ intended to be used as file descriptor. In this example server, we use
the file descriptor. We also set the ``file_read_callback()`` callback the file descriptor. We also set the ``file_read_callback()`` callback
function to read the contents of the file:: function to read the contents of the file::
static ssize_t file_read_callback static ssize_t file_read_callback(nghttp2_session *session _U_,
(nghttp2_session *session, int32_t stream_id, int32_t stream_id _U_, uint8_t *buf,
uint8_t *buf, size_t length, uint32_t *data_flags, size_t length, uint32_t *data_flags,
nghttp2_data_source *source, void *user_data) nghttp2_data_source *source,
{ void *user_data _U_) {
int fd = source->fd; int fd = source->fd;
ssize_t r; ssize_t r;
while((r = read(fd, buf, length)) == -1 && errno == EINTR); while ((r = read(fd, buf, length)) == -1 && errno == EINTR)
if(r == -1) { ;
if (r == -1) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
if(r == 0) { if (r == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF; *data_flags |= NGHTTP2_DATA_FLAG_EOF;
} }
return r; return r;
@ -559,16 +544,13 @@ remote peer.
The ``on_stream_close_callback()`` function is invoked when the stream The ``on_stream_close_callback()`` function is invoked when the stream
is about to close:: is about to close::
static int on_stream_close_callback(nghttp2_session *session, static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
int32_t stream_id, uint32_t error_code _U_, void *user_data) {
nghttp2_error_code error_code, http2_session_data *session_data = (http2_session_data *)user_data;
void *user_data)
{
http2_session_data *session_data = (http2_session_data*)user_data;
http2_stream_data *stream_data; http2_stream_data *stream_data;
stream_data = nghttp2_session_get_stream_user_data(session, stream_id); stream_data = nghttp2_session_get_stream_user_data(session, stream_id);
if(!stream_data) { if (!stream_data) {
return 0; return 0;
} }
remove_stream(session_data, stream_data); remove_stream(session_data, stream_data);