/* Licensed to the Apache Software Foundation (ASF) under one or more *contributorlicenseagreements.SeetheNOTICEfiledistributedwith *thisworkforadditionalinformationregardingcopyrightownership. *TheASFlicensesthisfiletoYouundertheApacheLicense,Version2.0 *(the"License");youmaynotusethisfileexceptincompliancewith *theLicense.YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense.
*/
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/valuepairsatall.Thelatestnghttp2versionhavebecome *prettygoodatdetectingthisearly.Inanycase,wehave
* 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 *goingon.Theinput/outputbeamsgetabortedandthestream's *endofthein/outnotificationsgetclosed.
*/
ap_assert(stream);
H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK); if (stream->out_buffer) {
apr_brigade_cleanup(stream->out_buffer);
}
}
/* CONNECT. rfc7540, ch. 8.3: *InHTTP/2,theCONNECTmethodisusedtoestablishatunnelovera *singleHTTP/2streamtoaremotehostforsimilarpurposes.TheHTTP *headerfieldmappingworksasdefinedinSection8.1.2.3("Request *Pseudo-HeaderFields"),withafewdifferences.Specifically: *oThe":method"pseudo-headerfieldissetto"CONNECT". *oThe":scheme"and":path"pseudo-headerfieldsMUSTbeomitted. *oThe":authority"pseudo-headerfieldcontainsthehostandportto *connectto(equivalenttotheauthority-formoftherequest-target *ofCONNECTrequests(see[RFC7230],Section5.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 *ourHTTPprotocolhandlingandthemissing'/'atthestartwillprevent
* 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 *mightbemegabytes,butthememoryusedistiny.Forbuffering,
* 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,wehavenowoutputpendingforwhichwemaynot *receiveanotherpollevent.Weneedtomakesurethatthis *streamisnotsuspendedsowekeeponprocessingoutput.
*/
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) { return1;
} elseif (stream->out_buffer && get_first_response_bucket(stream->out_buffer)) { return1;
} return0;
}
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 *valuesoncethisstabilizessomewhat.Thegeneralideais *toadaptstreamwindowsizesiftheinputwindowchanges *a)veryquickly(<goodRTT)fromfulltoempty *b)onlyalittlebit(>badRTT) *whereina)itgrowsandinb)itshrinksagain.
*/ 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. *weshouldhavesubmittedthefinalresponseatthistime
* 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 *isrequested.Butwecanreducethesizeincasethemaster
* 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 *thatstreamoutputisincomplete.Commonly,weexpecttosee *anERRORbuckettohavebeengenerated.Butfaultyhandlers *maynothavegeneratedone. *WeneedtoRSTthestreambcotherwisetheclientthinks
* 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;
}
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"));
} elseif (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"));
} elseif (h2_stream_is_at(stream, H2_SS_CLOSED_L)) { /* We have delivered a response to a stream that was not closed *bytheclient.ThiscouldbeaPOSTwithbodythatwenegate
* 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 *twomodesofoperationhere: *1)thefinalresponsehasbeensubmitted.nghttp2isinvoking *stream_data_cb()toprogressthestream.ThishandlesDATA, *trailers,EOSandERRORs. *Whenstream_data_cb()runsoutofthingstosend,itreturns *NGHTTP2_ERR_DEFERREDandnghttp2*suspends*furtherprocessing *untilwetellittoresume. *2)Wehavenotseenthe*final*responseyet.Thestreamcannot *sendanyresponseDATA.Thenghttp2stream_data_cb()isnot *invoked.Weneedtoreceiveoutput,expectingnotDATAbut *RESPONSEs(intermediatemayarrive)andsubmitthose.On *thefinalresponse,nghttp2willstartcallingstream_data_cb().
*/ if (stream->response) {
nghttp2_session_resume_data(stream->session->ngh2, stream->id);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
H2_STRM_MSG(stream, "resumed"));
} else {
stream_do_responses(stream); if (!stream->rst_error) {
nghttp2_session_resume_data(stream->session->ngh2, stream->id);
}
}
}
}
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 und die Messung sind noch experimentell.