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
44 #include "nghttp2_hd.h"
45 #include "nghttp2_frame.h"
47 #include "comp_helper.h"
52 size_t deflate_table_size;
54 int dump_header_table;
57 static deflate_config config;
59 static size_t input_sum;
60 static size_t output_sum;
62 static char to_hex_digit(uint8_t n) {
69 static void to_hex(char *dest, const uint8_t *src, size_t len) {
71 for (i = 0; i < len; ++i) {
72 *dest++ = to_hex_digit(src[i] >> 4);
73 *dest++ = to_hex_digit(src[i] & 0xf);
77 static void output_to_json(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
78 size_t inputlen, const std::vector<nghttp2_nv> &nva,
80 auto len = nghttp2_bufs_len(bufs);
81 auto hex = std::vector<char>(len * 2);
82 auto obj = json_object();
83 auto comp_ratio = inputlen == 0 ? 0.0 : (double)len / inputlen * 100;
85 json_object_set_new(obj, "seq", json_integer(seq));
86 json_object_set_new(obj, "input_length", json_integer(inputlen));
87 json_object_set_new(obj, "output_length", json_integer(len));
88 json_object_set_new(obj, "percentage_of_original_size",
89 json_real(comp_ratio));
91 auto hexp = hex.data();
92 for (auto ci = bufs->head; ci; ci = ci->next) {
94 to_hex(hexp, buf->pos, nghttp2_buf_len(buf));
95 hexp += nghttp2_buf_len(buf);
99 json_object_set_new(obj, "wire", json_string(""));
101 json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size()));
103 json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size()));
105 // We only change the header table size only once at the beginning
106 json_object_set_new(obj, "header_table_size",
107 json_integer(config.table_size));
109 if (config.dump_header_table) {
110 json_object_set_new(obj, "header_table", dump_header_table(&deflater->ctx));
112 json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2));
117 static void deflate_hd(nghttp2_hd_deflater *deflater,
118 const std::vector<nghttp2_nv> &nva, size_t inputlen,
123 nghttp2_bufs_init2(&bufs, 4096, 16, 0, nghttp2_mem_default());
125 rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, (nghttp2_nv *)nva.data(),
128 fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq);
132 input_sum += inputlen;
133 output_sum += nghttp2_bufs_len(&bufs);
135 output_to_json(deflater, &bufs, inputlen, nva, seq);
136 nghttp2_bufs_free(&bufs);
139 static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater,
143 auto js = json_object_get(obj, "headers");
145 fprintf(stderr, "'headers' key is missing at %d\n", seq);
148 if (!json_is_array(js)) {
149 fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq);
153 auto len = json_array_size(js);
154 auto nva = std::vector<nghttp2_nv>(len);
156 for (size_t i = 0; i < len; ++i) {
157 auto nv_pair = json_array_get(js, i);
161 if (!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) {
162 fprintf(stderr, "bad formatted name/value pair object at %d\n", seq);
166 json_object_foreach(nv_pair, name, value) {
167 nva[i].name = (uint8_t *)name;
168 nva[i].namelen = strlen(name);
170 if (!json_is_string(value)) {
171 fprintf(stderr, "value is not string at %d\n", seq);
175 nva[i].value = (uint8_t *)json_string_value(value);
176 nva[i].valuelen = strlen(json_string_value(value));
178 nva[i].flags = NGHTTP2_NV_FLAG_NONE;
181 inputlen += nva[i].namelen + nva[i].valuelen;
184 deflate_hd(deflater, nva, inputlen, seq);
189 static nghttp2_hd_deflater *init_deflater() {
190 nghttp2_hd_deflater *deflater;
191 nghttp2_hd_deflate_new(&deflater, config.deflate_table_size);
192 nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
196 static void deinit_deflater(nghttp2_hd_deflater *deflater) {
197 nghttp2_hd_deflate_del(deflater);
200 static int perform(void) {
203 auto json = json_loadf(stdin, 0, &error);
205 if (json == nullptr) {
206 fprintf(stderr, "JSON loading failed\n");
210 auto cases = json_object_get(json, "cases");
212 if (cases == nullptr) {
213 fprintf(stderr, "Missing 'cases' key in root object\n");
217 if (!json_is_array(cases)) {
218 fprintf(stderr, "'cases' must be JSON array\n");
222 auto deflater = init_deflater();
223 output_json_header();
224 auto len = json_array_size(cases);
226 for (size_t i = 0; i < len; ++i) {
227 auto obj = json_array_get(cases, i);
228 if (!json_is_object(obj)) {
229 fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
232 if (deflate_hd_json(obj, deflater, i) != 0) {
239 output_json_footer();
240 deinit_deflater(deflater);
245 static int perform_from_http1text(void) {
249 auto deflater = init_deflater();
250 output_json_header();
252 std::vector<nghttp2_nv> nva;
257 char *rv = fgets(line, sizeof(line), stdin);
262 } else if (line[0] == '\n') {
267 auto &nv = nva.back();
269 val = strchr(line + 1, ':');
270 if (val == nullptr) {
271 fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq);
276 for (; *val && (*val == ' ' || *val == '\t'); ++val)
278 for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n');
283 nv.namelen = strlen(line);
284 nv.valuelen = strlen(val);
285 nv.name = (uint8_t *)strdup(line);
286 nv.value = (uint8_t *)strdup(val);
287 nv.flags = NGHTTP2_NV_FLAG_NONE;
289 inputlen += nv.namelen + nv.valuelen;
296 deflate_hd(deflater, nva, inputlen, seq);
299 for (auto &nv : nva) {
308 output_json_footer();
309 deinit_deflater(deflater);
313 static void print_help(void) {
314 std::cout << R"(HPACK HTTP/2 header encoder
315 Usage: deflatehd [OPTIONS] < INPUT
317 Reads JSON data or HTTP/1-style header fields from stdin and outputs
318 deflated header block in JSON array.
320 For the JSON input, the root JSON object must contain "context" key,
321 which indicates which compression context is used. If it is
322 "request", request compression context is used. Otherwise, response
323 compression context is used. The value of "cases" key contains the
324 sequence of input header set. They share the same compression context
325 and are processed in the order they appear. Each item in the sequence
326 is a JSON object and it must have at least "headers" key. Its value
327 is an array of a JSON object containing exactly one name/value pair.
331 "context": "request",
336 { ":method": "GET" },
342 { ":method": "POST" },
349 With -t option, the program can accept more familiar HTTP/1 style
350 header field block. Each header set must be followed by one empty
362 The output of this program can be used as input for inflatehd.
365 -t, --http1text Use HTTP/1 style header field text as input.
366 Each header set is delimited by single empty
369 Set dynamic table size. In the HPACK
370 specification, this value is denoted by
371 SETTINGS_HEADER_TABLE_SIZE.
373 -S, --deflate-table-size=<N>
374 Use first N bytes of dynamic header table
377 -d, --dump-header-table
378 Output dynamic header table.)" << std::endl;
381 static struct option long_options[] = {
382 {"http1text", no_argument, nullptr, 't'},
383 {"table-size", required_argument, nullptr, 's'},
384 {"deflate-table-size", required_argument, nullptr, 'S'},
385 {"dump-header-table", no_argument, nullptr, 'd'},
386 {nullptr, 0, nullptr, 0}};
388 int main(int argc, char **argv) {
391 config.table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
392 config.deflate_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE;
393 config.http1text = 0;
394 config.dump_header_table = 0;
396 int option_index = 0;
397 int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index);
407 config.http1text = 1;
412 config.table_size = strtoul(optarg, &end, 10);
413 if (errno == ERANGE || *end != '\0') {
414 fprintf(stderr, "-s: Bad option value\n");
419 // --deflate-table-size
421 config.deflate_table_size = strtoul(optarg, &end, 10);
422 if (errno == ERANGE || *end != '\0') {
423 fprintf(stderr, "-S: Bad option value\n");
428 // --dump-header-table
429 config.dump_header_table = 1;
437 if (config.http1text) {
438 perform_from_http1text();
443 auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum;
445 fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum,
446 output_sum, comp_ratio);