[core] h2_send_headers() specialized for resp hdrs

specialized version of http_response_write_header(); send headers
directly to HPACK encoder, rather than double-buffering in chunkqueue
This commit is contained in:
Glenn Strauss 2020-08-25 06:34:47 -04:00
parent 014e5240ef
commit ada09a23b0
8 changed files with 227 additions and 36 deletions

View File

@ -1097,9 +1097,10 @@ connection_state_machine_loop (request_st * const r, connection * const con)
}
/* fall through */
/*case CON_STATE_RESPONSE_START:*//*occurred;transient*/
http_response_write_header(r);
if (r->http_version > HTTP_VERSION_1_1)
h2_send_cqheaders(r, con);
h2_send_headers(r, con);
else
http_response_write_header(r);
connection_set_state(r, CON_STATE_WRITE);
/* fall through */
case CON_STATE_WRITE:

205
src/h2.c
View File

@ -19,6 +19,7 @@
#include "http_header.h"
#include "log.h"
#include "request.h"
#include "response.h" /* http_response_omit_header() */
static request_st * h2_init_stream (request_st * const h2r, connection * const con);
@ -1616,6 +1617,205 @@ h2_send_hpack (request_st * const r, connection * const con, const char *data, u
}
void
h2_send_headers (request_st * const r, connection * const con)
{
/*(set keep_alive_idle; out-of-place and non-event for most configs,
* but small attempt to (maybe) preserve behavior for specific configs)*/
con->keep_alive_idle = r->conf.max_keep_alive_idle;
/* specialized version of http_response_write_header(); send headers
* directly to HPACK encoder, rather than double-buffering in chunkqueue */
if (304 == r->http_status && (r->resp_htags & HTTP_HEADER_CONTENT_ENCODING))
http_header_response_unset(r, HTTP_HEADER_CONTENT_ENCODING,
CONST_STR_LEN("Content-Encoding"));
/*(h2_init_con() resized h2r->tmp_buf to 64k; shared with r->tmp_buf)*/
buffer * const tb = r->tmp_buf;
force_assert(tb->size >= 65536);/*(sanity check; remove in future)*/
unsigned char *dst = (unsigned char *)tb->ptr;
unsigned char * const dst_end = (unsigned char *)tb->ptr + tb->size;
h2con * const h2c = con->h2;
struct lshpack_enc * const encoder = &h2c->encoder;
lsxpack_header_t lsx;
uint32_t alen = 7+3+4; /* ":status: xxx\r\n" */
const int log_response_header = r->conf.log_response_header;
const int resp_header_repeated = r->resp_header_repeated;
char status[12] = ":status: ";
int x = r->http_status; /*(expect status < 1000; should be [100-599])*/
status[11] = (x % 10) + '0';
status[10] = (x / 10 % 10) + '0';
status[9] = (x / 100) + '0';
if (log_response_header)
log_error(r->conf.errh, __FILE__, __LINE__,
"fd:%d id:%u resp: %.*s", r->con->fd, r->h2id, 12, status);
memset(&lsx, 0, sizeof(lsxpack_header_t));
lsx.buf = status;
lsx.name_offset = 0;
lsx.name_len = 7;
lsx.val_offset = 9;
lsx.val_len = 3;
dst = lshpack_enc_encode(encoder, dst, dst_end, &lsx);
if (dst == (unsigned char *)tb->ptr) {
h2_send_rst_stream(r, con, H2_E_INTERNAL_ERROR);
return;
}
/* add all headers */
for (uint32_t i = 0, used = r->resp_headers.used; i < used; ++i) {
data_string * const restrict ds =(data_string *)r->resp_headers.data[i];
const uint32_t klen = buffer_string_length(&ds->key);
const uint32_t vlen = buffer_string_length(&ds->value);
const char * const restrict k = ds->key.ptr;
if (0 == vlen || 0 == klen) continue;
if ((k[0] & 0xdf) == 'X' && http_response_omit_header(r, ds))
continue;
alen += klen + vlen + 4;
if (alen > LSXPACK_MAX_STRLEN) {
/* ls-hpack default limit (UINT16_MAX) is per-line, due to field
* sizes of lsx.name_offset,lsx.name_len,lsx.val_offset,lsx.val_len
* However, similar to elsewhere, limit total size of expanded
* headers to (very generous) 64k - 1. Peers might allow less. */
h2_send_rst_stream(r, con, H2_E_INTERNAL_ERROR);
return;
}
/* HTTP/2 requires lowercase keys
* ls-hpack requires key and value be in same buffer
* Since keys are typically short, append (and lowercase) key onto
* end of value buffer */
char * const v = buffer_string_prepare_append(&ds->value, klen);
for (uint32_t j = 0; j < klen; ++j)
v[j] = (k[j] < 'A' || k[j] > 'Z') ? k[j] : (k[j] | 0x20);
/*buffer_commit(&ds->value, klen);*//*(not necessary; truncated below)*/
uint32_t voff = 0;
const char *n;
lsx.buf = ds->value.ptr;
do {
n = !resp_header_repeated
? NULL
: memchr(lsx.buf+voff, '\n', vlen - voff);
memset(&lsx, 0, sizeof(lsxpack_header_t));
lsx.buf = ds->value.ptr;
lsx.name_offset = vlen;
lsx.name_len = klen;
lsx.val_offset = voff;
if (NULL == n)
lsx.val_len = vlen - voff;
else {
/* multiple headers (same field-name) separated by "\r\n"
* and then "field-name: " (see http_header_response_insert())*/
voff = (uint32_t)(n + 1 - lsx.buf);
lsx.val_len = voff - 2 - lsx.val_offset; /*(-2 for "\r\n")*/
voff += klen + 2;
}
if (log_response_header)
log_error(r->conf.errh, __FILE__, __LINE__,
"fd:%d id:%u resp: %.*s: %.*s", r->con->fd, r->h2id,
(int)klen, lsx.buf + lsx.name_offset,
(int)lsx.val_len, lsx.buf + lsx.val_offset);
unsigned char * const dst_in = dst;
dst = lshpack_enc_encode(encoder, dst, dst_end, &lsx);
if (dst == dst_in) {
buffer_string_set_length(&ds->value, vlen); /*(restore prior value)*/
h2_send_rst_stream(r, con, H2_E_INTERNAL_ERROR);
return;
}
} while (n);
buffer_string_set_length(&ds->value, vlen); /*(restore prior value)*/
}
if (!(r->resp_htags & HTTP_HEADER_DATE)) {
/* HTTP/1.1 and later requires a Date: header */
/* "date: " 6-chars + 30-chars for "%a, %d %b %Y %H:%M:%S GMT" + '\0' */
static char tstr[36] = "date: ";
memset(&lsx, 0, sizeof(lsxpack_header_t));
lsx.buf = tstr;
lsx.name_offset = 0;
lsx.name_len = 4;
lsx.val_offset = 6;
lsx.val_len = 29;
/* cache the generated timestamp */
static time_t tlast;
const time_t cur_ts = log_epoch_secs;
if (tlast != cur_ts) {
tlast = cur_ts;
strftime(tstr+6, sizeof(tstr)-6,
"%a, %d %b %Y %H:%M:%S GMT", gmtime(&tlast));
}
alen += 35+2;
if (log_response_header)
log_error(r->conf.errh, __FILE__, __LINE__,
"fd:%d id:%u resp: %.*s", r->con->fd, r->h2id, 35, tstr);
unsigned char * const dst_in = dst;
dst = lshpack_enc_encode(encoder, dst, dst_end, &lsx);
if (dst == dst_in) {
h2_send_rst_stream(r, con, H2_E_INTERNAL_ERROR);
return;
}
}
if (!(r->resp_htags & HTTP_HEADER_SERVER)
&& !buffer_string_is_empty(r->conf.server_tag)) {
buffer * const b = chunk_buffer_acquire();
const uint32_t vlen = buffer_string_length(r->conf.server_tag);
buffer_copy_string_len(b, CONST_STR_LEN("server: "));
buffer_append_string_len(b, CONST_BUF_LEN(r->conf.server_tag));
alen += 6+vlen+4;
if (log_response_header)
log_error(r->conf.errh, __FILE__, __LINE__,
"fd:%d id:%u resp: %.*s", r->con->fd, r->h2id,
(int)6+vlen+2, b->ptr);
memset(&lsx, 0, sizeof(lsxpack_header_t));
lsx.buf = b->ptr;
lsx.name_offset = 0;
lsx.name_len = 6;
lsx.val_offset = 8;
lsx.val_len = vlen;
unsigned char * const dst_in = dst;
dst = lshpack_enc_encode(encoder, dst, dst_end, &lsx);
chunk_buffer_release(b);
if (dst == dst_in) {
h2_send_rst_stream(r, con, H2_E_INTERNAL_ERROR);
return;
}
}
alen += 2; /* "virtual" blank line ("\r\n") ending headers */
r->resp_header_len = alen;
/*(accounting for mod_accesslog and mod_rrdtool)*/
chunkqueue * const wq = r->write_queue;
wq->bytes_in += (off_t)alen;
wq->bytes_out += (off_t)alen;
const uint32_t dlen = (uint32_t)((char *)dst - tb->ptr);
const uint32_t flags =
(r->resp_body_finished && chunkqueue_is_empty(r->write_queue))
? H2_FLAG_END_STREAM
: 0;
h2_send_hpack(r, con, tb->ptr, dlen, flags);
}
__attribute_cold__
__attribute_noinline__
static void
h2_send_headers_block (request_st * const r, connection * const con, const char * const hdrs, const uint32_t hlen, uint32_t flags)
@ -1719,6 +1919,9 @@ h2_send_100_continue (request_st * const r, connection * const con)
* { 0x48, 0x03, 0x31, 0x30, 0x30 }
*/
/* short header block, so reuse shared code used for trailers
* rather than adding something specific for ls-hpack here */
h2_send_headers_block(r, con, CONST_STR_LEN(":status: 100\r\n\r\n"), 0);
}
@ -1767,6 +1970,7 @@ h2_send_end_stream_trailers (request_st * const r, connection * const con, const
}
#if 0 /*(replaced by h2_send_headers())*/
void
h2_send_cqheaders (request_st * const r, connection * const con)
{
@ -1782,6 +1986,7 @@ h2_send_cqheaders (request_st * const r, connection * const con)
h2_send_headers_block(r, con, c->mem->ptr + c->offset, len, flags);
chunkqueue_mark_written(r->write_queue, len);
}
#endif
#if 0

View File

@ -104,7 +104,7 @@ void h2_init_con (request_st * restrict h2r, connection * restrict con, const bu
void h2_send_100_continue (request_st *r, connection *con);
void h2_send_cqheaders (request_st *r, connection *con);
void h2_send_headers (request_st *r, connection *con);
void h2_send_cqdata (request_st *r, connection *con, struct chunkqueue *cq, uint32_t dlen);

View File

@ -275,6 +275,7 @@ void http_response_reset (request_st * const r) {
buffer_reset(&r->physical.rel_path);
}
r->resp_htags = 0;
r->resp_header_repeated = 0;
array_reset_data_strings(&r->resp_headers);
http_response_body_clear(r, 0);
}

View File

@ -183,6 +183,7 @@ void http_header_response_insert(request_st * const r, enum http_header_e id, co
if (!buffer_string_is_empty(vb)) { /* append value */
buffer_append_string_len(vb, CONST_STR_LEN("\r\n"));
if (r->http_version >= HTTP_VERSION_2) {
r->resp_header_repeated = 1;
char * const h = buffer_string_prepare_append(vb, klen + vlen + 2);
for (uint32_t i = 0; i < klen; ++i)
h[i] = (k[i] < 'A' || k[i] > 'Z') ? k[i] : (k[i] | 0x20);

View File

@ -172,6 +172,7 @@ struct request_st {
char resp_body_started;
char resp_send_chunked;
char resp_decode_chunked;
char resp_header_repeated;
char loops_per_request; /* catch endless loops in a single request */
char keep_alive; /* only request.c can enable it, all other just disable */

View File

@ -22,8 +22,10 @@
#include <string.h>
#include <time.h>
__attribute_cold__
static int http_response_omit_header(request_st * const r, const data_string * const ds) {
int
http_response_omit_header (request_st * const r, const data_string * const ds)
{
const size_t klen = buffer_string_length(&ds->key);
if (klen == sizeof("X-Sendfile")-1
&& buffer_eq_icase_ssn(ds->key.ptr, CONST_STR_LEN("X-Sendfile")))
@ -53,21 +55,9 @@ http_response_write_header (request_st * const r)
chunkqueue * const cq = r->write_queue;
buffer * const b = chunkqueue_prepend_buffer_open(cq);
/* future: might fork this routine and send headers directly to
* the HPACK encoder, rather than double-buffering in chunkqueue */
const int h1 = (r->http_version <= HTTP_VERSION_1_1);
if (!h1) {
buffer_append_string_len(b, CONST_STR_LEN(":status: "));
buffer_append_int(b, r->http_status);
}
else {
if (r->http_version == HTTP_VERSION_1_1) {
buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 "));
} else {
buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
}
http_status_append(b, r->http_status);
}
const char * const httpv = (r->http_version == HTTP_VERSION_1_1) ? "HTTP/1.1 " : "HTTP/1.0 ";
buffer_copy_string_len(b, httpv, sizeof("HTTP/1.1 ")-1);
http_status_append(b, r->http_status);
/* disable keep-alive if requested */
@ -87,8 +77,7 @@ http_response_write_header (request_st * const r)
if ((r->resp_htags & HTTP_HEADER_UPGRADE) && r->http_version == HTTP_VERSION_1_1) {
http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("upgrade"));
} else if (0 == r->keep_alive) {
if (h1) /*(e.g. not HTTP_VERSION_2)*/
http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close"));
http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close"));
} else if (r->http_version == HTTP_VERSION_1_0) {/*(&& r->keep_alive != 0)*/
http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive"));
}
@ -107,16 +96,7 @@ http_response_write_header (request_st * const r)
continue;
buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
if (!h1) { /* HTTP/2 requires lowercase keys */
const size_t klen = buffer_string_length(&ds->key);
char * const h = buffer_string_prepare_append(b, klen);
const char * const k = ds->key.ptr;
for (uint32_t j = 0; j < klen; ++j)
h[j] = (k[j] < 'A' || k[j] > 'Z') ? k[j] : (k[j] | 0x20);
buffer_commit(b, klen);
}
else
buffer_append_string_buffer(b, &ds->key);
buffer_append_string_buffer(b, &ds->key);
buffer_append_string_len(b, CONST_STR_LEN(": "));
buffer_append_string_buffer(b, &ds->value);
}
@ -135,15 +115,13 @@ http_response_write_header (request_st * const r)
}
/* HTTP/1.1 and later requires a Date: header */
buffer_append_string_len(b, !h1 ? "\r\ndate: " : "\r\nDate: ",
sizeof("\r\ndate: ")-1);
buffer_append_string_len(b, CONST_STR_LEN("\r\nDate: "));
buffer_append_string_len(b, tstr, tlen);
}
if (!(r->resp_htags & HTTP_HEADER_SERVER)) {
if (!buffer_string_is_empty(r->conf.server_tag)) {
buffer_append_string_len(b, !h1 ? "\r\nserver: " : "\r\nServer: ",
sizeof("\r\nserver: ")-1);
buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: "));
buffer_append_string_len(b, CONST_BUF_LEN(r->conf.server_tag));
}
}

View File

@ -54,6 +54,10 @@ void http_response_send_file (request_st *r, buffer *path);
void http_response_backend_done (request_st *r);
void http_response_backend_error (request_st *r);
void http_response_upgrade_read_body_unknown(request_st *r);
__attribute_cold__
int http_response_omit_header(request_st *r, const data_string *ds);
void http_response_write_header(request_st *r);
handler_t http_response_handler(request_st *r);