+ +
+

Tutorial: HPACK API

+

In this tutorial, we describe basic use of HPACK API in nghttp2 +library. We briefly describe APIs for deflating and inflating header +fields. The example of using these APIs are presented as complete +source code deflate.c.

+
+

Deflating (encoding) headers

+

First we need to initialize nghttp2_hd_deflater object using +nghttp2_hd_deflate_new() function:

+
int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
+                           size_t deflate_hd_table_bufsize_max);
+
+
+

This function allocates nghttp2_hd_deflater object and +initializes it and assigns its pointer to *deflater_ptr passed by +parameter. The deflate_hd_table_bufsize_max is the upper bound of +header table size the deflater will use. This will limit the memory +usage in deflater object for dynamic header table. If you doubt, just +specify 4096 here, which is the default upper bound of dynamic header +table buffer size.

+

To encode header fields, nghttp2_hd_deflate_hd() function:

+
ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater,
+                              uint8_t *buf, size_t buflen,
+                              const nghttp2_nv *nva, size_t nvlen);
+
+
+

The deflater is the deflater object initialized by +nghttp2_hd_deflate_new() function described above. The buf is a +pointer to buffer to store encoded byte string. The buflen is +capacity of buf. The nva is a pointer to nghttp2_nv, +which is an array of header fields to deflate. The nvlen is the +number of header fields which nva contains.

+

It is important to initialize and assign all members of +nghttp2_nv. If a header field should not be inserted in +dynamic header table for a security reason, set +NGHTTP2_NV_FLAG_NO_INDEX flag in nghttp2_nv.flags.

+

nghttp2_hd_deflate_hd() processes all headers given in nva. The +nva must include all request or response header fields to be sent in +one HEADERS (or optionally following (multiple) CONTINUATION +frame(s)). The buf must have enough space to store the encoded +result. Otherwise, the function will fail. To estimate the upper +bound of encoded result, use nghttp2_hd_deflate_bound() function:

+
size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
+                                const nghttp2_nv *nva, size_t nvlen);
+
+
+

Pass this function with the same paramters deflater, nva and +nvlen which will be passed to nghttp2_hd_deflate_hd().

+

The subsequent call of nghttp2_hd_deflate_hd() will use current +encoder state and perform differential encoding which is the +fundamental compression gain for HPACK.

+

Once nghttp2_hd_deflate_hd() fails, it cannot be undone and its +further call with the same deflater object shall fail. So it is very +important to use nghttp2_hd_deflate_bound() to know the required +size of buffer.

+

To delete nghttp2_hd_deflater object, use nghttp2_hd_deflate_del() +function.

+
+

Note

+

Generally, the order of header fields passed to +nghttp2_hd_deflate_hd() function is not preserved. It is known +that the relative ordering of header fields which do not share the +same name is insignificant. But some header fields sharing the +same name require the explicit ordering. To preserve this +ordering, those header values are concatenated into single header +field value using NULL (0x00) as delimiter. This is transparent to +HPACK API. Therefore, the application should examine the inflated +header values and split into multiple header field values by NULL.

+
+
+
+

Inflating (decoding) headers

+

We use nghttp2_hd_inflater object to inflate compressed header +data. To initialize the object, use nghttp2_hd_inflate_new():

+
int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr);
+
+
+

To inflate header data, use nghttp2_hd_inflate_hd() function:

+
ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
+                              nghttp2_nv *nv_out, int *inflate_flags,
+                              uint8_t *in, size_t inlen, int in_final);
+
+
+

The inflater is the inflater object initialized above. The nv_out +is a pointer to nghttp2_nv to store the result. The in is a +pointer to input data and inlen is its length. The caller is not +required to specify whole deflated header data to in at once. It +can call this function multiple times for portion of the data in +streaming way. If in_final is nonzero, it tells the function that +the passed data is the final sequence of deflated header data. The +inflate_flags is output parameter and successful call of this +function stores a set of flags in it. It will be described later.

+

This function returns when each header field is inflated. When this +happens, the function sets NGHTTP2_HD_INFLATE_EMIT flag to +inflate_flag parameter and header field is stored in nv_out. The +return value indicates the number of data read from in to processed +so far. It may be less than inlen. The caller should call the +function repeatedly until all data are processed by adjusting in and +inlen with the processed bytes.

+

If in_final is nonzero and all given data was processed, the +function sets NGHTTP2_HD_INFLATE_FINAL flag to +inflate_flag. If the caller sees this flag set, call +nghttp2_hd_inflate_end_headers() function.

+

If in_final is zero and NGHTTP2_HD_INFLATE_EMIT flag is not +set, it indicates that all given data was processed. The caller is +required to pass subsequent data.

+

It is important to note that the function may produce one or more +header fields even if inlen is 0 when in_final is nonzero, due to +differential encoding.

+

The example use of nghttp2_hd_inflate_hd() is shown in +inflate_header_block() function in deflate.c.

+

To delete nghttp2_hd_inflater object, use nghttp2_hd_inflate_del() +function.

+
+
+

deflate.c

+
/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <stdio.h>
+#include <string.h>
+
+#include <nghttp2/nghttp2.h>
+
+#define MAKE_NV(K, V)                                           \
+  { (uint8_t*)K, (uint8_t*)V, sizeof(K) - 1, sizeof(V) - 1,     \
+      NGHTTP2_NV_FLAG_NONE }
+
+static void deflate(nghttp2_hd_deflater *deflater,
+                    nghttp2_hd_inflater *inflater,
+                    const nghttp2_nv * const nva, size_t nvlen);
+
+static int inflate_header_block(nghttp2_hd_inflater *inflater,
+                                uint8_t *in, size_t inlen, int final);
+
+int main(int argc, char **argv)
+{
+  int rv;
+  nghttp2_hd_deflater *deflater;
+  nghttp2_hd_inflater *inflater;
+  /* Define 1st header set.  This is looks like a HTTP request. */
+  nghttp2_nv nva1[] = {
+    MAKE_NV(":scheme", "https"),
+    MAKE_NV(":authority", "example.org"),
+    MAKE_NV(":path", "/"),
+    MAKE_NV("user-agent", "libnghttp2"),
+    MAKE_NV("accept-encoding", "gzip, deflate")
+  };
+  /* Define 2nd header set */
+  nghttp2_nv nva2[] = {
+    MAKE_NV(":scheme", "https"),
+    MAKE_NV(":authority", "example.org"),
+    MAKE_NV(":path", "/stylesheet/style.css"),
+    MAKE_NV("user-agent", "libnghttp2"),
+    MAKE_NV("accept-encoding", "gzip, deflate"),
+    MAKE_NV("referer", "https://example.org")
+  };
+
+  rv = nghttp2_hd_deflate_new(&deflater, 4096);
+
+  if(rv != 0) {
+    fprintf(stderr, "nghttp2_hd_deflate_init failed with error: %s\n",
+            nghttp2_strerror(rv));
+    exit(EXIT_FAILURE);
+  }
+
+  rv = nghttp2_hd_inflate_new(&inflater);
+
+  if(rv != 0) {
+    fprintf(stderr, "nghttp2_hd_inflate_init failed with error: %s\n",
+            nghttp2_strerror(rv));
+    exit(EXIT_FAILURE);
+  }
+
+  /* Encode and decode 1st header set */
+  deflate(deflater, inflater, nva1, sizeof(nva1) / sizeof(nva1[0]));
+
+  /* Encode and decode 2nd header set, using differential encoding
+     using state after encoding 1st header set. */
+  deflate(deflater, inflater, nva2, sizeof(nva2) / sizeof(nva2[0]));
+
+  nghttp2_hd_inflate_del(inflater);
+  nghttp2_hd_deflate_del(deflater);
+
+  return 0;
+}
+
+static void deflate(nghttp2_hd_deflater *deflater,
+                    nghttp2_hd_inflater *inflater,
+                    const nghttp2_nv * const nva, size_t nvlen)
+{
+  ssize_t rv;
+  uint8_t *buf;
+  size_t buflen;
+  size_t outlen;
+  size_t i;
+  size_t sum;
+
+  sum = 0;
+
+  for(i = 0; i < nvlen; ++i) {
+    sum += nva[i].namelen + nva[i].valuelen;
+  }
+
+  printf("Input (%zu byte(s)):\n\n", sum);
+
+  for(i = 0; i < nvlen; ++i) {
+    fwrite(nva[i].name, nva[i].namelen, 1, stdout);
+    printf(": ");
+    fwrite(nva[i].value, nva[i].valuelen, 1, stdout);
+    printf("\n");
+  }
+
+  buflen = nghttp2_hd_deflate_bound(deflater, nva, nvlen);
+  buf = malloc(buflen);
+
+  rv = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, nvlen);
+
+  if(rv < 0) {
+    fprintf(stderr, "nghttp2_hd_deflate_hd() failed with error: %s\n",
+            nghttp2_strerror((int)rv));
+
+    free(buf);
+
+    exit(EXIT_FAILURE);
+  }
+
+  outlen = rv;
+
+  printf("\nDeflate (%zu byte(s), ratio %.02f):\n\n",
+         outlen, (double)outlen / sum);
+
+  for(i = 0; i < outlen; ++i) {
+    if((i & 0x0fu) == 0) {
+      printf("%08zX: ", i);
+    }
+
+    printf("%02X ", buf[i]);
+
+    if(((i + 1) & 0x0fu) == 0) {
+      printf("\n");
+    }
+  }
+
+  printf("\n\nInflate:\n\n");
+
+  /* We pass 1 to final parameter, because buf contains whole deflated
+     header data. */
+  rv = inflate_header_block(inflater, buf, outlen, 1);
+
+  if(rv != 0) {
+    free(buf);
+
+    exit(EXIT_FAILURE);
+  }
+
+  printf("\n-----------------------------------------------------------"
+         "--------------------\n");
+
+  free(buf);
+}
+
+int inflate_header_block(nghttp2_hd_inflater *inflater,
+                         uint8_t *in, size_t inlen, int final)
+{
+  ssize_t rv;
+
+  for(;;) {
+    nghttp2_nv nv;
+    int inflate_flags = 0;
+    size_t proclen;
+
+    rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags,
+                               in, inlen, final);
+
+    if(rv < 0) {
+      fprintf(stderr, "inflate failed with error code %zd", rv);
+      return -1;
+    }
+
+    proclen = rv;
+
+    in += proclen;
+    inlen -= proclen;
+
+    if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+      fwrite(nv.name, nv.namelen, 1, stderr);
+      fprintf(stderr, ": ");
+      fwrite(nv.value, nv.valuelen, 1, stderr);
+      fprintf(stderr, "\n");
+    }
+
+    if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+      nghttp2_hd_inflate_end_headers(inflater);
+      break;
+    }
+
+    if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 &&
+       inlen == 0) {
+      break;
+    }
+  }
+
+  return 0;
+}
+
+
+
+
+ + +