apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response, apr_bucket_brigade *bb) { apr_status_t status = APR_SUCCESS; stream->response = response; if (bb && !APR_BRIGADE_EMPTY(bb)) { int move_all = INT_MAX; /* we can move file handles from h2_mplx into this h2_stream as many * as we want, since the lifetimes are the same and we are not freeing * the ones in h2_mplx->io before this stream is done. */ status = h2_util_move(stream->bbout, bb, 16 * 1024, &move_all, "h2_stream_set_response"); } if (APLOGctrace1(stream->session->c)) { apr_size_t len = 0; int eos = 0; h2_util_bb_avail(stream->bbout, &len, &eos); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c, "h2_stream(%ld-%d): set_response(%s), len=%ld, eos=%d", stream->session->id, stream->id, response->status, (long)len, (int)eos); } return status; }
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c) { h2_config *cfg = h2_config_get(c); io->connection = c; io->input = apr_brigade_create(c->pool, c->bucket_alloc); io->output = apr_brigade_create(c->pool, c->bucket_alloc); io->buflen = 0; io->is_tls = h2_h2_is_tls(c); io->buffer_output = io->is_tls; if (io->buffer_output) { io->bufsize = WRITE_BUFFER_SIZE; io->buffer = apr_pcalloc(c->pool, io->bufsize); } else { io->bufsize = 0; } if (io->is_tls) { /* That is where we start with, * see https://issues.apache.org/jira/browse/TS-2503 */ io->warmup_size = h2_config_geti64(cfg, H2_CONF_TLS_WARMUP_SIZE); io->cooldown_usecs = (h2_config_geti(cfg, H2_CONF_TLS_COOLDOWN_SECS) * APR_USEC_PER_SEC); io->write_size = WRITE_SIZE_INITIAL; } else { io->warmup_size = 0; io->cooldown_usecs = 0; io->write_size = (int)io->bufsize; } if (APLOGctrace1(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection, "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, cd_secs=%f", io->connection->id, io->buffer_output, (long)io->warmup_size, ((float)io->cooldown_usecs/APR_USEC_PER_SEC)); } return APR_SUCCESS; }
apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response, ap_filter_t* f, apr_bucket_brigade *bb, struct apr_thread_cond_t *iowait) { apr_status_t status; int acquired; AP_DEBUG_ASSERT(m); if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { if (m->aborted) { status = APR_ECONNABORTED; } else { status = out_open(m, stream_id, response, f, bb, iowait); if (APLOGctrace1(m->c)) { h2_util_bb_log(m->c, stream_id, APLOG_TRACE1, "h2_mplx_out_open", bb); } } leave_mutex(m, acquired); } return status; }
apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response, ap_filter_t* f, apr_bucket_brigade *bb, struct apr_thread_cond_t *iowait) { apr_status_t status; AP_DEBUG_ASSERT(m); if (m->aborted) { return APR_ECONNABORTED; } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { status = out_open(m, stream_id, response, f, bb, iowait); if (APLOGctrace1(m->c)) { h2_util_bb_log(m->c, stream_id, APLOG_TRACE1, "h2_mplx_out_open", bb); } if (m->aborted) { return APR_ECONNABORTED; } apr_thread_mutex_unlock(m->lock); } return status; }
static int h2_h2_alpn_negotiated(conn_rec *c, const char *proto_name, apr_size_t proto_name_len) { if (APLOGctrace1(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "ALPN negotiated is %s", apr_pstrndup(c->pool, proto_name, proto_name_len)); } h2_config *cfg = h2_config_get(c); if (!h2_config_geti(cfg, H2_CONF_ENABLED)) { return DECLINED; } if (!h2_ctx_is_session(c) ) { return DECLINED; } if (h2_ctx_is_negotiated(c)) { // called twice? maybe alpn+npn overlap... return DECLINED; } for (int i = 0; i < h2_protos_len; ++i) { const char *proto = h2_protos[i]; if (proto_name_len == strlen(proto) && strncmp(proto, proto_name, proto_name_len) == 0) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "protocol set va ALPN to %s", proto); h2_ctx_set_protocol(c, proto); break; } } return OK; }
int h2_h2_process_conn(conn_rec* c) { apr_status_t status; h2_ctx *ctx; if (c->master) { return DECLINED; } ctx = h2_ctx_get(c, 0); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn"); if (h2_ctx_is_task(ctx)) { /* our stream pseudo connection */ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, task, declined"); return DECLINED; } if (!ctx && c->keepalives == 0) { const char *proto = ap_get_protocol(c); if (APLOGctrace1(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, " "new connection using protocol '%s', direct=%d, " "tls acceptable=%d", proto, h2_allows_h2_direct(c), h2_is_acceptable_connection(c, 1)); } if (!strcmp(AP_PROTOCOL_HTTP1, proto) && h2_allows_h2_direct(c) && h2_is_acceptable_connection(c, 1)) { /* Fresh connection still is on http/1.1 and H2Direct is enabled. * Otherwise connection is in a fully acceptable state. * -> peek at the first 24 incoming bytes */ apr_bucket_brigade *temp; char *s = NULL; apr_size_t slen; temp = apr_brigade_create(c->pool, c->bucket_alloc); status = ap_get_brigade(c->input_filters, temp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054) "h2_h2, error reading 24 bytes speculative"); apr_brigade_destroy(temp); return DECLINED; } apr_brigade_pflatten(temp, &s, &slen, c->pool); if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, direct mode detected"); if (!ctx) { ctx = h2_ctx_get(c, 1); } h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c"); } else { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, not detected in %d bytes: %s", (int)slen, s); } apr_brigade_destroy(temp); } } if (ctx) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn"); if (!h2_ctx_session_get(ctx)) { status = h2_conn_setup(ctx, c, NULL); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup"); if (status != APR_SUCCESS) { h2_ctx_clear(c); return status; } } return h2_conn_run(ctx, c); } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined"); return DECLINED; }
static apr_status_t h2_filter_slave_in(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { h2_task *task; apr_status_t status = APR_SUCCESS; apr_bucket *b, *next; apr_off_t bblen; const int trace1 = APLOGctrace1(f->c); apr_size_t rmax = ((readbytes <= APR_SIZE_MAX)? (apr_size_t)readbytes : APR_SIZE_MAX); task = h2_ctx_cget_task(f->c); ap_assert(task); if (trace1) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_slave_in(%s): read, mode=%d, block=%d, readbytes=%ld", task->id, mode, block, (long)readbytes); } if (mode == AP_MODE_INIT) { return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); } if (f->c->aborted) { return APR_ECONNABORTED; } if (!task->input.bb) { return APR_EOF; } /* Cleanup brigades from those nasty 0 length non-meta buckets * that apr_brigade_split_line() sometimes produces. */ for (b = APR_BRIGADE_FIRST(task->input.bb); b != APR_BRIGADE_SENTINEL(task->input.bb); b = next) { next = APR_BUCKET_NEXT(b); if (b->length == 0 && !APR_BUCKET_IS_METADATA(b)) { apr_bucket_delete(b); } } while (APR_BRIGADE_EMPTY(task->input.bb)) { /* Get more input data for our request. */ if (trace1) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_slave_in(%s): get more data from mplx, block=%d, " "readbytes=%ld", task->id, block, (long)readbytes); } if (task->input.beam) { status = h2_beam_receive(task->input.beam, task->input.bb, block, 128*1024); } else { status = APR_EOF; } if (trace1) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c, "h2_slave_in(%s): read returned", task->id); } if (APR_STATUS_IS_EAGAIN(status) && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) { /* chunked input handling does not seem to like it if we * return with APR_EAGAIN from a GETLINE read... * upload 100k test on test-ser.example.org hangs */ status = APR_SUCCESS; } else if (APR_STATUS_IS_EOF(status)) { break; } else if (status != APR_SUCCESS) { return status; } if (trace1) { h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, "input.beam recv raw", task->input.bb); } if (h2_task_logio_add_bytes_in) { apr_brigade_length(bb, 0, &bblen); h2_task_logio_add_bytes_in(f->c, bblen); } } /* Nothing there, no more data to get. Return APR_EAGAIN on * speculative reads, this is ap_check_pipeline()'s trick to * see if the connection needs closing. */ if (status == APR_EOF && APR_BRIGADE_EMPTY(task->input.bb)) { return (mode == AP_MODE_SPECULATIVE)? APR_EAGAIN : APR_EOF; } if (trace1) { h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, "task_input.bb", task->input.bb); } if (APR_BRIGADE_EMPTY(task->input.bb)) { if (trace1) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_slave_in(%s): no data", task->id); } return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF; } if (mode == AP_MODE_EXHAUSTIVE) { /* return all we have */ APR_BRIGADE_CONCAT(bb, task->input.bb); } else if (mode == AP_MODE_READBYTES) { status = h2_brigade_concat_length(bb, task->input.bb, rmax); } else if (mode == AP_MODE_SPECULATIVE) { status = h2_brigade_copy_length(bb, task->input.bb, rmax); } else if (mode == AP_MODE_GETLINE) { /* we are reading a single LF line, e.g. the HTTP headers. * this has the nasty side effect to split the bucket, even * though it ends with CRLF and creates a 0 length bucket */ status = apr_brigade_split_line(bb, task->input.bb, block, HUGE_STRING_LEN); if (APLOGctrace1(f->c)) { char buffer[1024]; apr_size_t len = sizeof(buffer)-1; apr_brigade_flatten(bb, buffer, &len); buffer[len] = 0; if (trace1) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_slave_in(%s): getline: %s", task->id, buffer); } } } else { /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not * to support it. Seems to work. */ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c, APLOGNO(03472) "h2_slave_in(%s), unsupported READ mode %d", task->id, mode); status = APR_ENOTIMPL; } if (trace1) { apr_brigade_length(bb, 0, &bblen); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_slave_in(%s): %ld data bytes", task->id, (long)bblen); } return status; }
apr_status_t h2_task_input_read(h2_task_input *input, ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { apr_status_t status = APR_SUCCESS; apr_off_t bblen = 0; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld", input->task->id, block, mode, (long)readbytes); if (mode == AP_MODE_INIT) { return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); } if (is_aborted(f)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_task_input(%s): is aborted", input->task->id); return APR_ECONNABORTED; } if (input->bb) { status = apr_brigade_length(input->bb, 1, &bblen); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c, APLOGNO(02958) "h2_task_input(%s): brigade length fail", input->task->id); return status; } } if ((bblen == 0) && input->task->input_eos) { return APR_EOF; } while ((bblen == 0) || (mode == AP_MODE_READBYTES && bblen < readbytes)) { /* Get more data for our stream from mplx. */ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_task_input(%s): get more data from mplx, block=%d, " "readbytes=%ld, queued=%ld", input->task->id, block, (long)readbytes, (long)bblen); /* Although we sometimes get called with APR_NONBLOCK_READs, we seem to fill our buffer blocking. Otherwise we get EAGAIN, return that to our caller and everyone throws up their hands, never calling us again. */ status = h2_mplx_in_read(input->task->mplx, APR_BLOCK_READ, input->task->stream_id, input->bb, input->task->io); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_task_input(%s): mplx in read returned", input->task->id); if (status != APR_SUCCESS) { return status; } status = apr_brigade_length(input->bb, 1, &bblen); if (status != APR_SUCCESS) { return status; } if ((bblen == 0) && (block == APR_NONBLOCK_READ)) { return h2_util_has_eos(input->bb, -1)? APR_EOF : APR_EAGAIN; } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_task_input(%s): mplx in read, %ld bytes in brigade", input->task->id, (long)bblen); } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_task_input(%s): read, mode=%d, block=%d, " "readbytes=%ld, queued=%ld", input->task->id, mode, block, (long)readbytes, (long)bblen); if (!APR_BRIGADE_EMPTY(input->bb)) { if (mode == AP_MODE_EXHAUSTIVE) { /* return all we have */ return h2_util_move(bb, input->bb, readbytes, NULL, "task_input_read(exhaustive)"); } else if (mode == AP_MODE_READBYTES) { return h2_util_move(bb, input->bb, readbytes, NULL, "task_input_read(readbytes)"); } else if (mode == AP_MODE_SPECULATIVE) { /* return not more than was asked for */ return h2_util_copy(bb, input->bb, readbytes, "task_input_read(speculative)"); } else if (mode == AP_MODE_GETLINE) { /* we are reading a single LF line, e.g. the HTTP headers */ status = apr_brigade_split_line(bb, input->bb, block, HUGE_STRING_LEN); if (APLOGctrace1(f->c)) { char buffer[1024]; apr_size_t len = sizeof(buffer)-1; apr_brigade_flatten(bb, buffer, &len); buffer[len] = 0; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_task_input(%s): getline: %s", input->task->id, buffer); } return status; } else { /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not * to support it. Seems to work. */ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c, APLOGNO(02942) "h2_task_input, unsupported READ mode %d", mode); return APR_ENOTIMPL; } } if (is_aborted(f)) { return APR_ECONNABORTED; } return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF; }