• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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