1 Tutorial: HTTP/2 client
2 =========================
4 In this tutorial, we are going to write 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 examples directory in the
9 This simple client takes 1 argument, HTTPS URI, and retrieves the
10 resource denoted by the URI. Its synopsis is like this::
12 $ libevent-client HTTPS_URI
14 We use libevent in this tutorial to handle networking I/O. Please
15 note that nghttp2 itself does not depend on libevent.
17 First we do some setup routine for libevent and OpenSSL library in
18 function ``main()`` and ``run()``, which is not so relevant to nghttp2
19 library use. The one thing you should look at is setup NPN callback.
20 The NPN callback is used for the client to select the next application
21 protocol over the SSL/TLS transport. In this tutorial, we use
22 `nghttp2_select_next_protocol()` function to select the HTTP/2
23 protocol the library supports::
25 static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
26 unsigned char *outlen, const unsigned char *in,
27 unsigned int inlen, void *arg _U_) {
28 if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
29 errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
31 return SSL_TLSEXT_ERR_OK;
34 The callback is set to the SSL_CTX object using
35 ``SSL_CTX_set_next_proto_select_cb()`` function::
37 static SSL_CTX *create_ssl_ctx(void) {
39 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
41 errx(1, "Could not create SSL/TLS context: %s",
42 ERR_error_string(ERR_get_error(), NULL));
44 SSL_CTX_set_options(ssl_ctx,
45 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
46 SSL_OP_NO_COMPRESSION |
47 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
48 SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
52 We use ``http2_session_data`` structure to store the data related to
56 nghttp2_session *session;
57 struct evdns_base *dnsbase;
58 struct bufferevent *bev;
59 http2_stream_data *stream_data;
62 Since this program only handles 1 URI, it uses only 1 stream. We store
63 its stream specific data in ``http2_stream_data`` structure and the
64 ``stream_data`` points to it. The ``struct http2_stream_data`` is
68 /* The NULL-terminated URI string to retreive. */
70 /* Parsed result of the |uri| */
71 struct http_parser_url *u;
72 /* The authroity portion of the |uri|, not NULL-terminated */
74 /* The path portion of the |uri|, including query, not
77 /* The length of the |authority| */
79 /* The length of the |path| */
81 /* The stream ID of this stream */
85 We creates and initializes these structures in
86 ``create_http2_session_data()`` and ``create_http2_stream_data()``
89 Then we call function ``initiate_connection()`` to start connecting to
92 static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
93 const char *host, uint16_t port,
94 http2_session_data *session_data) {
96 struct bufferevent *bev;
99 ssl = create_ssl(ssl_ctx);
100 bev = bufferevent_openssl_socket_new(
101 evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
102 BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
103 bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
104 rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
105 AF_UNSPEC, host, port);
108 errx(1, "Could not connect to the remote host %s", host);
110 session_data->bev = bev;
113 We set 3 callbacks for the bufferevent: ``reacb``, ``writecb`` and
116 The ``eventcb()`` is invoked by libevent event loop when an event
117 (e.g., connection has been established, timeout, etc) happens on the
118 underlying network socket::
120 static void eventcb(struct bufferevent *bev, short events, void *ptr) {
121 http2_session_data *session_data = (http2_session_data *)ptr;
122 if (events & BEV_EVENT_CONNECTED) {
123 int fd = bufferevent_getfd(bev);
125 fprintf(stderr, "Connected\n");
126 setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
127 initialize_nghttp2_session(session_data);
128 send_client_connection_header(session_data);
129 submit_request(session_data);
130 if (session_send(session_data) != 0) {
131 delete_http2_session_data(session_data);
135 if (events & BEV_EVENT_EOF) {
136 warnx("Disconnected from the remote host");
137 } else if (events & BEV_EVENT_ERROR) {
138 warnx("Network error");
139 } else if (events & BEV_EVENT_TIMEOUT) {
142 delete_http2_session_data(session_data);
145 For ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR`` and ``BEV_EVENT_TIMEOUT``
146 event, we just simply tear down the connection. The
147 ``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake is
148 finished successfully. We first initialize nghttp2 session object in
149 ``initialize_nghttp2_session()`` function::
151 static void initialize_nghttp2_session(http2_session_data *session_data) {
152 nghttp2_session_callbacks *callbacks;
154 nghttp2_session_callbacks_new(&callbacks);
156 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
158 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
159 on_frame_recv_callback);
161 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
162 callbacks, on_data_chunk_recv_callback);
164 nghttp2_session_callbacks_set_on_stream_close_callback(
165 callbacks, on_stream_close_callback);
167 nghttp2_session_callbacks_set_on_header_callback(callbacks,
170 nghttp2_session_callbacks_set_on_begin_headers_callback(
171 callbacks, on_begin_headers_callback);
173 nghttp2_session_client_new(&session_data->session, callbacks, session_data);
175 nghttp2_session_callbacks_del(callbacks);
178 Since we are creating client, we use `nghttp2_session_client_new()` to
179 initialize nghttp2 session object. We setup 7 callbacks for the
180 nghttp2 session. We'll explain these callbacks later.
182 The `delete_http2_session_data()` destroys ``session_data`` and frees
183 its bufferevent, so it closes underlying connection as well. It also
184 calls `nghttp2_session_del()` to delete nghttp2 session object.
186 We begin HTTP/2 communication by sending client connection preface,
187 which is 24 bytes magic byte sequence
188 (:macro:`NGHTTP2_CLIENT_CONNECTION_PREFACE`) and SETTINGS frame. The
189 transmission of client connection header is done in
190 ``send_client_connection_header()``::
192 static void send_client_connection_header(http2_session_data *session_data) {
193 nghttp2_settings_entry iv[1] = {
194 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
197 bufferevent_write(session_data->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
198 NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
199 rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
202 errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
206 Here we specify SETTINGS_MAX_CONCURRENT_STREAMS to 100, which is
207 really not needed for this tiny example progoram, but we are
208 demonstrating the use of SETTINGS frame. To queue the SETTINGS frame
209 for the transmission, we use `nghttp2_submit_settings()`. Note that
210 `nghttp2_submit_settings()` function only queues the frame and not
211 actually send it. All ``nghttp2_submit_*()`` family functions have
212 this property. To actually send the frame, `nghttp2_session_send()` is
213 used, which is described about later.
215 After the transmission of client connection header, we enqueue HTTP
216 request in ``submit_request()`` function::
218 static void submit_request(http2_session_data *session_data) {
220 http2_stream_data *stream_data = session_data->stream_data;
221 const char *uri = stream_data->uri;
222 const struct http_parser_url *u = stream_data->u;
223 nghttp2_nv hdrs[] = {
224 MAKE_NV2(":method", "GET"),
225 MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
226 u->field_data[UF_SCHEMA].len),
227 MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
228 MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
229 fprintf(stderr, "Request headers:\n");
230 print_headers(stderr, hdrs, ARRLEN(hdrs));
231 stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
232 ARRLEN(hdrs), NULL, stream_data);
234 errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
237 stream_data->stream_id = stream_id;
240 We build HTTP request header fields in ``hdrs`` which is an array of
241 :type:`nghttp2_nv`. There are 4 header fields to be sent: ``:method``,
242 ``:scheme``, ``:authority`` and ``:path``. To queue this HTTP request,
243 we use `nghttp2_submit_request()` function. The `stream_data` is
244 passed in *stream_user_data* parameter. It is used in nghttp2
245 callbacks which we'll describe about later.
246 `nghttp2_submit_request()` returns the newly assigned stream ID for
249 The next bufferevent callback is ``readcb()``, which is invoked when
250 data is available to read in the bufferevent input buffer::
252 static void readcb(struct bufferevent *bev, void *ptr) {
253 http2_session_data *session_data = (http2_session_data *)ptr;
255 struct evbuffer *input = bufferevent_get_input(bev);
256 size_t datalen = evbuffer_get_length(input);
257 unsigned char *data = evbuffer_pullup(input, -1);
259 readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
261 warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
262 delete_http2_session_data(session_data);
265 if (evbuffer_drain(input, readlen) != 0) {
266 warnx("Fatal error: evbuffer_drain failed");
267 delete_http2_session_data(session_data);
270 if (session_send(session_data) != 0) {
271 delete_http2_session_data(session_data);
276 In this function, we feed all unprocessed, received data to nghttp2
277 session object using `nghttp2_session_mem_recv()` function. The
278 `nghttp2_session_mem_recv()` processes the received data and may
279 invoke nghttp2 callbacks and also queue frames. Since there may be
280 pending frames, we call ``session_send()`` function to send those
281 frames. The ``session_send()`` function is defined as follows::
283 static int session_send(http2_session_data *session_data) {
286 rv = nghttp2_session_send(session_data->session);
288 warnx("Fatal error: %s", nghttp2_strerror(rv));
294 The `nghttp2_session_send()` function serializes the frame into wire
295 format and call ``send_callback()`` function of type
296 :type:`nghttp2_send_callback`. The ``send_callback()`` is defined as
299 static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
300 size_t length, int flags _U_, void *user_data) {
301 http2_session_data *session_data = (http2_session_data *)user_data;
302 struct bufferevent *bev = session_data->bev;
303 bufferevent_write(bev, data, length);
307 Since we use bufferevent to abstract network I/O, we just write the
308 data to the bufferevent object. Note that `nghttp2_session_send()`
309 continues to write all frames queued so far. If we were writing the
310 data to the non-blocking socket directly using ``write()`` system call
311 in the ``send_callback()``, we will surely get ``EAGAIN`` or
312 ``EWOULDBLOCK`` since the socket has limited send buffer. If that
313 happens, we can return :macro:`NGHTTP2_ERR_WOULDBLOCK` to signal the
314 nghttp2 library to stop sending further data. But writing to the
315 bufferevent, we have to regulate the amount data to be buffered by
316 ourselves to avoid possible huge memory consumption. In this example
317 client, we do not limit anything. To see how to regulate the amount of
318 buffered data, see the ``send_callback()`` in the server tutorial.
320 The third bufferevent callback is ``writecb()``, which is invoked when
321 all data written in the bufferevent output buffer have been sent::
323 static void writecb(struct bufferevent *bev _U_, void *ptr) {
324 http2_session_data *session_data = (http2_session_data *)ptr;
325 if (nghttp2_session_want_read(session_data->session) == 0 &&
326 nghttp2_session_want_write(session_data->session) == 0 &&
327 evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
328 delete_http2_session_data(session_data);
332 As described earlier, we just write off all data in `send_callback()`,
333 we have no data to write in this function. All we have to do is check
334 we have to drop connection or not. The nghttp2 session object keeps
335 track of reception and transmission of GOAWAY frame and other error
336 conditions as well. Using these information, nghttp2 session object
337 will tell whether the connection should be dropped or not. More
338 specifically, both `nghttp2_session_want_read()` and
339 `nghttp2_session_want_write()` return 0, we have no business in the
340 connection. But since we are using bufferevent and its deferred
341 callback option, the bufferevent output buffer may contain the pending
342 data when the ``writecb()`` is called. To handle this situation, we
343 also check whether the output buffer is empty or not. If these
344 conditions are met, we drop connection.
346 We have already described about nghttp2 callback ``send_callback()``.
347 Let's describe remaining nghttp2 callbacks we setup in
348 ``initialize_nghttp2_setup()`` function.
350 Each request header name/value pair is emitted via
351 ``on_header_callback`` function::
353 static int on_header_callback(nghttp2_session *session _U_,
354 const nghttp2_frame *frame, const uint8_t *name,
355 size_t namelen, const uint8_t *value,
356 size_t valuelen, uint8_t flags _U_,
358 http2_session_data *session_data = (http2_session_data *)user_data;
359 switch (frame->hd.type) {
360 case NGHTTP2_HEADERS:
361 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
362 session_data->stream_data->stream_id == frame->hd.stream_id) {
363 /* Print response headers for the initiated request. */
364 print_header(stderr, name, namelen, value, valuelen);
371 In this tutorial, we just print the name/value pair.
373 After all name/value pairs are emitted for a frame,
374 ``on_frame_recv_callback`` function is called::
376 static int on_frame_recv_callback(nghttp2_session *session _U_,
377 const nghttp2_frame *frame, void *user_data) {
378 http2_session_data *session_data = (http2_session_data *)user_data;
379 switch (frame->hd.type) {
380 case NGHTTP2_HEADERS:
381 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
382 session_data->stream_data->stream_id == frame->hd.stream_id) {
383 fprintf(stderr, "All headers received\n");
390 In this tutorial, we are just interested in the HTTP response
391 HEADERS. We check te frame type and its category (it should be
392 :macro:`NGHTTP2_HCAT_RESPONSE` for HTTP response HEADERS). Also check
395 The ``on_data_chunk_recv_callback()`` function is invoked when a chunk
396 of data is received from the remote peer::
398 static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
399 uint8_t flags _U_, int32_t stream_id,
400 const uint8_t *data, size_t len,
402 http2_session_data *session_data = (http2_session_data *)user_data;
403 if (session_data->stream_data->stream_id == stream_id) {
404 fwrite(data, len, 1, stdout);
409 In our case, a chunk of data is response body. After checking stream
410 ID, we just write the recieved data to the stdout. Note that the
411 output in the terminal may be corrupted if the response body contains
414 The ``on_stream_close_callback()`` function is invoked when the stream
417 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
418 nghttp2_error_code error_code,
420 http2_session_data *session_data = (http2_session_data *)user_data;
423 if (session_data->stream_data->stream_id == stream_id) {
424 fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
426 rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
428 return NGHTTP2_ERR_CALLBACK_FAILURE;
434 If the stream ID matches the one we initiated, it means that its
435 stream is going to be closed. Since we have finished to get the
436 resource we want (or the stream was reset by RST_STREAM from the
437 remote peer), we call `nghttp2_session_terminate_session()` to
438 commencing the closure of the HTTP/2 session gracefully. If you have
439 some data associated for the stream to be closed, you may delete it