1 Tutorial: HTTP/2 client
2 =========================
4 In this tutorial, we are going to write a very primitive HTTP/2
5 client. The complete source code, `libevent-client.c`_, is attached at
6 the end of this page. It also resides in the examples directory in
7 the archive or repository.
9 This simple client takes a single HTTPS URI and retrieves the resource
10 at the URI. The synopsis is:
14 $ libevent-client HTTPS_URI
16 We use libevent in this tutorial to handle networking I/O. Please
17 note that nghttp2 itself does not depend on libevent.
19 The client starts with some libevent and OpenSSL setup in the
20 ``main()`` and ``run()`` functions. This setup isn't specific to
21 nghttp2, but one thing you should look at is setup of the NPN
22 callback. The NPN callback is used by the client to select the next
23 application protocol over TLS. In this tutorial, we use the
24 `nghttp2_select_next_protocol()` helper function to select the HTTP/2
25 protocol the library supports::
27 static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
28 unsigned char *outlen, const unsigned char *in,
29 unsigned int inlen, void *arg _U_) {
30 if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
31 errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
33 return SSL_TLSEXT_ERR_OK;
36 If you are following TLS related RFC, you know that NPN is not the
37 standardized way to negotiate HTTP/2. NPN itself is not event
38 published as RFC. The standard way to negotiate HTTP/2 is ALPN,
39 Application-Layer Protocol Negotiation Extension, defined in `RFC 7301
40 <https://tools.ietf.org/html/rfc7301>`_. The one caveat of ALPN is
41 that OpenSSL >= 1.0.2 is required. We use macro to enable/disable
42 ALPN support depending on OpenSSL version. OpenSSL's ALPN
43 implementation does not require callback function like the above. But
44 we have to instruct OpenSSL SSL_CTX to use ALPN, which we'll talk
47 The callback is added to the SSL_CTX object using
48 ``SSL_CTX_set_next_proto_select_cb()``::
50 static SSL_CTX *create_ssl_ctx(void) {
52 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
54 errx(1, "Could not create SSL/TLS context: %s",
55 ERR_error_string(ERR_get_error(), NULL));
57 SSL_CTX_set_options(ssl_ctx,
58 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
59 SSL_OP_NO_COMPRESSION |
60 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
61 SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
63 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
64 SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
65 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
70 Here we see ``SSL_CTX_get_alpn_protos()`` function call. We instructs
71 OpenSSL to notify the server that we support h2, ALPN identifier for
74 The example client defines a couple of structs:
76 We define and use a ``http2_session_data`` structure to store data
77 related to the HTTP/2 session::
80 nghttp2_session *session;
81 struct evdns_base *dnsbase;
82 struct bufferevent *bev;
83 http2_stream_data *stream_data;
86 Since this program only handles one URI, it uses only one stream. We
87 store the single stream's data in a ``http2_stream_data`` structure
88 and the ``stream_data`` points to it. The ``http2_stream_data``
89 structure is defined as follows::
92 /* The NULL-terminated URI string to retrieve. */
94 /* Parsed result of the |uri| */
95 struct http_parser_url *u;
96 /* The authority portion of the |uri|, not NULL-terminated */
98 /* The path portion of the |uri|, including query, not
101 /* The length of the |authority| */
103 /* The length of the |path| */
105 /* The stream ID of this stream */
109 We create and initialize these structures in
110 ``create_http2_session_data()`` and ``create_http2_stream_data()``
113 ``initiate_connection()`` is called to start the connection to the
114 remote server. It's defined as::
116 static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
117 const char *host, uint16_t port,
118 http2_session_data *session_data) {
120 struct bufferevent *bev;
123 ssl = create_ssl(ssl_ctx);
124 bev = bufferevent_openssl_socket_new(
125 evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
126 BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
127 bufferevent_enable(bev, EV_READ | EV_WRITE);
128 bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
129 rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
130 AF_UNSPEC, host, port);
133 errx(1, "Could not connect to the remote host %s", host);
135 session_data->bev = bev;
138 ``initiate_connection()`` creates a bufferevent for the connection and
139 sets up three callbacks: ``readcb``, ``writecb``, and ``eventcb``.
141 The ``eventcb()`` is invoked by the libevent event loop when an event
142 (e.g. connection has been established, timeout, etc.) occurs on the
143 underlying network socket::
145 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
146 http2_session_data *session_data = (http2_session_data *)ptr;
147 if (events & BEV_EVENT_CONNECTED) {
148 int fd = bufferevent_getfd(bev);
150 const unsigned char *alpn = NULL;
151 unsigned int alpnlen = 0;
154 fprintf(stderr, "Connected\n");
156 ssl = bufferevent_openssl_get_ssl(session_data->bev);
158 SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen);
159 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
161 SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
163 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
165 if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
166 fprintf(stderr, "h2 is not negotiated\n");
167 delete_http2_session_data(session_data);
171 setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
172 initialize_nghttp2_session(session_data);
173 send_client_connection_header(session_data);
174 submit_request(session_data);
175 if (session_send(session_data) != 0) {
176 delete_http2_session_data(session_data);
180 if (events & BEV_EVENT_EOF) {
181 warnx("Disconnected from the remote host");
182 } else if (events & BEV_EVENT_ERROR) {
183 warnx("Network error");
184 } else if (events & BEV_EVENT_TIMEOUT) {
187 delete_http2_session_data(session_data);
190 Here we validate that HTTP/2 is negotiated, and if not, drop
193 For ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR``, and ``BEV_EVENT_TIMEOUT``
194 events, we just simply tear down the connection.
196 The ``BEV_EVENT_CONNECTED`` event is invoked when the SSL/TLS
197 handshake has completed successfully. After this we're ready to begin
198 communicating via HTTP/2.
200 The ``initialize_nghttp2_session()`` function initializes the nghttp2
201 session object and several callbacks::
203 static void initialize_nghttp2_session(http2_session_data *session_data) {
204 nghttp2_session_callbacks *callbacks;
206 nghttp2_session_callbacks_new(&callbacks);
208 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
210 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
211 on_frame_recv_callback);
213 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
214 callbacks, on_data_chunk_recv_callback);
216 nghttp2_session_callbacks_set_on_stream_close_callback(
217 callbacks, on_stream_close_callback);
219 nghttp2_session_callbacks_set_on_header_callback(callbacks,
222 nghttp2_session_callbacks_set_on_begin_headers_callback(
223 callbacks, on_begin_headers_callback);
225 nghttp2_session_client_new(&session_data->session, callbacks, session_data);
227 nghttp2_session_callbacks_del(callbacks);
230 Since we are creating a client, we use `nghttp2_session_client_new()`
231 to initialize the nghttp2 session object. The callbacks setup are
234 The `delete_http2_session_data()` function destroys ``session_data``
235 and frees its bufferevent, so the underlying connection is closed. It
236 also calls `nghttp2_session_del()` to delete the nghttp2 session
239 A HTTP/2 connection begins by sending the client connection preface,
240 which is a 24 byte magic byte string (:macro:`NGHTTP2_CLIENT_MAGIC`),
241 followed by a SETTINGS frame. The 24 byte magic string is sent
242 automatically by nghttp2. We send the SETTINGS frame in
243 ``send_client_connection_header()``::
245 static void send_client_connection_header(http2_session_data *session_data) {
246 nghttp2_settings_entry iv[1] = {
247 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
250 /* client 24 bytes magic string will be sent by nghttp2 library */
251 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
254 errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
258 Here we specify SETTINGS_MAX_CONCURRENT_STREAMS as 100. This is not
259 needed for this tiny example program, it just demonstrates use of the
260 SETTINGS frame. To queue the SETTINGS frame for transmission, we call
261 `nghttp2_submit_settings()`. Note that `nghttp2_submit_settings()`
262 only queues the frame for transmission, and doesn't actually send it.
263 All ``nghttp2_submit_*()`` family functions have this property. To
264 actually send the frame, `nghttp2_session_send()` has to be called,
265 which is described (and called) later.
267 After the transmission of the client connection header, we enqueue the
268 HTTP request in the ``submit_request()`` function::
270 static void submit_request(http2_session_data *session_data) {
272 http2_stream_data *stream_data = session_data->stream_data;
273 const char *uri = stream_data->uri;
274 const struct http_parser_url *u = stream_data->u;
275 nghttp2_nv hdrs[] = {
276 MAKE_NV2(":method", "GET"),
277 MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
278 u->field_data[UF_SCHEMA].len),
279 MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
280 MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
281 fprintf(stderr, "Request headers:\n");
282 print_headers(stderr, hdrs, ARRLEN(hdrs));
283 stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
284 ARRLEN(hdrs), NULL, stream_data);
286 errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
289 stream_data->stream_id = stream_id;
292 We build the HTTP request header fields in ``hdrs``, which is an array
293 of :type:`nghttp2_nv`. There are four header fields to be sent:
294 ``:method``, ``:scheme``, ``:authority``, and ``:path``. To queue the
295 HTTP request, we call `nghttp2_submit_request()`. The ``stream_data``
296 is passed via the *stream_user_data* parameter, which is helpfully
297 later passed back to callback functions.
299 `nghttp2_submit_request()` returns the newly assigned stream ID for
302 The next bufferevent callback is ``readcb()``, which is invoked when
303 data is available to read from the bufferevent input buffer::
305 static void readcb(struct bufferevent *bev, void *ptr) {
306 http2_session_data *session_data = (http2_session_data *)ptr;
308 struct evbuffer *input = bufferevent_get_input(bev);
309 size_t datalen = evbuffer_get_length(input);
310 unsigned char *data = evbuffer_pullup(input, -1);
312 readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
314 warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
315 delete_http2_session_data(session_data);
318 if (evbuffer_drain(input, (size_t)readlen) != 0) {
319 warnx("Fatal error: evbuffer_drain failed");
320 delete_http2_session_data(session_data);
323 if (session_send(session_data) != 0) {
324 delete_http2_session_data(session_data);
329 In this function we feed all unprocessed, received data to the nghttp2
330 session object using the `nghttp2_session_mem_recv()` function.
331 `nghttp2_session_mem_recv()` processes the received data and may
332 invoke nghttp2 callbacks and queue frames for transmission. Since
333 there may be pending frames for transmission, we call immediately
334 ``session_send()`` to send them. ``session_send()`` is defined as
337 static int session_send(http2_session_data *session_data) {
340 rv = nghttp2_session_send(session_data->session);
342 warnx("Fatal error: %s", nghttp2_strerror(rv));
348 The `nghttp2_session_send()` function serializes pending frames into
349 wire format and calls the ``send_callback()`` function to send them.
350 ``send_callback()`` has type :type:`nghttp2_send_callback` and is
353 static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
354 size_t length, int flags _U_, void *user_data) {
355 http2_session_data *session_data = (http2_session_data *)user_data;
356 struct bufferevent *bev = session_data->bev;
357 bufferevent_write(bev, data, length);
358 return (ssize_t)length;
361 Since we use bufferevent to abstract network I/O, we just write the
362 data to the bufferevent object. Note that `nghttp2_session_send()`
363 continues to write all frames queued so far. If we were writing the
364 data to the non-blocking socket directly using the ``write()`` system
365 call, we'd soon receive an ``EAGAIN`` or ``EWOULDBLOCK`` error, since
366 sockets have a limited send buffer. If that happens, it's possible to
367 return :macro:`NGHTTP2_ERR_WOULDBLOCK` to signal the nghttp2 library
368 to stop sending further data. When writing to a bufferevent, you
369 should regulate the amount of data written, to avoid possible huge
370 memory consumption. In this example client however we don't implement
371 a limit. To see how to regulate the amount of buffered data, see the
372 ``send_callback()`` in the server tutorial.
374 The third bufferevent callback is ``writecb()``, which is invoked when
375 all data written in the bufferevent output buffer has been sent::
377 static void writecb(struct bufferevent *bev _U_, void *ptr) {
378 http2_session_data *session_data = (http2_session_data *)ptr;
379 if (nghttp2_session_want_read(session_data->session) == 0 &&
380 nghttp2_session_want_write(session_data->session) == 0 &&
381 evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
382 delete_http2_session_data(session_data);
386 As described earlier, we just write off all data in `send_callback()`,
387 so there is no data to write in this function. All we have to do is
388 check if the connection should be dropped or not. The nghttp2 session
389 object keeps track of reception and transmission of GOAWAY frames and
390 other error conditions. Using this information, the nghttp2 session
391 object can state whether the connection should be dropped or not.
392 More specifically, when both `nghttp2_session_want_read()` and
393 `nghttp2_session_want_write()` return 0, the connection is no-longer
394 required and can be closed. Since we're using bufferevent and its
395 deferred callback option, the bufferevent output buffer may still
396 contain pending data when the ``writecb()`` is called. To handle this
397 situation, we also check whether the output buffer is empty or not. If
398 all of these conditions are met, then we drop the connection.
400 Now let's look at the remaining nghttp2 callbacks setup in the
401 ``initialize_nghttp2_setup()`` function.
403 A server responds to the request by first sending a HEADERS frame.
404 The HEADERS frame consists of response header name/value pairs, and
405 the ``on_header_callback()`` is called for each name/value pair::
407 static int on_header_callback(nghttp2_session *session _U_,
408 const nghttp2_frame *frame, const uint8_t *name,
409 size_t namelen, const uint8_t *value,
410 size_t valuelen, uint8_t flags _U_,
412 http2_session_data *session_data = (http2_session_data *)user_data;
413 switch (frame->hd.type) {
414 case NGHTTP2_HEADERS:
415 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
416 session_data->stream_data->stream_id == frame->hd.stream_id) {
417 /* Print response headers for the initiated request. */
418 print_header(stderr, name, namelen, value, valuelen);
425 In this tutorial, we just print the name/value pairs on stderr.
427 After the HEADERS frame has been fully received (and thus all response
428 header name/value pairs have been received), the
429 ``on_frame_recv_callback()`` function is called::
431 static int on_frame_recv_callback(nghttp2_session *session _U_,
432 const nghttp2_frame *frame, void *user_data) {
433 http2_session_data *session_data = (http2_session_data *)user_data;
434 switch (frame->hd.type) {
435 case NGHTTP2_HEADERS:
436 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
437 session_data->stream_data->stream_id == frame->hd.stream_id) {
438 fprintf(stderr, "All headers received\n");
445 ``on_frame_recv_callback()`` is called for other frame types too.
447 In this tutorial, we are just interested in the HTTP response HEADERS
448 frame. We check the frame type and its category (it should be
449 :macro:`NGHTTP2_HCAT_RESPONSE` for HTTP response HEADERS). We also
452 Next, zero or more DATA frames can be received. The
453 ``on_data_chunk_recv_callback()`` function is invoked when a chunk of
454 data is received from the remote peer::
456 static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
457 uint8_t flags _U_, int32_t stream_id,
458 const uint8_t *data, size_t len,
460 http2_session_data *session_data = (http2_session_data *)user_data;
461 if (session_data->stream_data->stream_id == stream_id) {
462 fwrite(data, len, 1, stdout);
467 In our case, a chunk of data is HTTP response body. After checking the
468 stream ID, we just write the received data to stdout. Note the output
469 in the terminal may be corrupted if the response body contains some
472 The ``on_stream_close_callback()`` function is invoked when the stream
475 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
476 nghttp2_error_code error_code,
478 http2_session_data *session_data = (http2_session_data *)user_data;
481 if (session_data->stream_data->stream_id == stream_id) {
482 fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
484 rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
486 return NGHTTP2_ERR_CALLBACK_FAILURE;
492 If the stream ID matches the one we initiated, it means that its
493 stream is going to be closed. Since we have finished receiving
494 resource we wanted (or the stream was reset by RST_STREAM from the
495 remote peer), we call `nghttp2_session_terminate_session()` to
496 commence closure of the HTTP/2 session gracefully. If you have
497 some data associated for the stream to be closed, you may delete it