comparison src/http/v2/ngx_http_v2_filter_module.c @ 6395:ba3c2ca21aa5

HTTP/2: implemented HPACK Huffman encoding for response headers. This reduces the size of headers by over 30% on average. Based on the patch by Vlad Krasnov: http://mailman.nginx.org/pipermail/nginx-devel/2015-December/007682.html
author Valentin Bartenev <vbart@nginx.com>
date Thu, 11 Feb 2016 15:35:36 +0300
parents 11e019750adc
children b4c28876d2c3
comparison
equal deleted inserted replaced
6394:5fe617f38222 6395:ba3c2ca21aa5
22 #define ngx_http_v2_literal_size(h) \ 22 #define ngx_http_v2_literal_size(h) \
23 (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) 23 (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1)
24 24
25 #define ngx_http_v2_indexed(i) (128 + (i)) 25 #define ngx_http_v2_indexed(i) (128 + (i))
26 #define ngx_http_v2_inc_indexed(i) (64 + (i)) 26 #define ngx_http_v2_inc_indexed(i) (64 + (i))
27
28 #define ngx_http_v2_write_name(dst, src, len, tmp) \
29 ngx_http_v2_string_encode(dst, src, len, tmp, 1)
30 #define ngx_http_v2_write_value(dst, src, len, tmp) \
31 ngx_http_v2_string_encode(dst, src, len, tmp, 0)
27 32
28 #define NGX_HTTP_V2_ENCODE_RAW 0 33 #define NGX_HTTP_V2_ENCODE_RAW 0
29 #define NGX_HTTP_V2_ENCODE_HUFF 0x80 34 #define NGX_HTTP_V2_ENCODE_HUFF 0x80
30 35
31 #define NGX_HTTP_V2_STATUS_INDEX 8 36 #define NGX_HTTP_V2_STATUS_INDEX 8
44 #define NGX_HTTP_V2_LOCATION_INDEX 46 49 #define NGX_HTTP_V2_LOCATION_INDEX 46
45 #define NGX_HTTP_V2_SERVER_INDEX 54 50 #define NGX_HTTP_V2_SERVER_INDEX 54
46 #define NGX_HTTP_V2_VARY_INDEX 59 51 #define NGX_HTTP_V2_VARY_INDEX 59
47 52
48 53
54 static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
55 u_char *tmp, ngx_uint_t lower);
49 static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, 56 static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
50 ngx_uint_t value); 57 ngx_uint_t value);
51 static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( 58 static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
52 ngx_http_request_t *r, u_char *pos, u_char *end); 59 ngx_http_request_t *r, u_char *pos, u_char *end);
53 60
117 124
118 125
119 static ngx_int_t 126 static ngx_int_t
120 ngx_http_v2_header_filter(ngx_http_request_t *r) 127 ngx_http_v2_header_filter(ngx_http_request_t *r)
121 { 128 {
122 u_char status, *pos, *start, *p; 129 u_char status, *pos, *start, *p, *tmp;
123 size_t len; 130 size_t len, tmp_len;
124 ngx_str_t host, location; 131 ngx_str_t host, location;
125 ngx_uint_t i, port; 132 ngx_uint_t i, port;
126 ngx_list_part_t *part; 133 ngx_list_part_t *part;
127 ngx_table_elt_t *header; 134 ngx_table_elt_t *header;
128 ngx_connection_t *fc; 135 ngx_connection_t *fc;
134 #if (NGX_HAVE_INET6) 141 #if (NGX_HAVE_INET6)
135 struct sockaddr_in6 *sin6; 142 struct sockaddr_in6 *sin6;
136 #endif 143 #endif
137 u_char addr[NGX_SOCKADDR_STRLEN]; 144 u_char addr[NGX_SOCKADDR_STRLEN];
138 145
146 static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7";
147 #if (NGX_HTTP_GZIP)
148 static const u_char accept_encoding[12] =
149 "\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f";
150 #endif
151
152 static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER);
153 static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)];
139 154
140 if (!r->stream) { 155 if (!r->stream) {
141 return ngx_http_next_header_filter(r); 156 return ngx_http_next_header_filter(r);
142 } 157 }
143 158
213 len = status ? 1 : 1 + ngx_http_v2_literal_size("418"); 228 len = status ? 1 : 1 + ngx_http_v2_literal_size("418");
214 229
215 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); 230 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
216 231
217 if (r->headers_out.server == NULL) { 232 if (r->headers_out.server == NULL) {
218 len += 1 + (clcf->server_tokens ? ngx_http_v2_literal_size(NGINX_VER) 233 len += 1 + (clcf->server_tokens ? nginx_ver_len : sizeof(nginx));
219 : ngx_http_v2_literal_size("nginx"));
220 } 234 }
221 235
222 if (r->headers_out.date == NULL) { 236 if (r->headers_out.date == NULL) {
223 len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); 237 len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
224 } 238 }
338 r->headers_out.location->hash = 0; 352 r->headers_out.location->hash = 0;
339 353
340 len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len; 354 len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len;
341 } 355 }
342 356
357 tmp_len = len;
358
343 #if (NGX_HTTP_GZIP) 359 #if (NGX_HTTP_GZIP)
344 if (r->gzip_vary) { 360 if (r->gzip_vary) {
345 if (clcf->gzip_vary) { 361 if (clcf->gzip_vary) {
346 len += 1 + ngx_http_v2_literal_size("Accept-Encoding"); 362 len += 1 + sizeof(accept_encoding);
347 363
348 } else { 364 } else {
349 r->gzip_vary = 0; 365 r->gzip_vary = 0;
350 } 366 }
351 } 367 }
384 return NGX_ERROR; 400 return NGX_ERROR;
385 } 401 }
386 402
387 len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len 403 len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
388 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; 404 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
389 } 405
390 406 if (header[i].key.len > tmp_len) {
391 pos = ngx_palloc(r->pool, len); 407 tmp_len = header[i].key.len;
392 if (pos == NULL) { 408 }
409
410 if (header[i].value.len > tmp_len) {
411 tmp_len = header[i].value.len;
412 }
413 }
414
415 tmp = ngx_palloc(r->pool, tmp_len);
416 pos = ngx_pnalloc(r->pool, len);
417
418 if (pos == NULL || tmp == NULL) {
393 return NGX_ERROR; 419 return NGX_ERROR;
394 } 420 }
395 421
396 start = pos; 422 start = pos;
397 423
406 432
407 if (r->headers_out.server == NULL) { 433 if (r->headers_out.server == NULL) {
408 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); 434 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
409 435
410 if (clcf->server_tokens) { 436 if (clcf->server_tokens) {
411 *pos++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof(NGINX_VER) - 1); 437 if (nginx_ver[0] == '\0') {
412 pos = ngx_cpymem(pos, NGINX_VER, sizeof(NGINX_VER) - 1); 438 p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER,
439 sizeof(NGINX_VER) - 1, tmp);
440 nginx_ver_len = p - nginx_ver;
441 }
442
443 pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len);
413 444
414 } else { 445 } else {
415 *pos++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof("nginx") - 1); 446 pos = ngx_cpymem(pos, nginx, sizeof(nginx));
416 pos = ngx_cpymem(pos, "nginx", sizeof("nginx") - 1);
417 } 447 }
418 } 448 }
419 449
420 if (r->headers_out.date == NULL) { 450 if (r->headers_out.date == NULL) {
421 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); 451 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
422 *pos++ = (u_char) ngx_cached_http_time.len; 452 pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data,
423 453 ngx_cached_http_time.len, tmp);
424 pos = ngx_cpymem(pos, ngx_cached_http_time.data,
425 ngx_cached_http_time.len);
426 } 454 }
427 455
428 if (r->headers_out.content_type.len) { 456 if (r->headers_out.content_type.len) {
429 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); 457 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
430 458
431 if (r->headers_out.content_type_len == r->headers_out.content_type.len 459 if (r->headers_out.content_type_len == r->headers_out.content_type.len
432 && r->headers_out.charset.len) 460 && r->headers_out.charset.len)
433 { 461 {
434 *pos = NGX_HTTP_V2_ENCODE_RAW; 462 len = r->headers_out.content_type.len + sizeof("; charset=") - 1
435 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), 463 + r->headers_out.charset.len;
436 r->headers_out.content_type.len 464
437 + sizeof("; charset=") - 1 465 p = ngx_pnalloc(r->pool, len);
438 + r->headers_out.charset.len); 466 if (p == NULL) {
439 467 return NGX_ERROR;
440 p = pos; 468 }
441 469
442 pos = ngx_cpymem(pos, r->headers_out.content_type.data, 470 p = ngx_cpymem(p, r->headers_out.content_type.data,
443 r->headers_out.content_type.len); 471 r->headers_out.content_type.len);
444 472
445 pos = ngx_cpymem(pos, "; charset=", sizeof("; charset=") - 1); 473 p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
446 474
447 pos = ngx_cpymem(pos, r->headers_out.charset.data, 475 p = ngx_cpymem(p, r->headers_out.charset.data,
448 r->headers_out.charset.len); 476 r->headers_out.charset.len);
449 477
450 /* update r->headers_out.content_type for possible logging */ 478 /* updated r->headers_out.content_type is also needed for logging */
451 479
452 r->headers_out.content_type.len = pos - p; 480 r->headers_out.content_type.len = len;
453 r->headers_out.content_type.data = p; 481 r->headers_out.content_type.data = p - len;
454 482 }
455 } else { 483
456 *pos = NGX_HTTP_V2_ENCODE_RAW; 484 pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data,
457 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), 485 r->headers_out.content_type.len, tmp);
458 r->headers_out.content_type.len);
459 pos = ngx_cpymem(pos, r->headers_out.content_type.data,
460 r->headers_out.content_type.len);
461 }
462 } 486 }
463 487
464 if (r->headers_out.content_length == NULL 488 if (r->headers_out.content_length == NULL
465 && r->headers_out.content_length_n >= 0) 489 && r->headers_out.content_length_n >= 0)
466 { 490 {
474 if (r->headers_out.last_modified == NULL 498 if (r->headers_out.last_modified == NULL
475 && r->headers_out.last_modified_time != -1) 499 && r->headers_out.last_modified_time != -1)
476 { 500 {
477 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); 501 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
478 502
479 *pos++ = NGX_HTTP_V2_ENCODE_RAW 503 ngx_http_time(pos, r->headers_out.last_modified_time);
480 | (sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1); 504 len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1;
481 pos = ngx_http_time(pos, r->headers_out.last_modified_time); 505
506 /*
507 * Date will always be encoded using huffman in the temporary buffer,
508 * so it's safe here to use src and dst pointing to the same address.
509 */
510 pos = ngx_http_v2_write_value(pos, pos, len, tmp);
482 } 511 }
483 512
484 if (r->headers_out.location && r->headers_out.location->value.len) { 513 if (r->headers_out.location && r->headers_out.location->value.len) {
485 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); 514 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
486 515 pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data,
487 *pos = NGX_HTTP_V2_ENCODE_RAW; 516 r->headers_out.location->value.len, tmp);
488 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
489 r->headers_out.location->value.len);
490 pos = ngx_cpymem(pos, r->headers_out.location->value.data,
491 r->headers_out.location->value.len);
492 } 517 }
493 518
494 #if (NGX_HTTP_GZIP) 519 #if (NGX_HTTP_GZIP)
495 if (r->gzip_vary) { 520 if (r->gzip_vary) {
496 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); 521 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
497 *pos++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof("Accept-Encoding") - 1); 522 pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding));
498 pos = ngx_cpymem(pos, "Accept-Encoding", sizeof("Accept-Encoding") - 1);
499 } 523 }
500 #endif 524 #endif
501 525
502 part = &r->headers_out.headers.part; 526 part = &r->headers_out.headers.part;
503 header = part->elts; 527 header = part->elts;
518 continue; 542 continue;
519 } 543 }
520 544
521 *pos++ = 0; 545 *pos++ = 0;
522 546
523 *pos = NGX_HTTP_V2_ENCODE_RAW; 547 pos = ngx_http_v2_write_name(pos, header[i].key.data,
524 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), 548 header[i].key.len, tmp);
525 header[i].key.len); 549
526 ngx_strlow(pos, header[i].key.data, header[i].key.len); 550 pos = ngx_http_v2_write_value(pos, header[i].value.data,
527 pos += header[i].key.len; 551 header[i].value.len, tmp);
528
529 *pos = NGX_HTTP_V2_ENCODE_RAW;
530 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
531 header[i].value.len);
532 pos = ngx_cpymem(pos, header[i].value.data, header[i].value.len);
533 } 552 }
534 553
535 frame = ngx_http_v2_create_headers_frame(r, start, pos); 554 frame = ngx_http_v2_create_headers_frame(r, start, pos);
536 if (frame == NULL) { 555 if (frame == NULL) {
537 return NGX_ERROR; 556 return NGX_ERROR;
551 570
552 fc->send_chain = ngx_http_v2_send_chain; 571 fc->send_chain = ngx_http_v2_send_chain;
553 fc->need_last_buf = 1; 572 fc->need_last_buf = 1;
554 573
555 return ngx_http_v2_filter_send(fc, r->stream); 574 return ngx_http_v2_filter_send(fc, r->stream);
575 }
576
577
578 static u_char *
579 ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
580 ngx_uint_t lower)
581 {
582 size_t hlen;
583
584 hlen = ngx_http_v2_huff_encode(src, len, tmp, lower);
585
586 if (hlen > 0) {
587 *dst = NGX_HTTP_V2_ENCODE_HUFF;
588 dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen);
589 return ngx_cpymem(dst, tmp, hlen);
590 }
591
592 *dst = NGX_HTTP_V2_ENCODE_RAW;
593 dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len);
594
595 if (lower) {
596 ngx_strlow(dst, src, len);
597 return dst + len;
598 }
599
600 return ngx_cpymem(dst, src, len);
556 } 601 }
557 602
558 603
559 static u_char * 604 static u_char *
560 ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) 605 ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)