Created
March 12, 2023 02:25
-
-
Save theraw/e7d68d523d663d6e5de87c460a0229b4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/auto/modules b/auto/modules | |
index 09bfcb08..645eb897 100644 | |
--- a/auto/modules | |
+++ b/auto/modules | |
@@ -438,6 +438,10 @@ if [ $HTTP = YES ]; then | |
. auto/module | |
fi | |
+ if [ $HTTP_V2_HPACK_ENC = YES ]; then | |
+ have=NGX_HTTP_V2_HPACK_ENC . auto/have | |
+ fi | |
+ | |
if :; then | |
ngx_module_name=ngx_http_static_module | |
ngx_module_incs= | |
diff --git a/auto/options b/auto/options | |
index 48e8d3f..f2a0e56 100644 | |
--- a/auto/options | |
+++ b/auto/options | |
@@ -59,6 +59,7 @@ HTTP_CHARSET=YES | |
HTTP_GZIP=YES | |
HTTP_SSL=NO | |
HTTP_V2=NO | |
+HTTP_V2_HPACK_ENC=NO | |
HTTP_V3=NO | |
HTTP_SSI=YES | |
HTTP_REALIP=NO | |
@@ -228,6 +229,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" | |
--with-http_ssl_module) HTTP_SSL=YES ;; | |
--with-http_v2_module) HTTP_V2=YES ;; | |
+ --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; | |
--with-http_v3_module) HTTP_V3=YES ;; | |
--with-http_realip_module) HTTP_REALIP=YES ;; | |
--with-http_addition_module) HTTP_ADDITION=YES ;; | |
@@ -447,6 +449,7 @@ cat << END | |
--with-http_ssl_module enable ngx_http_ssl_module | |
--with-http_v2_module enable ngx_http_v2_module | |
+ --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc | |
--with-http_v3_module enable ngx_http_v3_module | |
--with-http_realip_module enable ngx_http_realip_module | |
--with-http_addition_module enable ngx_http_addition_module | |
diff --git a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c | |
index 5ade658d..4932f20d 100644 | |
--- a/src/core/ngx_murmurhash.c | |
+++ b/src/core/ngx_murmurhash.c | |
@@ -50,3 +50,63 @@ ngx_murmur_hash2(u_char *data, size_t len) | |
return h; | |
} | |
+ | |
+ | |
+uint64_t | |
+ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) | |
+{ | |
+ uint64_t h, k; | |
+ | |
+ h = seed ^ len; | |
+ | |
+ while (len >= 8) { | |
+ k = data[0]; | |
+ k |= data[1] << 8; | |
+ k |= data[2] << 16; | |
+ k |= data[3] << 24; | |
+ k |= (uint64_t)data[4] << 32; | |
+ k |= (uint64_t)data[5] << 40; | |
+ k |= (uint64_t)data[6] << 48; | |
+ k |= (uint64_t)data[7] << 56; | |
+ | |
+ k *= 0xc6a4a7935bd1e995ull; | |
+ k ^= k >> 47; | |
+ k *= 0xc6a4a7935bd1e995ull; | |
+ | |
+ h ^= k; | |
+ h *= 0xc6a4a7935bd1e995ull; | |
+ | |
+ data += 8; | |
+ len -= 8; | |
+ } | |
+ | |
+ switch (len) { | |
+ case 7: | |
+ h ^= (uint64_t)data[6] << 48; | |
+ /* fall through */ | |
+ case 6: | |
+ h ^= (uint64_t)data[5] << 40; | |
+ /* fall through */ | |
+ case 5: | |
+ h ^= (uint64_t)data[4] << 32; | |
+ /* fall through */ | |
+ case 4: | |
+ h ^= data[3] << 24; | |
+ /* fall through */ | |
+ case 3: | |
+ h ^= data[2] << 16; | |
+ /* fall through */ | |
+ case 2: | |
+ h ^= data[1] << 8; | |
+ /* fall through */ | |
+ case 1: | |
+ h ^= data[0]; | |
+ h *= 0xc6a4a7935bd1e995ull; | |
+ } | |
+ | |
+ h ^= h >> 47; | |
+ h *= 0xc6a4a7935bd1e995ull; | |
+ h ^= h >> 47; | |
+ | |
+ return h; | |
+} | |
diff --git a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h | |
index 54e867d3..322b3df9 100644 | |
--- a/src/core/ngx_murmurhash.h | |
+++ b/src/core/ngx_murmurhash.h | |
@@ -15,5 +15,7 @@ | |
uint32_t ngx_murmur_hash2(u_char *data, size_t len); | |
+uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); | |
+ | |
#endif /* _NGX_MURMURHASH_H_INCLUDED_ */ | |
diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c | |
index e7770192..c18b60ca 100644 | |
--- a/src/http/v2/ngx_http_v2.c | |
+++ b/src/http/v2/ngx_http_v2.c | |
@@ -270,6 +270,8 @@ ngx_http_v2_init(ngx_event_t *rev) | |
h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; | |
+ h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; | |
+ | |
h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); | |
h2c->concurrent_pushes = h2scf->concurrent_pushes; | |
@@ -2076,6 +2078,14 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, | |
case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: | |
h2c->table_update = 1; | |
+ | |
+ if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { | |
+ h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; | |
+ } else { | |
+ h2c->max_hpack_table_size = value; | |
+ } | |
+ | |
+ h2c->indicate_resize = 1; | |
break; | |
default: | |
diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h | |
index ebd0e77c..b152b504 100644 | |
--- a/src/http/v2/ngx_http_v2.h | |
+++ b/src/http/v2/ngx_http_v2.h | |
@@ -52,6 +52,14 @@ | |
#define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) | |
#define NGX_HTTP_V2_DEFAULT_WINDOW 65535 | |
+#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ | |
+#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) | |
+#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ | |
+#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ | |
+ | |
+#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 | |
+#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ | |
+ | |
#define NGX_HTTP_V2_DEFAULT_WEIGHT 16 | |
@@ -115,6 +123,46 @@ typedef struct { | |
} ngx_http_v2_hpack_t; | |
+#if (NGX_HTTP_V2_HPACK_ENC) | |
+typedef struct { | |
+ uint64_t hash_val; | |
+ uint32_t index; | |
+ uint16_t pos; | |
+ uint16_t klen, vlen; | |
+ uint16_t size; | |
+ uint16_t next; | |
+} ngx_http_v2_hpack_enc_entry_t; | |
+ | |
+ | |
+typedef struct { | |
+ uint64_t hash_val; | |
+ uint32_t index; | |
+ uint16_t pos; | |
+ uint16_t klen; | |
+} ngx_http_v2_hpack_name_entry_t; | |
+ | |
+ | |
+typedef struct { | |
+ size_t size; /* size as defined in RFC 7541 */ | |
+ uint32_t top; /* the last entry */ | |
+ uint32_t pos; | |
+ uint16_t n_elems; /* number of elements */ | |
+ uint16_t base; /* index of the oldest entry */ | |
+ uint16_t last; /* index of the newest entry */ | |
+ | |
+ /* hash table for dynamic entries, instead using a generic hash table, | |
+ which would be too slow to process a significant amount of headers, | |
+ this table is not determenistic, and might ocasionally fail to insert | |
+ a value, at the cost of slightly worse compression, but significantly | |
+ faster performance */ | |
+ ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; | |
+ ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; | |
+ u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + | |
+ HPACK_ENC_MAX_ENTRY]; | |
+} ngx_http_v2_hpack_enc_t; | |
+#endif | |
+ | |
+ | |
struct ngx_http_v2_connection_s { | |
ngx_connection_t *connection; | |
ngx_http_connection_t *http_connection; | |
@@ -130,6 +178,8 @@ struct ngx_http_v2_connection_s { | |
size_t frame_size; | |
+ size_t max_hpack_table_size; | |
+ | |
ngx_queue_t waiting; | |
ngx_http_v2_state_t state; | |
@@ -157,6 +207,11 @@ struct ngx_http_v2_connection_s { | |
unsigned blocked:1; | |
unsigned goaway:1; | |
unsigned push_disabled:1; | |
+ unsigned indicate_resize:1; | |
+ | |
+#if (NGX_HTTP_V2_HPACK_ENC) | |
+ ngx_http_v2_hpack_enc_t hpack_enc; | |
+#endif | |
}; | |
@@ -198,6 +253,8 @@ struct ngx_http_v2_stream_s { | |
ngx_array_t *cookies; | |
+ size_t header_limit; | |
+ | |
ngx_pool_t *pool; | |
unsigned waiting:1; | |
@@ -410,4 +467,35 @@ u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, | |
u_char *tmp, ngx_uint_t lower); | |
+u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, | |
+ u_char *tmp, ngx_uint_t lower); | |
+ | |
+u_char * | |
+ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); | |
+ | |
+#define ngx_http_v2_write_name(dst, src, len, tmp) \ | |
+ ngx_http_v2_string_encode(dst, src, len, tmp, 1) | |
+#define ngx_http_v2_write_value(dst, src, len, tmp) \ | |
+ ngx_http_v2_string_encode(dst, src, len, tmp, 0) | |
+ | |
+u_char * | |
+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, | |
+ u_char *key, size_t key_len, u_char *value, size_t value_len, | |
+ u_char *tmp); | |
+ | |
+void | |
+ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); | |
+ | |
+#define ngx_http_v2_write_header_str(key, value) \ | |
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ | |
+ (u_char *) value, sizeof(value) - 1, tmp); | |
+ | |
+#define ngx_http_v2_write_header_tbl(key, val) \ | |
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ | |
+ val.data, val.len, tmp); | |
+ | |
+#define ngx_http_v2_write_header_pot(key, val) \ | |
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ | |
+ val->data, val->len, tmp); | |
+ | |
#endif /* _NGX_HTTP_V2_H_INCLUDED_ */ | |
diff --git a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c | |
index ac792084..d1fb7217 100644 | |
--- a/src/http/v2/ngx_http_v2_encode.c | |
+++ b/src/http/v2/ngx_http_v2_encode.c | |
@@ -10,7 +10,7 @@ | |
#include <ngx_http.h> | |
-static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, | |
+u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, | |
ngx_uint_t value); | |
@@ -40,7 +40,7 @@ ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, | |
} | |
-static u_char * | |
+u_char * | |
ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) | |
{ | |
if (value < prefix) { | |
diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c | |
index 853faefd..61e1f051 100644 | |
--- a/src/http/v2/ngx_http_v2_filter_module.c | |
+++ b/src/http/v2/ngx_http_v2_filter_module.c | |
@@ -23,10 +23,53 @@ | |
#define ngx_http_v2_literal_size(h) \ | |
(ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) | |
+#define ngx_http_v2_indexed(i) (128 + (i)) | |
+#define ngx_http_v2_inc_indexed(i) (64 + (i)) | |
+ | |
+#define NGX_HTTP_V2_ENCODE_RAW 0 | |
+#define NGX_HTTP_V2_ENCODE_HUFF 0x80 | |
+ | |
+#define NGX_HTTP_V2_AUTHORITY_INDEX 1 | |
+#define NGX_HTTP_V2_METHOD_GET_INDEX 2 | |
+#define NGX_HTTP_V2_PATH_INDEX 4 | |
+ | |
+#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 | |
+#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 | |
+ | |
+#define NGX_HTTP_V2_STATUS_INDEX 8 | |
+#define NGX_HTTP_V2_STATUS_200_INDEX 8 | |
+#define NGX_HTTP_V2_STATUS_204_INDEX 9 | |
+#define NGX_HTTP_V2_STATUS_206_INDEX 10 | |
+#define NGX_HTTP_V2_STATUS_304_INDEX 11 | |
+#define NGX_HTTP_V2_STATUS_400_INDEX 12 | |
+#define NGX_HTTP_V2_STATUS_404_INDEX 13 | |
+#define NGX_HTTP_V2_STATUS_500_INDEX 14 | |
+ | |
+#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 | |
+#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 | |
+#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 | |
+#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 | |
+#define NGX_HTTP_V2_DATE_INDEX 33 | |
+#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 | |
+#define NGX_HTTP_V2_LOCATION_INDEX 46 | |
+#define NGX_HTTP_V2_SERVER_INDEX 54 | |
+#define NGX_HTTP_V2_USER_AGENT_INDEX 58 | |
+#define NGX_HTTP_V2_VARY_INDEX 59 | |
#define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 | |
+static const struct { | |
+ u_char *name; | |
+ u_char const len; | |
+} push_header[] = { | |
+ { (u_char*)":authority" , 10 }, | |
+ { (u_char*)"accept-encoding" , 15 }, | |
+ { (u_char*)"accept-language" , 15 }, | |
+ { (u_char*)"user-agent" , 10 } | |
+}; | |
+ | |
+ | |
typedef struct { | |
ngx_str_t name; | |
u_char index; | |
@@ -155,11 +198,9 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) | |
#endif | |
static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); | |
- static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; | |
static size_t nginx_ver_build_len = | |
ngx_http_v2_literal_size(NGINX_VER_BUILD); | |
- static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; | |
stream = r->stream; | |
@@ -435,7 +476,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) | |
} | |
tmp = ngx_palloc(r->pool, tmp_len); | |
- pos = ngx_pnalloc(r->pool, len); | |
+ pos = ngx_pnalloc(r->pool, len + 15 + 1); | |
if (pos == NULL || tmp == NULL) { | |
return NGX_ERROR; | |
@@ -443,11 +484,16 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) | |
start = pos; | |
- if (h2c->table_update) { | |
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 table size update: 0"); | |
- *pos++ = (1 << 5) | 0; | |
- h2c->table_update = 0; | |
+ h2c = r->stream->connection; | |
+ | |
+ if (h2c->indicate_resize) { | |
+ *pos = 32; | |
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), | |
+ h2c->max_hpack_table_size); | |
+ h2c->indicate_resize = 0; | |
+#if (NGX_HTTP_V2_HPACK_ENC) | |
+ ngx_http_v2_table_resize(h2c); | |
+#endif | |
} | |
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
@@ -458,67 +504,28 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) | |
*pos++ = status; | |
} else { | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); | |
- *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; | |
- pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); | |
+ ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); | |
+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", | |
+ sizeof(":status") - 1, pos + 8, 3, tmp); | |
} | |
if (r->headers_out.server == NULL) { | |
- | |
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { | |
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"server: %s\"", | |
- NGINX_VER); | |
+ pos = ngx_http_v2_write_header_str("server", NGINX_VER); | |
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { | |
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"server: %s\"", | |
- NGINX_VER_BUILD); | |
+ pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); | |
} else { | |
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"server: nginx\""); | |
- } | |
- | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); | |
- | |
- if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { | |
- if (nginx_ver[0] == '\0') { | |
- p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, | |
- sizeof(NGINX_VER) - 1, tmp); | |
- nginx_ver_len = p - nginx_ver; | |
- } | |
- | |
- pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); | |
- | |
- } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { | |
- if (nginx_ver_build[0] == '\0') { | |
- p = ngx_http_v2_write_value(nginx_ver_build, | |
- (u_char *) NGINX_VER_BUILD, | |
- sizeof(NGINX_VER_BUILD) - 1, tmp); | |
- nginx_ver_build_len = p - nginx_ver_build; | |
- } | |
- | |
- pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); | |
- | |
- } else { | |
- pos = ngx_cpymem(pos, nginx, sizeof(nginx)); | |
+ pos = ngx_http_v2_write_header_str("server", "nginx"); | |
} | |
} | |
if (r->headers_out.date == NULL) { | |
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"date: %V\"", | |
- &ngx_cached_http_time); | |
- | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); | |
- pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, | |
- ngx_cached_http_time.len, tmp); | |
+ pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); | |
} | |
if (r->headers_out.content_type.len) { | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); | |
- | |
if (r->headers_out.content_type_len == r->headers_out.content_type.len | |
&& r->headers_out.charset.len) | |
{ | |
@@ -544,64 +551,36 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) | |
r->headers_out.content_type.data = p - len; | |
} | |
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"content-type: %V\"", | |
- &r->headers_out.content_type); | |
- | |
- pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, | |
- r->headers_out.content_type.len, tmp); | |
+ pos = ngx_http_v2_write_header_tbl("content-type", | |
+ r->headers_out.content_type); | |
} | |
if (r->headers_out.content_length == NULL | |
&& r->headers_out.content_length_n >= 0) | |
{ | |
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"content-length: %O\"", | |
- r->headers_out.content_length_n); | |
- | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); | |
- | |
- p = pos; | |
- pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); | |
- *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); | |
+ p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); | |
+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", | |
+ sizeof("content-length") - 1, pos + 15, | |
+ p - (pos + 15), tmp); | |
} | |
if (r->headers_out.last_modified == NULL | |
&& r->headers_out.last_modified_time != -1) | |
{ | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); | |
- | |
- ngx_http_time(pos, r->headers_out.last_modified_time); | |
+ ngx_http_time(pos + 14, r->headers_out.last_modified_time); | |
len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; | |
- | |
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"last-modified: %*s\"", | |
- len, pos); | |
- | |
- /* | |
- * Date will always be encoded using huffman in the temporary buffer, | |
- * so it's safe here to use src and dst pointing to the same address. | |
- */ | |
- pos = ngx_http_v2_write_value(pos, pos, len, tmp); | |
+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", | |
+ sizeof("last-modified") - 1, pos + 14, | |
+ len, tmp); | |
} | |
if (r->headers_out.location && r->headers_out.location->value.len) { | |
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"location: %V\"", | |
- &r->headers_out.location->value); | |
- | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); | |
- pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, | |
- r->headers_out.location->value.len, tmp); | |
+ pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); | |
} | |
#if (NGX_HTTP_GZIP) | |
if (r->gzip_vary) { | |
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"vary: Accept-Encoding\""); | |
- | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); | |
- pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); | |
+ pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); | |
} | |
#endif | |
@@ -624,23 +603,10 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) | |
continue; | |
} | |
-#if (NGX_DEBUG) | |
- if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { | |
- ngx_strlow(tmp, header[i].key.data, header[i].key.len); | |
- | |
- ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 output header: \"%*s: %V\"", | |
- header[i].key.len, tmp, &header[i].value); | |
- } | |
-#endif | |
- | |
- *pos++ = 0; | |
- | |
- pos = ngx_http_v2_write_name(pos, header[i].key.data, | |
- header[i].key.len, tmp); | |
+ pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, | |
+ header[i].key.len, header[i].value.data, | |
+ header[i].value.len, tmp); | |
- pos = ngx_http_v2_write_value(pos, header[i].value.data, | |
- header[i].value.len, tmp); | |
} | |
fin = r->header_only | |
@@ -998,6 +964,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, | |
for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { | |
len += binary[i].len; | |
+ len += push_header[i].len + 1; | |
} | |
pos = ngx_pnalloc(r->pool, len); | |
@@ -1007,12 +974,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, | |
start = pos; | |
- if (h2c->table_update) { | |
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
- "http2 table size update: 0"); | |
- *pos++ = (1 << 5) | 0; | |
- h2c->table_update = 0; | |
- } | |
+ h2c = r->stream->connection; | |
+ | |
+ if (h2c->indicate_resize) { | |
+ *pos = 32; | |
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), | |
+ h2c->max_hpack_table_size); | |
+ h2c->indicate_resize = 0; | |
+#if (NGX_HTTP_V2_HPACK_ENC) | |
+ ngx_http_v2_table_resize(h2c); | |
+#endif | |
+ } | |
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
"http2 push header: \":method: GET\""); | |
@@ -1022,8 +994,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, | |
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
"http2 push header: \":path: %V\"", path); | |
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); | |
- pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); | |
+ pos = ngx_http_v2_write_header_pot(":path", path); | |
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
"http2 push header: \":scheme: %V\"", &r->schema); | |
@@ -1048,11 +1019,15 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, | |
continue; | |
} | |
+ value = &(*h)->value; | |
+ | |
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, | |
"http2 push header: \"%V: %V\"", | |
&ph[i].name, &(*h)->value); | |
- pos = ngx_cpymem(pos, binary[i].data, binary[i].len); | |
+ pos = ngx_http_v2_write_header(h2c, pos, | |
+ push_header[i].name, push_header[i].len, value->data, value->len, | |
+ tmp); | |
} | |
frame = ngx_http_v2_create_push_frame(r, start, pos); | |
diff --git a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c | |
index 7d49803f..b9ee2048 100644 | |
--- a/src/http/v2/ngx_http_v2_table.c | |
+++ b/src/http/v2/ngx_http_v2_table.c | |
@@ -361,3 +361,434 @@ ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size) | |
return NGX_OK; | |
} | |
+ | |
+ | |
+#if (NGX_HTTP_V2_HPACK_ENC) | |
+ | |
+static ngx_int_t | |
+hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); | |
+ | |
+static ngx_int_t | |
+hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, | |
+ uint8_t *key, size_t key_len); | |
+ | |
+ | |
+void | |
+ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) | |
+{ | |
+ ngx_http_v2_hpack_enc_entry_t *table; | |
+ uint64_t idx; | |
+ | |
+ table = h2c->hpack_enc.htable; | |
+ | |
+ while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { | |
+ idx = h2c->hpack_enc.base; | |
+ h2c->hpack_enc.base = table[idx].next; | |
+ h2c->hpack_enc.size -= table[idx].size; | |
+ table[idx].hash_val = 0; | |
+ h2c->hpack_enc.n_elems--; | |
+ } | |
+} | |
+ | |
+ | |
+/* checks if a header is in the hpack table - if so returns the table entry, | |
+ otherwise encodes and inserts into the table and returns 0, | |
+ if failed to insert into table, returns -1 */ | |
+static ngx_int_t | |
+ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, | |
+ size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, | |
+ ngx_int_t *header_idx) | |
+{ | |
+ uint64_t hash_val, key_hash, idx, lru; | |
+ int i; | |
+ size_t size = key_len + val_len + 32; | |
+ uint8_t *storage = h2c->hpack_enc.storage; | |
+ | |
+ ngx_http_v2_hpack_enc_entry_t *table; | |
+ ngx_http_v2_hpack_name_entry_t *name; | |
+ | |
+ *header_idx = NGX_ERROR; | |
+ /* step 1: compute the hash value of header */ | |
+ if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { | |
+ return NGX_ERROR; | |
+ } | |
+ | |
+ key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); | |
+ hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); | |
+ | |
+ if (hash_val == 0) { | |
+ return NGX_ERROR; | |
+ } | |
+ | |
+ /* step 2: check if full header in the table */ | |
+ idx = hash_val; | |
+ i = -1; | |
+ while (idx) { | |
+ /* at most 8 locations are checked, but most will be done in 1 or 2 */ | |
+ table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; | |
+ if (table->hash_val == hash_val | |
+ && table->klen == key_len | |
+ && table->vlen == val_len | |
+ && ngx_memcmp(key, storage + table->pos, key_len) == 0 | |
+ && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) | |
+ { | |
+ return (h2c->hpack_enc.top - table->index) + 61; | |
+ } | |
+ | |
+ if (table->hash_val == 0 && i == -1) { | |
+ i = idx % HPACK_ENC_HTABLE_SZ; | |
+ break; | |
+ } | |
+ | |
+ idx >>= 8; | |
+ } | |
+ | |
+ /* step 3: check if key is in one of the tables */ | |
+ *header_idx = hpack_get_static_index(h2c, key, key_len); | |
+ | |
+ if (i == -1) { | |
+ return NGX_ERROR; | |
+ } | |
+ | |
+ if (*header_idx == NGX_ERROR) { | |
+ *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); | |
+ } | |
+ | |
+ /* step 4: store the new entry */ | |
+ table = h2c->hpack_enc.htable; | |
+ | |
+ if (h2c->hpack_enc.top == 0xffffffff) { | |
+ /* just to be on the safe side, avoid overflow */ | |
+ ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); | |
+ } | |
+ | |
+ while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) | |
+ || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { | |
+ /* make space for the new entry first */ | |
+ idx = h2c->hpack_enc.base; | |
+ h2c->hpack_enc.base = table[idx].next; | |
+ h2c->hpack_enc.size -= table[idx].size; | |
+ table[idx].hash_val = 0; | |
+ h2c->hpack_enc.n_elems--; | |
+ } | |
+ | |
+ table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, | |
+ .index = h2c->hpack_enc.top, | |
+ .pos = h2c->hpack_enc.pos, | |
+ .klen = key_len, | |
+ .vlen = val_len, | |
+ .size = size, | |
+ .next = 0}; | |
+ | |
+ table[h2c->hpack_enc.last].next = i; | |
+ if (h2c->hpack_enc.n_elems == 0) { | |
+ h2c->hpack_enc.base = i; | |
+ } | |
+ | |
+ h2c->hpack_enc.last = i; | |
+ h2c->hpack_enc.top++; | |
+ h2c->hpack_enc.size += size; | |
+ h2c->hpack_enc.n_elems++; | |
+ | |
+ /* update header name lookup */ | |
+ if (*header_idx == NGX_ERROR ) { | |
+ lru = h2c->hpack_enc.top; | |
+ | |
+ for (i=0; i<HPACK_ENC_DYNAMIC_KEY_TBL_SZ; i++) { | |
+ | |
+ name = &h2c->hpack_enc.heads[i]; | |
+ | |
+ if ( name->hash_val == 0 || (name->hash_val == key_hash | |
+ && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) | |
+ { | |
+ name->hash_val = key_hash; | |
+ name->pos = h2c->hpack_enc.pos; | |
+ name->index = h2c->hpack_enc.top - 1; | |
+ break; | |
+ } | |
+ | |
+ if (lru > name->index) { | |
+ lru = name->index; | |
+ idx = i; | |
+ } | |
+ } | |
+ | |
+ if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { | |
+ name = &h2c->hpack_enc.heads[idx]; | |
+ name->hash_val = hash_val; | |
+ name->pos = h2c->hpack_enc.pos; | |
+ name->index = h2c->hpack_enc.top - 1; | |
+ } | |
+ } | |
+ | |
+ ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); | |
+ ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); | |
+ | |
+ h2c->hpack_enc.pos += size; | |
+ if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { | |
+ h2c->hpack_enc.pos = 0; | |
+ } | |
+ | |
+ return NGX_OK; | |
+} | |
+ | |
+ | |
+u_char * | |
+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, | |
+ u_char *key, size_t key_len, | |
+ u_char *value, size_t value_len, | |
+ u_char *tmp) | |
+{ | |
+ ngx_int_t idx, header_idx; | |
+ | |
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 output header: %*s: %*s", key_len, key, value_len, | |
+ value); | |
+ | |
+ /* attempt to find the value in the dynamic table */ | |
+ idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, | |
+ &header_idx); | |
+ | |
+ if (idx > 0) { | |
+ /* positive index indicates success */ | |
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 hpack encode: Indexed Header Field: %ud", idx); | |
+ | |
+ *pos = 128; | |
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); | |
+ | |
+ } else { | |
+ | |
+ if (header_idx == NGX_ERROR) { /* if key is not present */ | |
+ | |
+ if (idx == NGX_ERROR) { /* if header was not added */ | |
+ *pos++ = 0; | |
+ | |
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 hpack encode: Literal Header Field without" | |
+ " Indexing — New Name"); | |
+ } else { /* if header was added */ | |
+ *pos++ = 64; | |
+ | |
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 hpack encode: Literal Header Field with " | |
+ "Incremental Indexing — New Name"); | |
+ } | |
+ | |
+ pos = ngx_http_v2_write_name(pos, key, key_len, tmp); | |
+ | |
+ } else { /* if key is present */ | |
+ | |
+ if (idx == NGX_ERROR) { | |
+ *pos = 0; | |
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); | |
+ | |
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 hpack encode: Literal Header Field without" | |
+ " Indexing — Indexed Name: %ud", header_idx); | |
+ } else { | |
+ *pos = 64; | |
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); | |
+ | |
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 hpack encode: Literal Header Field with " | |
+ "Incremental Indexing — Indexed Name: %ud", header_idx); | |
+ } | |
+ } | |
+ | |
+ pos = ngx_http_v2_write_value(pos, value, value_len, tmp); | |
+ } | |
+ | |
+ return pos; | |
+} | |
+ | |
+ | |
+static ngx_int_t | |
+hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, | |
+ uint8_t *key, size_t key_len) | |
+{ | |
+ ngx_http_v2_hpack_name_entry_t *name; | |
+ int i; | |
+ | |
+ for (i=0; i<HPACK_ENC_DYNAMIC_KEY_TBL_SZ; i++) { | |
+ name = &h2c->hpack_enc.heads[i]; | |
+ | |
+ if (name->hash_val == key_hash | |
+ && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) | |
+ { | |
+ if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { | |
+ return (h2c->hpack_enc.top - name->index) + 61; | |
+ } | |
+ break; | |
+ } | |
+ } | |
+ | |
+ return NGX_ERROR; | |
+} | |
+ | |
+ | |
+/* decide if a given header is present in the static dictionary, this could be | |
+ done in several ways, but it seems the fastest one is "exhaustive" search */ | |
+static ngx_int_t | |
+hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) | |
+{ | |
+ /* the static dictionary of response only headers, | |
+ although response headers can be put by origin, | |
+ that would be rare */ | |
+ static const struct { | |
+ u_char len; | |
+ const u_char val[28]; | |
+ u_char idx; | |
+ } server_headers[] = { | |
+ { 3, "age", 21},//0 | |
+ { 3, "via", 60}, | |
+ { 4, "date", 33},//2 | |
+ { 4, "etag", 34}, | |
+ { 4, "link", 45}, | |
+ { 4, "vary", 59}, | |
+ { 5, "allow", 22},//6 | |
+ { 6, "server", 54},//7 | |
+ { 7, "expires", 36},//8 | |
+ { 7, "refresh", 52}, | |
+ { 8, "location", 46},//10 | |
+ {10, "set-cookie", 55},//11 | |
+ {11, "retry-after", 53},//12 | |
+ {12, "content-type", 31},//13 | |
+ {13, "content-range", 30},//14 | |
+ {13, "accept-ranges", 18}, | |
+ {13, "cache-control", 24}, | |
+ {13, "last-modified", 44}, | |
+ {14, "content-length", 28},//18 | |
+ {16, "content-encoding", 26},//19 | |
+ {16, "content-language", 27}, | |
+ {16, "content-location", 29}, | |
+ {16, "www-authenticate", 61}, | |
+ {17, "transfer-encoding", 57},//23 | |
+ {18, "proxy-authenticate", 48},//24 | |
+ {19, "content-disposition", 25},//25 | |
+ {25, "strict-transport-security", 56},//26 | |
+ {27, "access-control-allow-origin", 20},//27 | |
+ {99, "", 99}, | |
+ }, *header; | |
+ | |
+ /* for a given length, where to start the search | |
+ since minimal length is 3, the table has a -3 | |
+ offset */ | |
+ static const int8_t start_at[] = { | |
+ [3-3] = 0, | |
+ [4-3] = 2, | |
+ [5-3] = 6, | |
+ [6-3] = 7, | |
+ [7-3] = 8, | |
+ [8-3] = 10, | |
+ [9-3] = -1, | |
+ [10-3] = 11, | |
+ [11-3] = 12, | |
+ [12-3] = 13, | |
+ [13-3] = 14, | |
+ [14-3] = 18, | |
+ [15-3] = -1, | |
+ [16-3] = 19, | |
+ [17-3] = 23, | |
+ [18-3] = 24, | |
+ [19-3] = 25, | |
+ [20-3] = -1, | |
+ [21-3] = -1, | |
+ [22-3] = -1, | |
+ [23-3] = -1, | |
+ [24-3] = -1, | |
+ [25-3] = 26, | |
+ [26-3] = -1, | |
+ [27-3] = 27, | |
+ }; | |
+ | |
+ uint64_t pref; | |
+ size_t save_len = len, i; | |
+ int8_t start; | |
+ | |
+ /* early exit for out of bounds lengths */ | |
+ if (len < 3 || len > 27) { | |
+ return NGX_ERROR; | |
+ } | |
+ | |
+ start = start_at[len - 3]; | |
+ if (start == -1) { | |
+ /* exit for non existent lengths */ | |
+ return NGX_ERROR; | |
+ } | |
+ | |
+ header = &server_headers[start_at[len - 3]]; | |
+ | |
+ /* load first 8 bytes of key, for fast comparison */ | |
+ if (len < 8) { | |
+ pref = 0; | |
+ if (len >= 4) { | |
+ pref = *(uint32_t *)(val + len - 4) | 0x20202020; | |
+ len -= 4; | |
+ } | |
+ while (len > 0) { /* 3 iterations at most */ | |
+ pref = (pref << 8) ^ (val[len - 1] | 0x20); | |
+ len--; | |
+ } | |
+ } else { | |
+ pref = *(uint64_t *)val | 0x2020202020202020; | |
+ len -= 8; | |
+ } | |
+ | |
+ /* iterate over headers with the right length */ | |
+ while (header->len == save_len) { | |
+ /* quickly compare the first 8 bytes, most tests will end here */ | |
+ if (pref != *(uint64_t *) header->val) { | |
+ header++; | |
+ continue; | |
+ } | |
+ | |
+ if (len == 0) { | |
+ /* len == 0, indicates prefix held the entire key */ | |
+ return header->idx; | |
+ } | |
+ /* for longer keys compare the rest */ | |
+ i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ | |
+ | |
+ while (i + 8 <= save_len) { /* 3 iterations at most */ | |
+ if ( *(uint64_t *)&header->val[i] | |
+ != (*(uint64_t *) &val[i]| 0x2020202020202020) ) | |
+ { | |
+ header++; | |
+ i = 0; | |
+ break; | |
+ } | |
+ i += 8; | |
+ } | |
+ | |
+ if (i == 0) { | |
+ continue; | |
+ } | |
+ | |
+ /* found the corresponding entry in the static dictionary */ | |
+ return header->idx; | |
+ } | |
+ | |
+ return NGX_ERROR; | |
+} | |
+ | |
+#else | |
+ | |
+u_char * | |
+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, | |
+ u_char *key, size_t key_len, | |
+ u_char *value, size_t value_len, | |
+ u_char *tmp) | |
+{ | |
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
+ "http2 output header: %*s: %*s", key_len, key, value_len, | |
+ value); | |
+ | |
+ *pos++ = 64; | |
+ pos = ngx_http_v2_write_name(pos, key, key_len, tmp); | |
+ pos = ngx_http_v2_write_value(pos, value, value_len, tmp); | |
+ | |
+ return pos; | |
+} | |
+ | |
+#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment