1Tutorial: HTTP/2 server 2========================= 3 4In this tutorial, we are going to write a single-threaded, event-based 5HTTP/2 web server, which supports HTTPS only. It can handle concurrent 6multiple requests, but only the GET method is supported. The complete 7source code, `libevent-server.c`_, is attached at the end of this 8page. The source also resides in the examples directory in the 9archive or repository. 10 11This simple server takes 3 arguments: The port number to listen on, 12the path to your SSL/TLS private key file, and the path to your 13certificate file. The synopsis is: 14 15.. code-block:: text 16 17 $ libevent-server PORT /path/to/server.key /path/to/server.crt 18 19We use libevent in this tutorial to handle networking I/O. Please 20note that nghttp2 itself does not depend on libevent. 21 22The server starts with some libevent and OpenSSL setup in the 23``main()`` and ``run()`` functions. This setup isn't specific to 24nghttp2, but one thing you should look at is setup of ALPN callback. 25The ALPN callback is used by the server to select application 26protocols offered by client. In ALPN, client sends the list of 27supported application protocols, and server selects one of them. We 28provide the callback for it:: 29 30 static int alpn_select_proto_cb(SSL *ssl _U_, const unsigned char **out, 31 unsigned char *outlen, const unsigned char *in, 32 unsigned int inlen, void *arg _U_) { 33 int rv; 34 35 rv = nghttp2_select_alpn(out, outlen, in, inlen); 36 37 if (rv != 1) { 38 return SSL_TLSEXT_ERR_NOACK; 39 } 40 41 return SSL_TLSEXT_ERR_OK; 42 } 43 44 static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { 45 SSL_CTX *ssl_ctx; 46 EC_KEY *ecdh; 47 48 ssl_ctx = SSL_CTX_new(SSLv23_server_method()); 49 50 ... 51 52 SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); 53 54 return ssl_ctx; 55 } 56 57In ``alpn_select_proto_cb()``, we use `nghttp2_select_alpn()` to 58select application protocol. The `nghttp2_select_alpn()` returns 1 59only if it selected h2 (ALPN identifier for HTTP/2), and out 60parameters were assigned accordingly. 61 62Next, let's take a look at the main structures used by the example 63application: 64 65We use the ``app_context`` structure to store application-wide data:: 66 67 struct app_context { 68 SSL_CTX *ssl_ctx; 69 struct event_base *evbase; 70 }; 71 72We use the ``http2_session_data`` structure to store session-level 73(which corresponds to one HTTP/2 connection) data:: 74 75 typedef struct http2_session_data { 76 struct http2_stream_data root; 77 struct bufferevent *bev; 78 app_context *app_ctx; 79 nghttp2_session *session; 80 char *client_addr; 81 } http2_session_data; 82 83We use the ``http2_stream_data`` structure to store stream-level data:: 84 85 typedef struct http2_stream_data { 86 struct http2_stream_data *prev, *next; 87 char *request_path; 88 int32_t stream_id; 89 int fd; 90 } http2_stream_data; 91 92A single HTTP/2 session can have multiple streams. To manage them, we 93use a doubly linked list: The first element of this list is pointed 94to by the ``root->next`` in ``http2_session_data``. Initially, 95``root->next`` is ``NULL``. 96 97libevent's bufferevent structure is used to perform network I/O, with 98the pointer to the bufferevent stored in the ``http2_session_data`` 99structure. Note that the bufferevent object is kept in 100``http2_session_data`` and not in ``http2_stream_data``. This is 101because ``http2_stream_data`` is just a logical stream multiplexed 102over the single connection managed by the bufferevent in 103``http2_session_data``. 104 105We first create a listener object to accept incoming connections. 106libevent's ``struct evconnlistener`` is used for this purpose:: 107 108 static void start_listen(struct event_base *evbase, const char *service, 109 app_context *app_ctx) { 110 int rv; 111 struct addrinfo hints; 112 struct addrinfo *res, *rp; 113 114 memset(&hints, 0, sizeof(hints)); 115 hints.ai_family = AF_UNSPEC; 116 hints.ai_socktype = SOCK_STREAM; 117 hints.ai_flags = AI_PASSIVE; 118 #ifdef AI_ADDRCONFIG 119 hints.ai_flags |= AI_ADDRCONFIG; 120 #endif /* AI_ADDRCONFIG */ 121 122 rv = getaddrinfo(NULL, service, &hints, &res); 123 if (rv != 0) { 124 errx(1, NULL); 125 } 126 for (rp = res; rp; rp = rp->ai_next) { 127 struct evconnlistener *listener; 128 listener = evconnlistener_new_bind( 129 evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 130 16, rp->ai_addr, (int)rp->ai_addrlen); 131 if (listener) { 132 freeaddrinfo(res); 133 134 return; 135 } 136 } 137 errx(1, "Could not start listener"); 138 } 139 140We specify the ``acceptcb`` callback, which is called when a new connection is 141accepted:: 142 143 static void acceptcb(struct evconnlistener *listener _U_, int fd, 144 struct sockaddr *addr, int addrlen, void *arg) { 145 app_context *app_ctx = (app_context *)arg; 146 http2_session_data *session_data; 147 148 session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); 149 150 bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); 151 } 152 153Here we create the ``http2_session_data`` object. The connection's 154bufferevent is initialized at the same time. We specify three 155callbacks for the bufferevent: ``readcb``, ``writecb``, and 156``eventcb``. 157 158The ``eventcb()`` callback is invoked by the libevent event loop when an event 159(e.g. connection has been established, timeout, etc.) occurs on the 160underlying network socket:: 161 162 static void eventcb(struct bufferevent *bev _U_, short events, void *ptr) { 163 http2_session_data *session_data = (http2_session_data *)ptr; 164 if (events & BEV_EVENT_CONNECTED) { 165 const unsigned char *alpn = NULL; 166 unsigned int alpnlen = 0; 167 SSL *ssl; 168 169 fprintf(stderr, "%s connected\n", session_data->client_addr); 170 171 ssl = bufferevent_openssl_get_ssl(session_data->bev); 172 173 SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); 174 175 if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { 176 fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); 177 delete_http2_session_data(session_data); 178 return; 179 } 180 181 initialize_nghttp2_session(session_data); 182 183 if (send_server_connection_header(session_data) != 0 || 184 session_send(session_data) != 0) { 185 delete_http2_session_data(session_data); 186 return; 187 } 188 189 return; 190 } 191 if (events & BEV_EVENT_EOF) { 192 fprintf(stderr, "%s EOF\n", session_data->client_addr); 193 } else if (events & BEV_EVENT_ERROR) { 194 fprintf(stderr, "%s network error\n", session_data->client_addr); 195 } else if (events & BEV_EVENT_TIMEOUT) { 196 fprintf(stderr, "%s timeout\n", session_data->client_addr); 197 } 198 delete_http2_session_data(session_data); 199 } 200 201Here we validate that HTTP/2 is negotiated, and if not, drop 202connection. 203 204For the ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR``, and 205``BEV_EVENT_TIMEOUT`` events, we just simply tear down the connection. 206The ``delete_http2_session_data()`` function destroys the 207``http2_session_data`` object and its associated bufferevent member. 208As a result, the underlying connection is closed. 209 210The 211``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake has 212completed successfully. After this we are ready to begin communicating 213via HTTP/2. 214 215The ``initialize_nghttp2_session()`` function initializes the nghttp2 216session object and several callbacks:: 217 218 static void initialize_nghttp2_session(http2_session_data *session_data) { 219 nghttp2_session_callbacks *callbacks; 220 221 nghttp2_session_callbacks_new(&callbacks); 222 223 nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback); 224 225 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, 226 on_frame_recv_callback); 227 228 nghttp2_session_callbacks_set_on_stream_close_callback( 229 callbacks, on_stream_close_callback); 230 231 nghttp2_session_callbacks_set_on_header_callback(callbacks, 232 on_header_callback); 233 234 nghttp2_session_callbacks_set_on_begin_headers_callback( 235 callbacks, on_begin_headers_callback); 236 237 nghttp2_session_server_new(&session_data->session, callbacks, session_data); 238 239 nghttp2_session_callbacks_del(callbacks); 240 } 241 242Since we are creating a server, we use `nghttp2_session_server_new()` 243to initialize the nghttp2 session object. We also setup 5 callbacks 244for the nghttp2 session, these are explained later. 245 246The server now begins by sending the server connection preface, which 247always consists of a SETTINGS frame. 248``send_server_connection_header()`` configures and submits it:: 249 250 static int send_server_connection_header(http2_session_data *session_data) { 251 nghttp2_settings_entry iv[1] = { 252 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; 253 int rv; 254 255 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, 256 ARRLEN(iv)); 257 if (rv != 0) { 258 warnx("Fatal error: %s", nghttp2_strerror(rv)); 259 return -1; 260 } 261 return 0; 262 } 263 264In the example SETTINGS frame we've set 265SETTINGS_MAX_CONCURRENT_STREAMS to 100. `nghttp2_submit_settings()` 266is used to queue the frame for transmission, but note it only queues 267the frame for transmission, and doesn't actually send it. All 268functions in the ``nghttp2_submit_*()`` family have this property. To 269actually send the frame, `nghttp2_session_send()` should be used, as 270described later. 271 272Since bufferevent may buffer more than the first 24 bytes from the client, we 273have to process them here since libevent won't invoke callback functions for 274this pending data. To process the received data, we call the 275``session_recv()`` function:: 276 277 static int session_recv(http2_session_data *session_data) { 278 nghttp2_ssize readlen; 279 struct evbuffer *input = bufferevent_get_input(session_data->bev); 280 size_t datalen = evbuffer_get_length(input); 281 unsigned char *data = evbuffer_pullup(input, -1); 282 283 readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen); 284 if (readlen < 0) { 285 warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); 286 return -1; 287 } 288 if (evbuffer_drain(input, (size_t)readlen) != 0) { 289 warnx("Fatal error: evbuffer_drain failed"); 290 return -1; 291 } 292 if (session_send(session_data) != 0) { 293 return -1; 294 } 295 return 0; 296 } 297 298In this function, we feed all unprocessed but already received data to 299the nghttp2 session object using the `nghttp2_session_mem_recv2()` 300function. The `nghttp2_session_mem_recv2()` function processes the 301data and may both invoke the previously setup callbacks and also queue 302outgoing frames. To send any pending outgoing frames, we immediately 303call ``session_send()``. 304 305The ``session_send()`` function is defined as follows:: 306 307 static int session_send(http2_session_data *session_data) { 308 int rv; 309 rv = nghttp2_session_send(session_data->session); 310 if (rv != 0) { 311 warnx("Fatal error: %s", nghttp2_strerror(rv)); 312 return -1; 313 } 314 return 0; 315 } 316 317The `nghttp2_session_send()` function serializes the frame into wire 318format and calls the ``send_callback()``, which is of type 319:type:`nghttp2_send_callback2`. The ``send_callback()`` is defined as 320follows:: 321 322 static nghttp2_ssize send_callback(nghttp2_session *session _U_, 323 const uint8_t *data, size_t length, 324 int flags _U_, void *user_data) { 325 http2_session_data *session_data = (http2_session_data *)user_data; 326 struct bufferevent *bev = session_data->bev; 327 /* Avoid excessive buffering in server side. */ 328 if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= 329 OUTPUT_WOULDBLOCK_THRESHOLD) { 330 return NGHTTP2_ERR_WOULDBLOCK; 331 } 332 bufferevent_write(bev, data, length); 333 return (nghttp2_ssize)length; 334 } 335 336Since we use bufferevent to abstract network I/O, we just write the 337data to the bufferevent object. Note that `nghttp2_session_send()` 338continues to write all frames queued so far. If we were writing the 339data to a non-blocking socket directly using the ``write()`` system 340call in the ``send_callback()``, we'd soon receive an ``EAGAIN`` or 341``EWOULDBLOCK`` error since sockets have a limited send buffer. If 342that happens, it's possible to return :macro:`NGHTTP2_ERR_WOULDBLOCK` 343to signal the nghttp2 library to stop sending further data. But here, 344when writing to the bufferevent, we have to regulate the amount data 345to buffered ourselves to avoid using huge amounts of memory. To 346achieve this, we check the size of the output buffer and if it reaches 347more than or equal to ``OUTPUT_WOULDBLOCK_THRESHOLD`` bytes, we stop 348writing data and return :macro:`NGHTTP2_ERR_WOULDBLOCK`. 349 350The next bufferevent callback is ``readcb()``, which is invoked when 351data is available to read in the bufferevent input buffer:: 352 353 static void readcb(struct bufferevent *bev _U_, void *ptr) { 354 http2_session_data *session_data = (http2_session_data *)ptr; 355 if (session_recv(session_data) != 0) { 356 delete_http2_session_data(session_data); 357 return; 358 } 359 } 360 361In this function, we just call ``session_recv()`` to process incoming 362data. 363 364The third bufferevent callback is ``writecb()``, which is invoked when all 365data in the bufferevent output buffer has been sent:: 366 367 static void writecb(struct bufferevent *bev, void *ptr) { 368 http2_session_data *session_data = (http2_session_data *)ptr; 369 if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { 370 return; 371 } 372 if (nghttp2_session_want_read(session_data->session) == 0 && 373 nghttp2_session_want_write(session_data->session) == 0) { 374 delete_http2_session_data(session_data); 375 return; 376 } 377 if (session_send(session_data) != 0) { 378 delete_http2_session_data(session_data); 379 return; 380 } 381 } 382 383First we check whether we should drop the connection or not. The 384nghttp2 session object keeps track of reception and transmission of 385GOAWAY frames and other error conditions as well. Using this 386information, the nghttp2 session object can state whether the 387connection should be dropped or not. More specifically, if both 388`nghttp2_session_want_read()` and `nghttp2_session_want_write()` 389return 0, the connection is no-longer required and can be closed. 390Since we are using bufferevent and its deferred callback option, the 391bufferevent output buffer may still contain pending data when the 392``writecb()`` is called. To handle this, we check whether the output 393buffer is empty or not. If all of these conditions are met, we drop 394connection. 395 396Otherwise, we call ``session_send()`` to process the pending output 397data. Remember that in ``send_callback()``, we must not write all data to 398bufferevent to avoid excessive buffering. We continue processing pending data 399when the output buffer becomes empty. 400 401We have already described the nghttp2 callback ``send_callback()``. Let's 402learn about the remaining nghttp2 callbacks setup in 403``initialize_nghttp2_setup()`` function. 404 405The ``on_begin_headers_callback()`` function is invoked when the reception of 406a header block in HEADERS or PUSH_PROMISE frame is started:: 407 408 static int on_begin_headers_callback(nghttp2_session *session, 409 const nghttp2_frame *frame, 410 void *user_data) { 411 http2_session_data *session_data = (http2_session_data *)user_data; 412 http2_stream_data *stream_data; 413 414 if (frame->hd.type != NGHTTP2_HEADERS || 415 frame->headers.cat != NGHTTP2_HCAT_REQUEST) { 416 return 0; 417 } 418 stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); 419 nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, 420 stream_data); 421 return 0; 422 } 423 424We are only interested in the HEADERS frame in this function. Since 425the HEADERS frame has several roles in the HTTP/2 protocol, we check 426that it is a request HEADERS, which opens new stream. If the frame is 427a request HEADERS, we create a ``http2_stream_data`` object to store 428the stream related data. We associate the created 429``http2_stream_data`` object with the stream in the nghttp2 session 430object using `nghttp2_set_stream_user_data()`. The 431``http2_stream_data`` object can later be easily retrieved from the 432stream, without searching through the doubly linked list. 433 434In this example server, we want to serve files relative to the current working 435directory in which the program was invoked. Each header name/value pair is 436emitted via ``on_header_callback`` function, which is called after 437``on_begin_headers_callback()``:: 438 439 static int on_header_callback(nghttp2_session *session, 440 const nghttp2_frame *frame, const uint8_t *name, 441 size_t namelen, const uint8_t *value, 442 size_t valuelen, uint8_t flags _U_, 443 void *user_data _U_) { 444 http2_stream_data *stream_data; 445 const char PATH[] = ":path"; 446 switch (frame->hd.type) { 447 case NGHTTP2_HEADERS: 448 if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { 449 break; 450 } 451 stream_data = 452 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); 453 if (!stream_data || stream_data->request_path) { 454 break; 455 } 456 if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { 457 size_t j; 458 for (j = 0; j < valuelen && value[j] != '?'; ++j) 459 ; 460 stream_data->request_path = percent_decode(value, j); 461 } 462 break; 463 } 464 return 0; 465 } 466 467We search for the ``:path`` header field among the request headers and 468store the requested path in the ``http2_stream_data`` object. In this 469example program, we ignore the ``:method`` header field and always 470treat the request as a GET request. 471 472The ``on_frame_recv_callback()`` function is invoked when a frame is 473fully received:: 474 475 static int on_frame_recv_callback(nghttp2_session *session, 476 const nghttp2_frame *frame, void *user_data) { 477 http2_session_data *session_data = (http2_session_data *)user_data; 478 http2_stream_data *stream_data; 479 switch (frame->hd.type) { 480 case NGHTTP2_DATA: 481 case NGHTTP2_HEADERS: 482 /* Check that the client request has finished */ 483 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { 484 stream_data = 485 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); 486 /* For DATA and HEADERS frame, this callback may be called after 487 on_stream_close_callback. Check that stream still alive. */ 488 if (!stream_data) { 489 return 0; 490 } 491 return on_request_recv(session, session_data, stream_data); 492 } 493 break; 494 default: 495 break; 496 } 497 return 0; 498 } 499 500First we retrieve the ``http2_stream_data`` object associated with the 501stream in ``on_begin_headers_callback()`` using 502`nghttp2_session_get_stream_user_data()`. If the requested path 503cannot be served for some reason (e.g. file is not found), we send a 504404 response using ``error_reply()``. Otherwise, we open 505the requested file and send its content. We send the header field 506``:status`` as a single response header. 507 508Sending the file content is performed by the ``send_response()`` function:: 509 510 static int send_response(nghttp2_session *session, int32_t stream_id, 511 nghttp2_nv *nva, size_t nvlen, int fd) { 512 int rv; 513 nghttp2_data_provider2 data_prd; 514 data_prd.source.fd = fd; 515 data_prd.read_callback = file_read_callback; 516 517 rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, &data_prd); 518 if (rv != 0) { 519 warnx("Fatal error: %s", nghttp2_strerror(rv)); 520 return -1; 521 } 522 return 0; 523 } 524 525nghttp2 uses the :type:`nghttp2_data_provider2` structure to send the 526entity body to the remote peer. The ``source`` member of this 527structure is a union, which can be either a void pointer or an int 528(which is intended to be used as file descriptor). In this example 529server, we use it as a file descriptor. We also set the 530``file_read_callback()`` callback function to read the contents of the 531file:: 532 533 static nghttp2_ssize file_read_callback(nghttp2_session *session _U_, 534 int32_t stream_id _U_, uint8_t *buf, 535 size_t length, uint32_t *data_flags, 536 nghttp2_data_source *source, 537 void *user_data _U_) { 538 int fd = source->fd; 539 ssize_t r; 540 while ((r = read(fd, buf, length)) == -1 && errno == EINTR) 541 ; 542 if (r == -1) { 543 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 544 } 545 if (r == 0) { 546 *data_flags |= NGHTTP2_DATA_FLAG_EOF; 547 } 548 return (nghttp2_ssize)r; 549 } 550 551If an error occurs while reading the file, we return 552:macro:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. This tells the 553library to send RST_STREAM to the stream. When all data has been 554read, the :macro:`NGHTTP2_DATA_FLAG_EOF` flag is set to signal nghttp2 555that we have finished reading the file. 556 557The `nghttp2_submit_response2()` function is used to send the response 558to the remote peer. 559 560The ``on_stream_close_callback()`` function is invoked when the stream 561is about to close:: 562 563 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, 564 uint32_t error_code _U_, void *user_data) { 565 http2_session_data *session_data = (http2_session_data *)user_data; 566 http2_stream_data *stream_data; 567 568 stream_data = nghttp2_session_get_stream_user_data(session, stream_id); 569 if (!stream_data) { 570 return 0; 571 } 572 remove_stream(session_data, stream_data); 573 delete_http2_stream_data(stream_data); 574 return 0; 575 } 576 577Lastly, we destroy the ``http2_stream_data`` object in this function, 578since the stream is about to close and we no longer need the object. 579