aboutsummaryrefslogtreecommitdiffstats
path: root/app/nginx/src/http/v2/ngx_http_v2_filter_module.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/nginx/src/http/v2/ngx_http_v2_filter_module.c')
-rw-r--r--app/nginx/src/http/v2/ngx_http_v2_filter_module.c1448
1 files changed, 1448 insertions, 0 deletions
diff --git a/app/nginx/src/http/v2/ngx_http_v2_filter_module.c b/app/nginx/src/http/v2/ngx_http_v2_filter_module.c
new file mode 100644
index 0000000..dac5046
--- /dev/null
+++ b/app/nginx/src/http/v2/ngx_http_v2_filter_module.c
@@ -0,0 +1,1448 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+#include <ngx_http_v2_module.h>
+
+
+/*
+ * This returns precise number of octets for values in range 0..253
+ * and estimate number for the rest, but not smaller than required.
+ */
+
+#define ngx_http_v2_integer_octets(v) (1 + (v) / 127)
+
+#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_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)
+
+#define NGX_HTTP_V2_ENCODE_RAW 0
+#define NGX_HTTP_V2_ENCODE_HUFF 0x80
+
+#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_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_VARY_INDEX 59
+
+
+static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
+ u_char *tmp, ngx_uint_t lower);
+static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
+ ngx_uint_t value);
+static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
+ ngx_http_request_t *r, u_char *pos, u_char *end);
+
+static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc,
+ ngx_chain_t *in, off_t limit);
+
+static ngx_chain_t *ngx_http_v2_filter_get_shadow(
+ ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size);
+static ngx_http_v2_out_frame_t *ngx_http_v2_filter_get_data_frame(
+ ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first,
+ ngx_chain_t *last);
+
+static ngx_inline ngx_int_t ngx_http_v2_flow_control(
+ ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream);
+static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
+ ngx_http_v2_stream_t *stream);
+
+static ngx_inline ngx_int_t ngx_http_v2_filter_send(
+ ngx_connection_t *fc, ngx_http_v2_stream_t *stream);
+
+static ngx_int_t ngx_http_v2_headers_frame_handler(
+ ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
+static ngx_int_t ngx_http_v2_data_frame_handler(
+ ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
+static ngx_inline void ngx_http_v2_handle_frame(
+ ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame);
+static ngx_inline void ngx_http_v2_handle_stream(
+ ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream);
+
+static void ngx_http_v2_filter_cleanup(void *data);
+
+static ngx_int_t ngx_http_v2_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_v2_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_v2_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_v2_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_v2_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+
+
+static ngx_int_t
+ngx_http_v2_header_filter(ngx_http_request_t *r)
+{
+ u_char status, *pos, *start, *p, *tmp;
+ size_t len, tmp_len;
+ ngx_str_t host, location;
+ ngx_uint_t i, port;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *fc;
+ ngx_http_cleanup_t *cln;
+ ngx_http_v2_out_frame_t *frame;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+ u_char addr[NGX_SOCKADDR_STRLEN];
+
+ static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7";
+#if (NGX_HTTP_GZIP)
+ static const u_char accept_encoding[12] =
+ "\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f";
+#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)];
+
+ if (!r->stream) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http2 header filter");
+
+ if (r->header_sent) {
+ return NGX_OK;
+ }
+
+ r->header_sent = 1;
+
+ if (r != r->main) {
+ return NGX_OK;
+ }
+
+ fc = r->connection;
+
+ if (fc->error) {
+ return NGX_ERROR;
+ }
+
+ if (r->method == NGX_HTTP_HEAD) {
+ r->header_only = 1;
+ }
+
+ switch (r->headers_out.status) {
+
+ case NGX_HTTP_OK:
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_200_INDEX);
+ break;
+
+ case NGX_HTTP_NO_CONTENT:
+ r->header_only = 1;
+
+ ngx_str_null(&r->headers_out.content_type);
+
+ r->headers_out.content_length = NULL;
+ r->headers_out.content_length_n = -1;
+
+ r->headers_out.last_modified_time = -1;
+ r->headers_out.last_modified = NULL;
+
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_204_INDEX);
+ break;
+
+ case NGX_HTTP_PARTIAL_CONTENT:
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_206_INDEX);
+ break;
+
+ case NGX_HTTP_NOT_MODIFIED:
+ r->header_only = 1;
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_304_INDEX);
+ break;
+
+ default:
+ r->headers_out.last_modified_time = -1;
+ r->headers_out.last_modified = NULL;
+
+ switch (r->headers_out.status) {
+
+ case NGX_HTTP_BAD_REQUEST:
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_400_INDEX);
+ break;
+
+ case NGX_HTTP_NOT_FOUND:
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_404_INDEX);
+ break;
+
+ case NGX_HTTP_INTERNAL_SERVER_ERROR:
+ status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_500_INDEX);
+ break;
+
+ default:
+ status = 0;
+ }
+ }
+
+ len = status ? 1 : 1 + ngx_http_v2_literal_size("418");
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_out.server == NULL) {
+
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ len += 1 + nginx_ver_len;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ len += 1 + nginx_ver_build_len;
+
+ } else {
+ len += 1 + sizeof(nginx);
+ }
+ }
+
+ if (r->headers_out.date == NULL) {
+ len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
+ }
+
+ if (r->headers_out.content_type.len) {
+ len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.content_type.len;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+ }
+
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+ len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN;
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
+ }
+
+ if (r->headers_out.location && r->headers_out.location->value.len) {
+
+ if (r->headers_out.location->value.data[0] == '/'
+ && clcf->absolute_redirect)
+ {
+ if (clcf->server_name_in_redirect) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ host = cscf->server_name;
+
+ } else if (r->headers_in.server.len) {
+ host = r->headers_in.server;
+
+ } else {
+ host.len = NGX_SOCKADDR_STRLEN;
+ host.data = addr;
+
+ if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ port = ngx_inet_get_port(fc->local_sockaddr);
+
+ location.len = sizeof("https://") - 1 + host.len
+ + r->headers_out.location->value.len;
+
+ if (clcf->port_in_redirect) {
+
+#if (NGX_HTTP_SSL)
+ if (fc->ssl)
+ port = (port == 443) ? 0 : port;
+ else
+#endif
+ port = (port == 80) ? 0 : port;
+
+ } else {
+ port = 0;
+ }
+
+ if (port) {
+ location.len += sizeof(":65535") - 1;
+ }
+
+ location.data = ngx_pnalloc(r->pool, location.len);
+ if (location.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(location.data, "http", sizeof("http") - 1);
+
+#if (NGX_HTTP_SSL)
+ if (fc->ssl) {
+ *p++ = 's';
+ }
+#endif
+
+ *p++ = ':'; *p++ = '/'; *p++ = '/';
+ p = ngx_cpymem(p, host.data, host.len);
+
+ if (port) {
+ p = ngx_sprintf(p, ":%ui", port);
+ }
+
+ p = ngx_cpymem(p, r->headers_out.location->value.data,
+ r->headers_out.location->value.len);
+
+ /* update r->headers_out.location->value for possible logging */
+
+ r->headers_out.location->value.len = p - location.data;
+ r->headers_out.location->value.data = location.data;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ }
+
+ r->headers_out.location->hash = 0;
+
+ len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len;
+ }
+
+ tmp_len = len;
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ if (clcf->gzip_vary) {
+ len += 1 + sizeof(accept_encoding);
+
+ } else {
+ r->gzip_vary = 0;
+ }
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
+ ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+ "too long response header name: \"%V\"",
+ &header[i].key);
+ return NGX_ERROR;
+ }
+
+ if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
+ ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+ "too long response header value: \"%V: %V\"",
+ &header[i].key, &header[i].value);
+ return NGX_ERROR;
+ }
+
+ len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
+ + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
+
+ if (header[i].key.len > tmp_len) {
+ tmp_len = header[i].key.len;
+ }
+
+ if (header[i].value.len > tmp_len) {
+ tmp_len = header[i].value.len;
+ }
+ }
+
+ tmp = ngx_palloc(r->pool, tmp_len);
+ pos = ngx_pnalloc(r->pool, len);
+
+ if (pos == NULL || tmp == NULL) {
+ return NGX_ERROR;
+ }
+
+ start = pos;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+ "http2 output header: \":status: %03ui\"",
+ r->headers_out.status);
+
+ if (status) {
+ *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);
+ }
+
+ 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);
+
+ } 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);
+
+ } 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));
+ }
+ }
+
+ 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);
+ }
+
+ 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)
+ {
+ len = r->headers_out.content_type.len + sizeof("; charset=") - 1
+ + r->headers_out.charset.len;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(p, r->headers_out.content_type.data,
+ r->headers_out.content_type.len);
+
+ p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
+
+ p = ngx_cpymem(p, r->headers_out.charset.data,
+ r->headers_out.charset.len);
+
+ /* updated r->headers_out.content_type is also needed for logging */
+
+ r->headers_out.content_type.len = len;
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ 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);
+ }
+
+ 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);
+ }
+
+#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));
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ 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_value(pos, header[i].value.data,
+ header[i].value.len, tmp);
+ }
+
+ frame = ngx_http_v2_create_headers_frame(r, start, pos);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_v2_queue_blocked_frame(r->stream->connection, frame);
+
+ cln = ngx_http_cleanup_add(r, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_v2_filter_cleanup;
+ cln->data = r->stream;
+
+ r->stream->queued = 1;
+
+ fc->send_chain = ngx_http_v2_send_chain;
+ fc->need_last_buf = 1;
+
+ return ngx_http_v2_filter_send(fc, r->stream);
+}
+
+
+static u_char *
+ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
+ ngx_uint_t lower)
+{
+ size_t hlen;
+
+ hlen = ngx_http_v2_huff_encode(src, len, tmp, lower);
+
+ if (hlen > 0) {
+ *dst = NGX_HTTP_V2_ENCODE_HUFF;
+ dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen);
+ return ngx_cpymem(dst, tmp, hlen);
+ }
+
+ *dst = NGX_HTTP_V2_ENCODE_RAW;
+ dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len);
+
+ if (lower) {
+ ngx_strlow(dst, src, len);
+ return dst + len;
+ }
+
+ return ngx_cpymem(dst, src, len);
+}
+
+
+static u_char *
+ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
+{
+ if (value < prefix) {
+ *pos++ |= value;
+ return pos;
+ }
+
+ *pos++ |= prefix;
+ value -= prefix;
+
+ while (value >= 128) {
+ *pos++ = value % 128 + 128;
+ value /= 128;
+ }
+
+ *pos++ = (u_char) value;
+
+ return pos;
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
+ u_char *end)
+{
+ u_char type, flags;
+ size_t rest, frame_size;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, **ll;
+ ngx_http_v2_stream_t *stream;
+ ngx_http_v2_out_frame_t *frame;
+
+ stream = r->stream;
+ rest = end - pos;
+
+ frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
+ if (frame == NULL) {
+ return NULL;
+ }
+
+ frame->handler = ngx_http_v2_headers_frame_handler;
+ frame->stream = stream;
+ frame->length = rest;
+ frame->blocked = 1;
+ frame->fin = r->header_only;
+
+ ll = &frame->first;
+
+ type = NGX_HTTP_V2_HEADERS_FRAME;
+ flags = r->header_only ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG;
+ frame_size = stream->connection->frame_size;
+
+ for ( ;; ) {
+ if (rest <= frame_size) {
+ frame_size = rest;
+ flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
+ }
+
+ b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type);
+ *b->last++ = flags;
+ b->last = ngx_http_v2_write_sid(b->last, stream->node->id);
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+
+ *ll = cl;
+ ll = &cl->next;
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->pos = pos;
+
+ pos += frame_size;
+
+ b->last = pos;
+ b->start = b->pos;
+ b->end = b->last;
+ b->temporary = 1;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+
+ *ll = cl;
+ ll = &cl->next;
+
+ rest -= frame_size;
+
+ if (rest) {
+ frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+ type = NGX_HTTP_V2_CONTINUATION_FRAME;
+ flags = NGX_HTTP_V2_NO_FLAG;
+ continue;
+ }
+
+ b->last_buf = r->header_only;
+ cl->next = NULL;
+ frame->last = cl;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http2:%ui create HEADERS frame %p: len:%uz",
+ stream->node->id, frame, frame->length);
+
+ return frame;
+ }
+}
+
+
+static ngx_chain_t *
+ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
+{
+ off_t size, offset;
+ size_t rest, frame_size;
+ ngx_chain_t *cl, *out, **ln;
+ ngx_http_request_t *r;
+ ngx_http_v2_stream_t *stream;
+ ngx_http_v2_loc_conf_t *h2lcf;
+ ngx_http_v2_out_frame_t *frame;
+ ngx_http_v2_connection_t *h2c;
+
+ r = fc->data;
+ stream = r->stream;
+
+#if (NGX_SUPPRESS_WARN)
+ size = 0;
+#endif
+
+ while (in) {
+ size = ngx_buf_size(in->buf);
+
+ if (size || in->buf->last_buf) {
+ break;
+ }
+
+ in = in->next;
+ }
+
+ if (in == NULL) {
+
+ if (stream->queued) {
+ fc->write->active = 1;
+ fc->write->ready = 0;
+
+ } else {
+ fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
+ }
+
+ return NULL;
+ }
+
+ h2c = stream->connection;
+
+ if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
+ fc->write->active = 1;
+ fc->write->ready = 0;
+ return in;
+ }
+
+ if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ cl->buf = in->buf;
+ in->buf = cl->buf->shadow;
+
+ offset = ngx_buf_in_memory(in->buf)
+ ? (cl->buf->pos - in->buf->pos)
+ : (cl->buf->file_pos - in->buf->file_pos);
+
+ cl->next = stream->free_bufs;
+ stream->free_bufs = cl;
+
+ } else {
+ offset = 0;
+ }
+
+ if (limit == 0 || limit > (off_t) h2c->send_window) {
+ limit = h2c->send_window;
+ }
+
+ if (limit > stream->send_window) {
+ limit = (stream->send_window > 0) ? stream->send_window : 0;
+ }
+
+ h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
+
+ frame_size = (h2lcf->chunk_size < h2c->frame_size)
+ ? h2lcf->chunk_size : h2c->frame_size;
+
+#if (NGX_SUPPRESS_WARN)
+ cl = NULL;
+#endif
+
+ for ( ;; ) {
+ if ((off_t) frame_size > limit) {
+ frame_size = (size_t) limit;
+ }
+
+ ln = &out;
+ rest = frame_size;
+
+ while ((off_t) rest >= size) {
+
+ if (offset) {
+ cl = ngx_http_v2_filter_get_shadow(stream, in->buf,
+ offset, size);
+ if (cl == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ offset = 0;
+
+ } else {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ cl->buf = in->buf;
+ }
+
+ *ln = cl;
+ ln = &cl->next;
+
+ rest -= (size_t) size;
+ in = in->next;
+
+ if (in == NULL) {
+ frame_size -= rest;
+ rest = 0;
+ break;
+ }
+
+ size = ngx_buf_size(in->buf);
+ }
+
+ if (rest) {
+ cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, rest);
+ if (cl == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ cl->buf->flush = 0;
+ cl->buf->last_buf = 0;
+
+ *ln = cl;
+
+ offset += rest;
+ size -= rest;
+ }
+
+ frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl);
+ if (frame == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_http_v2_queue_frame(h2c, frame);
+
+ h2c->send_window -= frame_size;
+
+ stream->send_window -= frame_size;
+ stream->queued++;
+
+ if (in == NULL) {
+ break;
+ }
+
+ limit -= frame_size;
+
+ if (limit == 0) {
+ break;
+ }
+ }
+
+ if (offset) {
+ cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size);
+ if (cl == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ in->buf = cl->buf;
+ ngx_free_chain(r->pool, cl);
+ }
+
+ if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ if (in && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
+ fc->write->active = 1;
+ fc->write->ready = 0;
+ }
+
+ return in;
+}
+
+
+static ngx_chain_t *
+ngx_http_v2_filter_get_shadow(ngx_http_v2_stream_t *stream, ngx_buf_t *buf,
+ off_t offset, off_t size)
+{
+ ngx_buf_t *chunk;
+ ngx_chain_t *cl;
+
+ cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ chunk = cl->buf;
+
+ ngx_memcpy(chunk, buf, sizeof(ngx_buf_t));
+
+ chunk->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow;
+ chunk->shadow = buf;
+
+ if (ngx_buf_in_memory(chunk)) {
+ chunk->pos += offset;
+ chunk->last = chunk->pos + size;
+ }
+
+ if (chunk->in_file) {
+ chunk->file_pos += offset;
+ chunk->file_last = chunk->file_pos + size;
+ }
+
+ return cl;
+}
+
+
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_filter_get_data_frame(ngx_http_v2_stream_t *stream,
+ size_t len, ngx_chain_t *first, ngx_chain_t *last)
+{
+ u_char flags;
+ ngx_buf_t *buf;
+ ngx_chain_t *cl;
+ ngx_http_v2_out_frame_t *frame;
+
+ frame = stream->free_frames;
+
+ if (frame) {
+ stream->free_frames = frame->next;
+
+ } else {
+ frame = ngx_palloc(stream->request->pool,
+ sizeof(ngx_http_v2_out_frame_t));
+ if (frame == NULL) {
+ return NULL;
+ }
+ }
+
+ flags = last->buf->last_buf ? NGX_HTTP_V2_END_STREAM_FLAG : 0;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0,
+ "http2:%ui create DATA frame %p: len:%uz flags:%ui",
+ stream->node->id, frame, len, (ngx_uint_t) flags);
+
+ cl = ngx_chain_get_free_buf(stream->request->pool,
+ &stream->free_frame_headers);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ buf = cl->buf;
+
+ if (buf->start == NULL) {
+ buf->start = ngx_palloc(stream->request->pool,
+ NGX_HTTP_V2_FRAME_HEADER_SIZE);
+ if (buf->start == NULL) {
+ return NULL;
+ }
+
+ buf->end = buf->start + NGX_HTTP_V2_FRAME_HEADER_SIZE;
+ buf->last = buf->end;
+
+ buf->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
+ buf->memory = 1;
+ }
+
+ buf->pos = buf->start;
+ buf->last = buf->pos;
+
+ buf->last = ngx_http_v2_write_len_and_type(buf->last, len,
+ NGX_HTTP_V2_DATA_FRAME);
+ *buf->last++ = flags;
+
+ buf->last = ngx_http_v2_write_sid(buf->last, stream->node->id);
+
+ cl->next = first;
+ first = cl;
+
+ last->buf->flush = 1;
+
+ frame->first = first;
+ frame->last = last;
+ frame->handler = ngx_http_v2_data_frame_handler;
+ frame->stream = stream;
+ frame->length = len;
+ frame->blocked = 0;
+ frame->fin = last->buf->last_buf;
+
+ return frame;
+}
+
+
+static ngx_inline ngx_int_t
+ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream)
+{
+ stream->blocked = 1;
+
+ if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) {
+ fc->error = 1;
+ return NGX_ERROR;
+ }
+
+ stream->blocked = 0;
+
+ if (stream->queued) {
+ fc->buffered |= NGX_HTTP_V2_BUFFERED;
+ fc->write->active = 1;
+ fc->write->ready = 0;
+ return NGX_AGAIN;
+ }
+
+ fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
+
+ return NGX_OK;
+}
+
+
+static ngx_inline ngx_int_t
+ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c,
+ ngx_http_v2_stream_t *stream)
+{
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2:%ui available windows: conn:%uz stream:%z",
+ stream->node->id, h2c->send_window, stream->send_window);
+
+ if (stream->send_window <= 0) {
+ stream->exhausted = 1;
+ return NGX_DECLINED;
+ }
+
+ if (h2c->send_window == 0) {
+ ngx_http_v2_waiting_queue(h2c, stream);
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
+ ngx_http_v2_stream_t *stream)
+{
+ ngx_queue_t *q;
+ ngx_http_v2_stream_t *s;
+
+ if (stream->waiting) {
+ return;
+ }
+
+ stream->waiting = 1;
+
+ for (q = ngx_queue_last(&h2c->waiting);
+ q != ngx_queue_sentinel(&h2c->waiting);
+ q = ngx_queue_prev(q))
+ {
+ s = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
+
+ if (s->node->rank < stream->node->rank
+ || (s->node->rank == stream->node->rank
+ && s->node->rel_weight >= stream->node->rel_weight))
+ {
+ break;
+ }
+ }
+
+ ngx_queue_insert_after(q, &stream->queue);
+}
+
+
+
+static ngx_int_t
+ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c,
+ ngx_http_v2_out_frame_t *frame)
+{
+ ngx_chain_t *cl, *ln;
+ ngx_http_v2_stream_t *stream;
+
+ stream = frame->stream;
+ cl = frame->first;
+
+ for ( ;; ) {
+ if (cl->buf->pos != cl->buf->last) {
+ frame->first = cl;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2:%ui HEADERS frame %p was sent partially",
+ stream->node->id, frame);
+
+ return NGX_AGAIN;
+ }
+
+ ln = cl->next;
+
+ if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
+ cl->next = stream->free_frame_headers;
+ stream->free_frame_headers = cl;
+
+ } else {
+ cl->next = stream->free_bufs;
+ stream->free_bufs = cl;
+ }
+
+ if (cl == frame->last) {
+ break;
+ }
+
+ cl = ln;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2:%ui HEADERS frame %p was sent",
+ stream->node->id, frame);
+
+ stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE
+ + frame->length;
+
+ ngx_http_v2_handle_frame(stream, frame);
+
+ ngx_http_v2_handle_stream(h2c, stream);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c,
+ ngx_http_v2_out_frame_t *frame)
+{
+ ngx_buf_t *buf;
+ ngx_chain_t *cl, *ln;
+ ngx_http_v2_stream_t *stream;
+
+ stream = frame->stream;
+ cl = frame->first;
+
+ if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
+
+ if (cl->buf->pos != cl->buf->last) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2:%ui DATA frame %p was sent partially",
+ stream->node->id, frame);
+
+ return NGX_AGAIN;
+ }
+
+ ln = cl->next;
+
+ cl->next = stream->free_frame_headers;
+ stream->free_frame_headers = cl;
+
+ if (cl == frame->last) {
+ goto done;
+ }
+
+ cl = ln;
+ }
+
+ for ( ;; ) {
+ if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
+ buf = cl->buf->shadow;
+
+ if (ngx_buf_in_memory(buf)) {
+ buf->pos = cl->buf->pos;
+ }
+
+ if (buf->in_file) {
+ buf->file_pos = cl->buf->file_pos;
+ }
+ }
+
+ if (ngx_buf_size(cl->buf) != 0) {
+
+ if (cl != frame->first) {
+ frame->first = cl;
+ ngx_http_v2_handle_stream(h2c, stream);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2:%ui DATA frame %p was sent partially",
+ stream->node->id, frame);
+
+ return NGX_AGAIN;
+ }
+
+ ln = cl->next;
+
+ if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
+ cl->next = stream->free_bufs;
+ stream->free_bufs = cl;
+
+ } else {
+ ngx_free_chain(stream->request->pool, cl);
+ }
+
+ if (cl == frame->last) {
+ goto done;
+ }
+
+ cl = ln;
+ }
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2:%ui DATA frame %p was sent",
+ stream->node->id, frame);
+
+ stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+ ngx_http_v2_handle_frame(stream, frame);
+
+ ngx_http_v2_handle_stream(h2c, stream);
+
+ return NGX_OK;
+}
+
+
+static ngx_inline void
+ngx_http_v2_handle_frame(ngx_http_v2_stream_t *stream,
+ ngx_http_v2_out_frame_t *frame)
+{
+ ngx_http_request_t *r;
+
+ r = stream->request;
+
+ r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length;
+
+ if (frame->fin) {
+ stream->out_closed = 1;
+ }
+
+ frame->next = stream->free_frames;
+ stream->free_frames = frame;
+
+ stream->queued--;
+}
+
+
+static ngx_inline void
+ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c,
+ ngx_http_v2_stream_t *stream)
+{
+ ngx_event_t *wev;
+ ngx_connection_t *fc;
+
+ if (stream->waiting || stream->blocked) {
+ return;
+ }
+
+ fc = stream->request->connection;
+
+ if (!fc->error && stream->exhausted) {
+ return;
+ }
+
+ wev = fc->write;
+
+ wev->active = 0;
+ wev->ready = 1;
+
+ if (!fc->error && wev->delayed) {
+ return;
+ }
+
+ ngx_post_event(wev, &ngx_posted_events);
+}
+
+
+static void
+ngx_http_v2_filter_cleanup(void *data)
+{
+ ngx_http_v2_stream_t *stream = data;
+
+ size_t window;
+ ngx_event_t *wev;
+ ngx_queue_t *q;
+ ngx_http_v2_out_frame_t *frame, **fn;
+ ngx_http_v2_connection_t *h2c;
+
+ if (stream->waiting) {
+ stream->waiting = 0;
+ ngx_queue_remove(&stream->queue);
+ }
+
+ if (stream->queued == 0) {
+ return;
+ }
+
+ window = 0;
+ h2c = stream->connection;
+ fn = &h2c->last_out;
+
+ for ( ;; ) {
+ frame = *fn;
+
+ if (frame == NULL) {
+ break;
+ }
+
+ if (frame->stream == stream && !frame->blocked) {
+ *fn = frame->next;
+
+ window += frame->length;
+
+ if (--stream->queued == 0) {
+ break;
+ }
+
+ continue;
+ }
+
+ fn = &frame->next;
+ }
+
+ if (h2c->send_window == 0 && window) {
+
+ while (!ngx_queue_empty(&h2c->waiting)) {
+ q = ngx_queue_head(&h2c->waiting);
+
+ ngx_queue_remove(q);
+
+ stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
+
+ stream->waiting = 0;
+
+ wev = stream->request->connection->write;
+
+ wev->active = 0;
+ wev->ready = 1;
+
+ if (!wev->delayed) {
+ ngx_post_event(wev, &ngx_posted_events);
+ }
+ }
+ }
+
+ h2c->send_window += window;
+}
+
+
+static ngx_int_t
+ngx_http_v2_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_v2_header_filter;
+
+ return NGX_OK;
+}