Mercurial > hg > nginx
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) |