2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2013 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 #endif // HAVE_CONFIG_H
31 #endif // HAVE_UNISTD_H
46 #include "nghttp2_hd.h"
47 #include "nghttp2_frame.h"
49 #include "comp_helper.h"
54 size_t deflate_table_size;
56 int dump_header_table;
59 static deflate_config config;
61 static size_t input_sum;
62 static size_t output_sum;
64 static char to_hex_digit(uint8_t n) {
71 static void to_hex(char *dest, const uint8_t *src, size_t len) {
73 for (i = 0; i < len; ++i) {
74 *dest++ = to_hex_digit(src[i] >> 4);
75 *dest++ = to_hex_digit(src[i] & 0xf);
79 static void output_to_json(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
80 size_t inputlen, const std::vector<nghttp2_nv> &nva,
82 auto len = nghttp2_bufs_len(bufs);
83 auto hex = std::vector<char>(len * 2);
84 auto obj = json_object();
85 auto comp_ratio = inputlen == 0 ? 0.0 : (double)len / inputlen * 100;
87 json_object_set_new(obj, "seq", json_integer(seq));
88 json_object_set_new(obj, "input_length", json_integer(inputlen));
89 json_object_set_new(obj, "output_length", json_integer(len));
90 json_object_set_new(obj, "percentage_of_original_size",
91 json_real(comp_ratio));
93 auto hexp = hex.data();
94 for (auto ci = bufs->head; ci; ci = ci->next) {
96 to_hex(hexp, buf->pos, nghttp2_buf_len(buf));
97 hexp += nghttp2_buf_len(buf);
101 json_object_set_new(obj, "wire", json_string(""));
103 json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size()));
105 json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size()));
107 // We only change the header table size only once at the beginning
108 json_object_set_new(obj, "header_table_size",
109 json_integer(config.table_size));
111 if (config.dump_header_table) {
112 json_object_set_new(obj, "header_table", dump_header_table(&deflater->ctx));
114 json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2));
119 static void deflate_hd(nghttp2_hd_deflater *deflater,
120 const std::vector<nghttp2_nv> &nva, size_t inputlen,
125 nghttp2_bufs_init2(&bufs, 4096, 16, 0, nghttp2_mem_default());
127 rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, (nghttp2_nv *)nva.data(),
130 fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq);
134 input_sum += inputlen;
135 output_sum += nghttp2_bufs_len(&bufs);
137 output_to_json(deflater, &bufs, inputlen, nva, seq);
138 nghttp2_bufs_free(&bufs);
141 static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater,
145 auto js = json_object_get(obj, "headers");
147 fprintf(stderr, "'headers' key is missing at %d\n", seq);
150 if (!json_is_array(js)) {
151 fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq);
155 auto len = json_array_size(js);
156 auto nva = std::vector<nghttp2_nv>(len);
158 for (size_t i = 0; i < len; ++i) {
159 auto nv_pair = json_array_get(js, i);
163 if (!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) {
164 fprintf(stderr, "bad formatted name/value pair object at %d\n", seq);
168 json_object_foreach(nv_pair, name, value) {
169 nva[i].name = (uint8_t *)name;
170 nva[i].namelen = strlen(name);
172 if (!json_is_string(value)) {
173 fprintf(stderr, "value is not string at %d\n", seq);
177 nva[i].value = (uint8_t *)json_string_value(value);
178 nva[i].valuelen = strlen(json_string_value(value));
180 nva[i].flags = NGHTTP2_NV_FLAG_NONE;
183 inputlen += nva[i].namelen + nva[i].valuelen;
186 deflate_hd(deflater, nva, inputlen, seq);
191 static nghttp2_hd_deflater *init_deflater() {
192 nghttp2_hd_deflater *deflater;
193 nghttp2_hd_deflate_new(&deflater, config.deflate_table_size);
194 nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
198 static void deinit_deflater(nghttp2_hd_deflater *deflater) {
199 nghttp2_hd_deflate_del(deflater);
202 static int perform(void) {
205 auto json = json_loadf(stdin, 0, &error);
207 if (json == nullptr) {
208 fprintf(stderr, "JSON loading failed\n");
212 auto cases = json_object_get(json, "cases");
214 if (cases == nullptr) {
215 fprintf(stderr, "Missing 'cases' key in root object\n");
219 if (!json_is_array(cases)) {
220 fprintf(stderr, "'cases' must be JSON array\n");
224 auto deflater = init_deflater();
225 output_json_header();
226 auto len = json_array_size(cases);
228 for (size_t i = 0; i < len; ++i) {
229 auto obj = json_array_get(cases, i);
230 if (!json_is_object(obj)) {
231 fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
234 if (deflate_hd_json(obj, deflater, i) != 0) {
241 output_json_footer();
242 deinit_deflater(deflater);
247 static int perform_from_http1text(void) {
251 auto deflater = init_deflater();
252 output_json_header();
254 std::vector<nghttp2_nv> nva;
259 char *rv = fgets(line, sizeof(line), stdin);
264 } else if (line[0] == '\n') {
269 auto &nv = nva.back();
271 val = strchr(line + 1, ':');
272 if (val == nullptr) {
273 fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq);
278 for (; *val && (*val == ' ' || *val == '\t'); ++val)
280 for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n');
285 nv.namelen = strlen(line);
286 nv.valuelen = strlen(val);
287 nv.name = (uint8_t *)strdup(line);
288 nv.value = (uint8_t *)strdup(val);
289 nv.flags = NGHTTP2_NV_FLAG_NONE;
291 inputlen += nv.namelen + nv.valuelen;
298 deflate_hd(deflater, nva, inputlen, seq);
301 for (auto &nv : nva) {
310 output_json_footer();
311 deinit_deflater(deflater);
315 static void print_help(void) {
316 std::cout << R"(HPACK HTTP/2 header encoder
317 Usage: deflatehd [OPTIONS] < INPUT
319 Reads JSON data or HTTP/1-style header fields from stdin and outputs
320 deflated header block in JSON array.
322 For the JSON input, the root JSON object must contain "context" key,
323 which indicates which compression context is used. If it is
324 "request", request compression context is used. Otherwise, response
325 compression context is used. The value of "cases" key contains the
326 sequence of input header set. They share the same compression context
327 and are processed in the order they appear. Each item in the sequence
328 is a JSON object and it must have at least "headers" key. Its value
329 is an array of a JSON object containing exactly one name/value pair.
333 "context": "request",
338 { ":method": "GET" },
344 { ":method": "POST" },
351 With -t option, the program can accept more familiar HTTP/1 style
352 header field block. Each header set must be followed by one empty
364 The output of this program can be used as input for inflatehd.
367 -t, --http1text Use HTTP/1 style header field text as input.
368 Each header set is delimited by single empty
371 Set dynamic table size. In the HPACK
372 specification, this value is denoted by
373 SETTINGS_HEADER_TABLE_SIZE.
375 -S, --deflate-table-size=<N>
376 Use first N bytes of dynamic header table
379 -d, --dump-header-table
380 Output dynamic header table.)" << std::endl;
383 static struct option long_options[] = {
384 {"http1text", no_argument, nullptr, 't'},
385 {"table-size", required_argument, nullptr, 's'},
386 {"deflate-table-size", required_argument, nullptr, 'S'},
387 {"dump-header-table", no_argument, nullptr, 'd'},
388 {nullptr, 0, nullptr, 0}};
390 int main(int argc, char **argv) {
393 config.table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
394 config.deflate_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE;
395 config.http1text = 0;
396 config.dump_header_table = 0;
398 int option_index = 0;
399 int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index);
409 config.http1text = 1;
414 config.table_size = strtoul(optarg, &end, 10);
415 if (errno == ERANGE || *end != '\0') {
416 fprintf(stderr, "-s: Bad option value\n");
421 // --deflate-table-size
423 config.deflate_table_size = strtoul(optarg, &end, 10);
424 if (errno == ERANGE || *end != '\0') {
425 fprintf(stderr, "-S: Bad option value\n");
430 // --dump-header-table
431 config.dump_header_table = 1;
439 if (config.http1text) {
440 perform_from_http1text();
445 auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum;
447 fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum,
448 output_sum, comp_ratio);