libevent-server: Use nghttp2_option_set_recv_client_preface()

This commit is contained in:
Tatsuhiro Tsujikawa 2014-09-13 19:58:39 +09:00
parent 901de5fbce
commit 5ff73de195
2 changed files with 72 additions and 104 deletions

View File

@ -79,7 +79,6 @@ We use the ``http2_session_data`` structure to store session-level
app_context *app_ctx; app_context *app_ctx;
nghttp2_session *session; nghttp2_session *session;
char *client_addr; char *client_addr;
size_t handshake_leftlen;
} http2_session_data; } http2_session_data;
We use the ``http2_stream_data`` structure to store stream-level data:: We use the ``http2_stream_data`` structure to store stream-level data::
@ -91,17 +90,15 @@ We use the ``http2_stream_data`` structure to store stream-level data::
int fd; int fd;
} http2_stream_data; } http2_stream_data;
A single HTTP/2 session can have multiple streams. We manage these multiple A single HTTP/2 session can have multiple streams. We manage these
streams with a doubly linked list. The first element of this list is pointed multiple streams with a doubly linked list. The first element of this
to by the ``root->next`` in ``http2_session_data``. Initially, ``root->next`` list is pointed to by the ``root->next`` in ``http2_session_data``.
is ``NULL``. The ``handshake_leftlen`` member of ``http2_session_data`` is Initially, ``root->next`` is ``NULL``. We use libevent's bufferevent
used to track the number of bytes remaining when receiving the first client structure to perform network I/O. Note that the bufferevent object is
connection preface (:macro:`NGHTTP2_CLIENT_CONNECTION_PREFACE`), which is a 24 kept in ``http2_session_data`` and not in ``http2_stream_data``. This
bytes long magic string from the client. We use libevent's bufferevent is because ``http2_stream_data`` is just a logical stream multiplexed
structure to perform network I/O. Note that the bufferevent object is kept in over the single connection managed by bufferevent in
``http2_session_data`` and not in ``http2_stream_data``. This is because ``http2_session_data``.
``http2_stream_data`` is just a logical stream multiplexed over the single
connection managed by bufferevent in ``http2_session_data``.
We first create a listener object to accept incoming connections. We use 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::
@ -148,13 +145,14 @@ accepted::
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);
bufferevent_setcb(session_data->bev, handshake_readcb, NULL, eventcb,
session_data); bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data);
} }
Here we create the ``http2_session_data`` object. The bufferevent for this Here we create the ``http2_session_data`` object. The bufferevent for
connection is also initialized at this time. We specify two callbacks for the this connection is also initialized at this time. We specify three
bufferevent: ``handshake_readcb`` and ``eventcb``. callbacks for the bufferevent: ``readcb``, ``writecb`` and
``eventcb``.
The ``eventcb()`` callback is invoked by the libevent event loop when an event 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
@ -165,6 +163,14 @@ underlying network socket::
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);
if(send_server_connection_header(session_data) != 0) {
delete_http2_session_data(session_data);
return;
}
return; return;
} }
if(events & BEV_EVENT_EOF) { if(events & BEV_EVENT_EOF) {
@ -177,60 +183,29 @@ underlying network socket::
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
} }
For the ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR`` and ``BEV_EVENT_TIMEOUT`` For the ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR`` and
events, we just simply tear down the connection. The ``BEV_EVENT_TIMEOUT`` events, we just simply tear down the connection.
``delete_http2_session_data()`` function destroys the ``http2_session_data`` The ``delete_http2_session_data()`` function destroys the
object and thus also its bufferevent member. As a result, the underlying ``http2_session_data`` object and thus also its bufferevent member.
connection is closed. The ``BEV_EVENT_CONNECTED`` event is invoked when As a result, the underlying connection is closed. The
SSL/TLS handshake is finished successfully. ``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake is
finished successfully. Now we are ready to start the HTTP/2
``handshake_readcb()`` is a callback function to handle a 24 bytes magic byte communication.
string coming from a client, since the nghttp2 library does not handle it::
static void handshake_readcb(struct bufferevent *bev, void *ptr)
{
http2_session_data *session_data = (http2_session_data*)ptr;
uint8_t data[24];
struct evbuffer *input = bufferevent_get_input(session_data->bev);
int readlen = evbuffer_remove(input, data, session_data->handshake_leftlen);
const char *conhead = NGHTTP2_CLIENT_CONNECTION_PREFACE;
if(memcmp(conhead + NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN
- session_data->handshake_leftlen, data, readlen) != 0) {
delete_http2_session_data(session_data);
return;
}
session_data->handshake_leftlen -= readlen;
if(session_data->handshake_leftlen == 0) {
bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, ptr);
/* Process pending data in buffer since they are not notified
further */
initialize_nghttp2_session(session_data);
if(send_server_connection_header(session_data) != 0) {
delete_http2_session_data(session_data);
return;
}
if(session_recv(session_data) != 0) {
delete_http2_session_data(session_data);
return;
}
}
}
We check that the received byte string matches
:macro:`NGHTTP2_CLIENT_CONNECTION_PREFACE`. When they match, the connection
state is ready to start the HTTP/2 communication. First we change the callback
functions for the bufferevent object. We use the same ``eventcb`` callback as
before, but we specify new ``readcb`` and ``writecb`` functions to handle the
HTTP/2 communication. These two functions are described later.
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_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
nghttp2_option_new(&option);
/* Tells nghttp2_session object that it handles client connection
preface */
nghttp2_option_set_recv_client_preface(option, 1);
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);
@ -247,14 +222,22 @@ We initialize a nghttp2 session object which is done in
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_server_new(&session_data->session, callbacks, session_data);
nghttp2_session_server_new2(&session_data->session, callbacks, session_data,
option);
nghttp2_session_callbacks_del(callbacks); nghttp2_session_callbacks_del(callbacks);
nghttp2_option_del(option);
} }
Since we are creating a server, the nghttp2 session object is created using Since we are creating a server and uses options, the nghttp2 session
`nghttp2_session_server_new()` function. We registers five callbacks for object is created using `nghttp2_session_server_new2()` function. We
nghttp2 session object. We'll talk about these callbacks later. registers five callbacks for nghttp2 session object. We'll talk about
these callbacks later. Our server only speaks HTTP/2. In this case,
we use `nghttp2_option_set_recv_client_preface()` to make
:type:`nghttp2_session` object handle client connection preface, which
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()``::

View File

@ -69,7 +69,6 @@ typedef struct http2_session_data {
app_context *app_ctx; app_context *app_ctx;
nghttp2_session *session; nghttp2_session *session;
char *client_addr; char *client_addr;
size_t handshake_leftlen;
} http2_session_data; } http2_session_data;
struct app_context { struct app_context {
@ -192,7 +191,6 @@ static http2_session_data* create_http2_session_data(app_context *app_ctx,
(app_ctx->evbase, fd, ssl, (app_ctx->evbase, fd, ssl,
BUFFEREVENT_SSL_ACCEPTING, BUFFEREVENT_SSL_ACCEPTING,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
session_data->handshake_leftlen = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
rv = getnameinfo(addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST); rv = getnameinfo(addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
if(rv != 0) { if(rv != 0) {
session_data->client_addr = strdup("(unknown)"); session_data->client_addr = strdup("(unknown)");
@ -555,8 +553,15 @@ static int on_stream_close_callback(nghttp2_session *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_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
nghttp2_option_new(&option);
/* Tells nghttp2_session object that it handles client connection
preface */
nghttp2_option_set_recv_client_preface(option, 1);
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);
@ -573,9 +578,13 @@ static void initialize_nghttp2_session(http2_session_data *session_data)
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_server_new(&session_data->session, callbacks, session_data);
nghttp2_session_server_new2(&session_data->session, callbacks, session_data,
option);
nghttp2_session_callbacks_del(callbacks); nghttp2_session_callbacks_del(callbacks);
nghttp2_option_del(option);
} }
/* Send HTTP/2 client connection header, which includes 24 bytes /* Send HTTP/2 client connection header, which includes 24 bytes
@ -638,6 +647,14 @@ 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) {
fprintf(stderr, "%s connected\n", session_data->client_addr); fprintf(stderr, "%s connected\n", session_data->client_addr);
initialize_nghttp2_session(session_data);
if(send_server_connection_header(session_data) != 0) {
delete_http2_session_data(session_data);
return;
}
return; return;
} }
if(events & BEV_EVENT_EOF) { if(events & BEV_EVENT_EOF) {
@ -650,38 +667,6 @@ static void eventcb(struct bufferevent *bev, short events, void *ptr)
delete_http2_session_data(session_data); delete_http2_session_data(session_data);
} }
/* readcb for bufferevent to check first 24 bytes client connection
header. */
static void handshake_readcb(struct bufferevent *bev, void *ptr)
{
http2_session_data *session_data = (http2_session_data*)ptr;
uint8_t data[24];
struct evbuffer *input = bufferevent_get_input(session_data->bev);
int readlen = evbuffer_remove(input, data, session_data->handshake_leftlen);
const char *conhead = NGHTTP2_CLIENT_CONNECTION_PREFACE;
if(memcmp(conhead + NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN
- session_data->handshake_leftlen, data, readlen) != 0) {
delete_http2_session_data(session_data);
return;
}
session_data->handshake_leftlen -= readlen;
if(session_data->handshake_leftlen == 0) {
bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, ptr);
/* Process pending data in buffer since they are not notified
further */
initialize_nghttp2_session(session_data);
if(send_server_connection_header(session_data) != 0) {
delete_http2_session_data(session_data);
return;
}
if(session_recv(session_data) != 0) {
delete_http2_session_data(session_data);
return;
}
}
}
/* callback for evconnlistener */ /* callback for evconnlistener */
static void acceptcb(struct evconnlistener *listener, int fd, static void acceptcb(struct evconnlistener *listener, int fd,
struct sockaddr *addr, int addrlen, void *arg) struct sockaddr *addr, int addrlen, void *arg)
@ -690,8 +675,8 @@ static void acceptcb(struct evconnlistener *listener, int fd,
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);
bufferevent_setcb(session_data->bev, handshake_readcb, NULL, eventcb,
session_data); bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data);
} }
static void start_listen(struct event_base *evbase, const char *service, static void start_listen(struct event_base *evbase, const char *service,