/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
staticconstchar *h2_ss_str(const h2_stream_state_t state)
{ switch (state) { case H2_SS_IDLE: return"IDLE"; case H2_SS_RSVD_L: return"RESERVED_LOCAL"; case H2_SS_RSVD_R: return"RESERVED_REMOTE"; case H2_SS_OPEN: return"OPEN"; case H2_SS_CLOSED_L: return"HALF_CLOSED_LOCAL"; case H2_SS_CLOSED_R: return"HALF_CLOSED_REMOTE"; case H2_SS_CLOSED: return"CLOSED"; case H2_SS_CLEANUP: return"CLEANUP"; default: return"UNKNOWN";
}
}
staticint on_map(h2_stream_state_t state, int map[H2_SS_MAX])
{ int op = map[state]; switch (op) { case S_XXX: case S_ERR: return op; case S_NOP: return state; default: return op-1;
}
}
apr_status_t h2_stream_prepare_processing(h2_stream *stream)
{ /* Right before processing starts, last chance to decide if
* there is need to an input beam. */ if (!stream->input_closed) {
stream_setup_input(stream);
} return APR_SUCCESS;
}
staticvoid on_state_invalid(h2_stream *stream)
{ if (stream->monitor && stream->monitor->on_state_invalid) {
stream->monitor->on_state_invalid(stream->monitor->ctx, stream);
} /* stream got an event/frame invalid in its state */
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
H2_STRM_MSG(stream, "invalid state event")); switch (stream->state) { case H2_SS_OPEN: case H2_SS_RSVD_L: case H2_SS_RSVD_R: case H2_SS_CLOSED_L: case H2_SS_CLOSED_R:
h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR); break; default: break;
}
}
apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
{
apr_status_t status = APR_SUCCESS; int new_state, eos = 0;
switch (ftype) { case NGHTTP2_DATA:
eos = (flags & NGHTTP2_FLAG_END_STREAM); break;
case NGHTTP2_HEADERS:
eos = (flags & NGHTTP2_FLAG_END_STREAM); break;
case NGHTTP2_PUSH_PROMISE: /* start pushed stream */
ap_assert(stream->request == NULL);
ap_assert(stream->rtmp != NULL);
status = h2_stream_end_headers(stream, 1, 0); if (status != APR_SUCCESS) goto leave; break;
default: break;
}
status = transit(stream, new_state); if (status == APR_SUCCESS && eos) {
status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
}
leave: return status;
}
apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
{
apr_status_t status = APR_SUCCESS; int new_state, eos = 0;
switch (ftype) { case NGHTTP2_DATA:
eos = (flags & NGHTTP2_FLAG_END_STREAM); break;
case NGHTTP2_HEADERS:
eos = (flags & NGHTTP2_FLAG_END_STREAM); if (h2_stream_is_at_or_past(stream, H2_SS_OPEN)) { /* trailer HEADER */ if (!eos) {
h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
}
stream->in_trailer_octets += frame_len;
} else { /* request HEADER */
ap_assert(stream->request == NULL); if (stream->rtmp == NULL) { /* This can only happen, if the stream has received no header * name/value pairs at all. The latest nghttp2 version have become * pretty good at detecting this early. In any case, we have
* to abort the connection here, since this is clearly a protocol error */ return APR_EINVAL;
}
status = h2_stream_end_headers(stream, eos, frame_len); if (status != APR_SUCCESS) goto leave;
} break;
default: break;
}
status = transit(stream, new_state); if (status == APR_SUCCESS && eos) {
status = transit(stream, on_event(stream, H2_SEV_CLOSED_R));
}
leave: return status;
}
void h2_stream_cleanup(h2_stream *stream)
{ /* Stream is done on c1. There might still be processing on a c2 * going on. The input/output beams get aborted and the stream's * end of the in/out notifications get closed.
*/
ap_assert(stream);
H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK); if (stream->out_buffer) {
apr_brigade_cleanup(stream->out_buffer);
}
}
/* http(s) scheme. rfc7540, ch. 8.1.2.3: * This [:path] pseudo-header field MUST NOT be empty for "http" or "https" * URIs; "http" or "https" URIs that do not contain a path component * MUST include a value of '/'. The exception to this rule is an * OPTIONS request for an "http" or "https" URI that does not include * a path component; these MUST include a ":path" pseudo-header field * with a value of '*' * * All HTTP/2 requests MUST include exactly one valid value for the * ":method", ":scheme", and ":path" pseudo-header fields, unless it is * a CONNECT request.
*/
is_http_or_https = (!req->scheme
|| !(ap_cstr_casecmpn(req->scheme, "http", 4) != 0
|| (req->scheme[4] != '\0'
&& (apr_tolower(req->scheme[4]) != 's'
|| req->scheme[5] != '\0'))));
/* CONNECT. rfc7540, ch. 8.3: * In HTTP/2, the CONNECT method is used to establish a tunnel over a * single HTTP/2 stream to a remote host for similar purposes. The HTTP * header field mapping works as defined in Section 8.1.2.3 ("Request * Pseudo-Header Fields"), with a few differences. Specifically: * o The ":method" pseudo-header field is set to "CONNECT". * o The ":scheme" and ":path" pseudo-header fields MUST be omitted. * o The ":authority" pseudo-header field contains the host and port to * connect to (equivalent to the authority-form of the request-target * of CONNECT requests (see [RFC7230], Section 5.3)).
*/ if (!ap_cstr_casecmp(req->method, "CONNECT")) { if (req->protocol) { if (!strcmp("websocket", req->protocol)) { if (!req->scheme || !req->path) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT " "without :scheme or :path, sending 400 answer"));
set_error_response(stream, HTTP_BAD_REQUEST); goto cleanup;
}
} else { /* do not know that protocol */
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1, APLOGNO(10460) "':protocol: %s' header present in %s request",
req->protocol, req->method);
set_error_response(stream, HTTP_NOT_IMPLEMENTED); goto cleanup;
}
} elseif (req->scheme || req->path) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10384), stream, "Request to CONNECT " "with :scheme or :path specified, sending 400 answer"));
set_error_response(stream, HTTP_BAD_REQUEST); goto cleanup;
}
} elseif (is_http_or_https) { if (!req->path) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10385), stream, "Request for http(s) " "resource without :path, sending 400 answer"));
set_error_response(stream, HTTP_BAD_REQUEST); goto cleanup;
} if (!req->scheme) {
req->scheme = ap_ssl_conn_is_ssl(stream->session->c1)? "https" : "http";
}
}
if (req->scheme && (req->path && req->path[0] != '/')) { /* We still have a scheme, which means we need to pass an absolute URI into * our HTTP protocol handling and the missing '/' at the start will prevent
* us from doing so (as it then confuses path and authority). */
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10379), stream, "Request :scheme '%s' and " "path '%s' do not allow creating an absolute URL. Failing " "request with 400."), req->scheme, req->path);
set_error_response(stream, HTTP_BAD_REQUEST); goto cleanup;
}
if (!stream->output) { goto cleanup;
} if (stream->rst_error) {
rv = APR_ECONNRESET; goto cleanup;
}
if (!stream->out_buffer) {
stream->out_buffer = apr_brigade_create(stream->pool, c1->bucket_alloc);
buf_len = 0;
} else { /* if the brigade contains a file bucket, its normal report length * might be megabytes, but the memory used is tiny. For buffering,
* we are only interested in the memory footprint. */
buf_len = h2_brigade_mem_size(stream->out_buffer);
}
if (buf_len > APR_INT32_MAX
|| (apr_size_t)buf_len >= stream->session->max_stream_mem) { /* we have buffered enough. No need to read more. * However, we have now output pending for which we may not * receive another poll event. We need to make sure that this * stream is not suspended so we keep on processing output.
*/
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_STRM_MSG(stream, "out_buffer, already has %ld length"),
(long)buf_len);
rv = APR_SUCCESS; goto cleanup;
}
/* get rid of buckets we have no need for */ if (!APR_BRIGADE_EMPTY(stream->out_buffer)) {
b = APR_BRIGADE_FIRST(stream->out_buffer); while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
e = APR_BUCKET_NEXT(b); if (APR_BUCKET_IS_METADATA(b)) { if (APR_BUCKET_IS_FLUSH(b)) { /* we flush any c1 data already */
APR_BUCKET_REMOVE(b);
apr_bucket_destroy(b);
} elseif (APR_BUCKET_IS_EOS(b)) {
stream->output_eos = 1;
} elseif (AP_BUCKET_IS_ERROR(b)) {
stream_do_error_bucket(stream, b); break;
}
} elseif (b->length == 0) { /* zero length data */
APR_BUCKET_REMOVE(b);
apr_bucket_destroy(b);
}
b = e;
}
}
H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "out_buffer, after receive");
#if AP_HAS_RESPONSE_BUCKETS const h2_priority *h2_stream_get_priority(h2_stream *stream,
ap_bucket_response *response) #else const h2_priority *h2_stream_get_priority(h2_stream *stream,
h2_headers *response) #endif
{
H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK); if (response && stream->initiated_on) { constchar *ctype = apr_table_get(response->headers, "content-type"); if (ctype) { /* FIXME: Not good enough, config needs to come from request->server */ return h2_cconfig_get_priority(stream->session->c1, ctype);
}
} return NULL;
}
int h2_stream_is_ready(h2_stream *stream)
{ /* Have we sent a response or do we have the response in our buffer? */
H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK); if (stream->response) { return 1;
} elseif (stream->out_buffer && get_first_response_bucket(stream->out_buffer)) { return 1;
} return 0;
}
while (consumed > 0) { int len = (consumed > INT_MAX)? INT_MAX : (int)consumed;
nghttp2_session_consume(session->ngh2, stream->id, len);
consumed -= len;
}
#ifdef H2_NG2_LOCAL_WIN_SIZE if (1) { int cur_size = nghttp2_session_get_stream_local_window_size(
session->ngh2, stream->id); int win = stream->in_window_size; int thigh = win * 8/10; int tlow = win * 2/10; constint win_max = 2*1024*1024; constint win_min = 32*1024;
/* Work in progress, probably should add directives for these * values once this stabilizes somewhat. The general idea is * to adapt stream window sizes if the input window changes * a) very quickly (< good RTT) from full to empty * b) only a little bit (> bad RTT) * where in a) it grows and in b) it shrinks again.
*/ if (cur_size > thigh && amount > thigh && win < win_max) { /* almost empty again with one reported consumption, how
* long did this take? */ long ms = apr_time_msec(apr_time_now() - stream->in_last_write); if (ms < 40) {
win = H2MIN(win_max, win + (64*1024));
}
} elseif (cur_size < tlow && amount < tlow && win > win_min) { /* staying full, for how long already? */ long ms = apr_time_msec(apr_time_now() - stream->in_last_write); if (ms > 700) {
win = H2MAX(win_min, win - (32*1024));
}
}
static apr_off_t output_data_buffered(h2_stream *stream, int *peos, int *pheader_blocked)
{ /* How much data do we have in our buffers that we can write? */
apr_off_t buf_len = 0;
apr_bucket *b;
/* nghttp2 wants to send more DATA for the stream. * we should have submitted the final response at this time
* after receiving output via stream_do_responses() */
ap_assert(session);
(void)ng2s;
(void)buf;
(void)source;
stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c1,
APLOGNO(02937)
H2_SSSN_STRM_MSG(session, stream_id, "data_cb, stream not found")); return NGHTTP2_ERR_CALLBACK_FAILURE;
}
H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK); if (!stream->output || !stream->response || !stream->out_buffer) { return NGHTTP2_ERR_DEFERRED;
} if (stream->rst_error) { return NGHTTP2_ERR_DEFERRED;
} if (h2_c1_io_needs_flush(&session->io)) {
rv = h2_c1_io_pass(&session->io); if (APR_STATUS_IS_EAGAIN(rv)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
H2_SSSN_STRM_MSG(session, stream_id, "suspending on c1 out needs flush"));
h2_stream_dispatch(stream, H2_SEV_OUT_C1_BLOCK); return NGHTTP2_ERR_DEFERRED;
} elseif (rv) {
h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL); return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
/* determine how much we'd like to send. We cannot send more than * is requested. But we can reduce the size in case the master
* connection operates in smaller chunks. (TSL warmup) */ if (stream->session->io.write_size > 0) {
apr_size_t chunk_len = stream->session->io.write_size - H2_FRAME_HDR_LEN; if (length > chunk_len) {
length = chunk_len;
}
} /* We allow configurable max DATA frame length. */ if (stream->session->max_data_frame_len > 0
&& length > stream->session->max_data_frame_len) {
length = stream->session->max_data_frame_len;
}
/* How much data do we have in our buffers that we can write?
* if not enough, receive more. */
buf_len = output_data_buffered(stream, &eos, &header_blocked); if (buf_len < (apr_off_t)length && !eos
&& !header_blocked && !stream->rst_error) { /* read more? */
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
H2_SSSN_STRM_MSG(session, stream_id, "need more (read len=%ld, %ld in buffer)"),
(long)length, (long)buf_len);
rv = buffer_output_receive(stream);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id, "buffer_output_received")); if (APR_STATUS_IS_EAGAIN(rv)) { /* currently, no more is available */
} elseif (APR_SUCCESS == rv) { /* got some, re-assess */
buf_len = output_data_buffered(stream, &eos, &header_blocked);
} elseif (APR_EOF == rv) { if (!stream->output_eos) { /* Seeing APR_EOF without an EOS bucket received before indicates * that stream output is incomplete. Commonly, we expect to see * an ERROR bucket to have been generated. But faulty handlers * may not have generated one. * We need to RST the stream bc otherwise the client thinks
* it is all fine. */
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id, "rst stream"));
h2_stream_rst(stream, H2_ERR_STREAM_CLOSED); return NGHTTP2_ERR_DEFERRED;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id, "eof on receive (read len=%ld, %ld in buffer)"),
(long)length, (long)buf_len);
eos = 1;
rv = APR_SUCCESS;
} elseif (APR_ECONNRESET == rv || APR_ECONNABORTED == rv) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
H2_STRM_LOG(APLOGNO(10471), stream, "data_cb, reading data"));
h2_stream_rst(stream, H2_ERR_STREAM_CLOSED); return NGHTTP2_ERR_DEFERRED;
} else {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
H2_STRM_LOG(APLOGNO(02938), stream, "data_cb, reading data"));
h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR); return NGHTTP2_ERR_DEFERRED;
}
}
if (stream->rst_error) { return NGHTTP2_ERR_DEFERRED;
}
if (buf_len == 0 && header_blocked) {
rv = stream_do_trailers(stream); if (APR_SUCCESS != rv && !APR_STATUS_IS_EAGAIN(rv)) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
H2_STRM_LOG(APLOGNO(10300), stream, "data_cb, error processing trailers")); return NGHTTP2_ERR_CALLBACK_FAILURE;
}
length = 0;
eos = 0;
} elseif (buf_len > (apr_off_t)length) {
eos = 0; /* Any EOS we have in the buffer does not apply yet */
} else {
length = (size_t)buf_len;
}
if (length) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
H2_STRM_MSG(stream, "data_cb, sending len=%ld, eos=%d"),
(long)length, eos);
*data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
} elseif (!eos && !stream->sent_trailers) { /* We have not reached the end of DATA yet, DEFER sending */
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
H2_STRM_LOG(APLOGNO(03071), stream, "data_cb, suspending")); return NGHTTP2_ERR_DEFERRED;
}
if (eos) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
} return length;
}
b = APR_BRIGADE_FIRST(stream->out_buffer); while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
e = APR_BUCKET_NEXT(b); if (APR_BUCKET_IS_METADATA(b)) { #if AP_HAS_RESPONSE_BUCKETS if (AP_BUCKET_IS_RESPONSE(b)) {
resp = b->data; #else/* AP_HAS_RESPONSE_BUCKETS */ if (H2_BUCKET_IS_HEADERS(b)) {
resp = h2_bucket_headers_get(b); #endif/* else AP_HAS_RESPONSE_BUCKETS */
APR_BUCKET_REMOVE(b);
apr_bucket_destroy(b);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
H2_STRM_MSG(stream, "process response %d"),
resp->status);
is_empty = (e != APR_BRIGADE_SENTINEL(stream->out_buffer)
&& APR_BUCKET_IS_EOS(e)); break;
} elseif (APR_BUCKET_IS_EOS(b)) {
h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
rv = APR_EINVAL; goto cleanup;
} elseif (AP_BUCKET_IS_ERROR(b)) {
stream_do_error_bucket(stream, b);
rv = APR_EINVAL; goto cleanup;
}
}
else {
/* data buckets before response headers, an error */
h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
rv = APR_EINVAL;
goto cleanup;
}
b = e;
}
if (!resp) {
rv = APR_EAGAIN;
goto cleanup;
}
if (resp->status < 100) {
h2_stream_rst(stream, resp->status);
goto cleanup;
}
if (resp->status == HTTP_FORBIDDEN && resp->notes) {
const char *cause = apr_table_get(resp->notes, "ssl-renegotiate-forbidden");
if (cause) {
/* This request triggered a TLS renegotiation that is not allowed
* in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
*/
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, resp->status, c1,
H2_STRM_LOG(APLOGNO(03061), stream,
"renegotiate forbidden, cause: %s"), cause);
h2_stream_rst(stream, H2_ERR_HTTP_1_1_REQUIRED);
goto cleanup;
}
}
/* If this stream is not a pushed one itself,
* and HTTP/2 server push is enabled here,
* and the response HTTP status is not sth >= 400,
* and the remote side has pushing enabled,
* -> find and perform any pushes on this stream
* *before* we submit the stream response itself.
* This helps clients avoid opening new streams on Link
* resp that get pushed right afterwards.
*
* *) the response code is relevant, as we do not want to
* make pushes on 401 or 403 codes and friends.
* And if we see a 304, we do not push either
* as the client, having this resource in its cache, might
* also have the pushed ones as well.
*/
if (!stream->initiated_on
&& !stream->response
&& stream->request && stream->request->method
&& !strcmp("GET", stream->request->method)
&& (resp->status < 400)
&& (resp->status != 304)
&& h2_session_push_enabled(stream->session)) {
/* PUSH is possible and enabled on server, unless the request
* denies it, submit resources to push */
const char *s = apr_table_get(resp->notes, H2_PUSH_MODE_NOTE);
if (!s || strcmp(s, "0")) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
H2_STRM_MSG(stream, "submit pushes, note=%s"), s);
h2_stream_submit_pushes(stream, resp);
}
}
if (resp->status == 103
&& !h2_config_sgeti(stream->session->s, H2_CONF_EARLY_HINTS)) {
/* suppress sending this to the client, it might have triggered
* pushes and served its purpose nevertheless */
rv = APR_SUCCESS;
goto cleanup;
}
if (resp->status >= 200) {
stream->response = resp;
}
/* stream->pout_recv_write signalled a change. Check what has happend, read
* from it and act on seeing a response/data. */
H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (!stream->output) {
/* c2 has not assigned the output beam to the stream (yet). */
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c1,
H2_STRM_MSG(stream, "read_output, no output beam registered"));
}
else if (h2_stream_is_at_or_past(stream, H2_SS_CLOSED)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
H2_STRM_LOG(APLOGNO(10301), stream, "already closed"));
}
else if (h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
/* We have delivered a response to a stream that was not closed
* by the client. This could be a POST with body that we negate
* and we need to RST_STREAM to end if. */
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
H2_STRM_LOG(APLOGNO(10313), stream, "remote close missing"));
h2_stream_rst(stream, H2_ERR_NO_ERROR);
}
else {
/* stream is not closed, a change in output happened. There are
* two modes of operation here:
* 1) the final response has been submitted. nghttp2 is invoking
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.86 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.