1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
18 #include <apr_thread_cond.h>
19 #include <apr_base64.h>
20 #include <apr_strings.h>
25 #include <http_core.h>
26 #include <http_config.h>
28 #include <scoreboard.h>
30 #include <mpm_common.h>
32 #include "h2_private.h"
34 #include "h2_bucket_beam.h"
35 #include "h2_bucket_eos.h"
36 #include "h2_config.h"
38 #include "h2_filter.h"
42 #include "h2_request.h"
43 #include "h2_headers.h"
44 #include "h2_stream.h"
46 #include "h2_session.h"
48 #include "h2_version.h"
49 #include "h2_workers.h"
52 static apr_status_t dispatch_master(h2_session *session);
53 static apr_status_t h2_session_read(h2_session *session, int block);
54 static void transit(h2_session *session, const char *action,
55 h2_session_state nstate);
57 static void on_stream_state_enter(void *ctx, h2_stream *stream);
58 static void on_stream_state_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
60 static int h2_session_status_from_apr_status(apr_status_t rv)
62 if (rv == APR_SUCCESS) {
63 return NGHTTP2_NO_ERROR;
65 else if (APR_STATUS_IS_EAGAIN(rv)) {
66 return NGHTTP2_ERR_WOULDBLOCK;
68 else if (APR_STATUS_IS_EOF(rv)) {
69 return NGHTTP2_ERR_EOF;
71 return NGHTTP2_ERR_PROTO;
74 static void update_window(void *ctx, int stream_id, apr_off_t bytes_read)
76 h2_session *session = (h2_session*)ctx;
77 while (bytes_read > 0) {
78 int len = (bytes_read > INT_MAX)? INT_MAX : bytes_read;
79 nghttp2_session_consume(session->ngh2, stream_id, (int)bytes_read);
80 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
81 "h2_stream(%ld-%d): consumed %d bytes",
82 session->id, stream_id, len);
87 static apr_status_t h2_session_receive(void *ctx,
88 const char *data, apr_size_t len,
91 static void dispatch_event(h2_session *session, h2_session_event_t ev,
92 int err, const char *msg);
94 static int rst_unprocessed_stream(h2_stream *stream, void *ctx)
96 int unprocessed = (!h2_stream_was_closed(stream)
97 && (H2_STREAM_CLIENT_INITIATED(stream->id)?
98 (!stream->session->local.accepting
99 && stream->id > stream->session->local.accepted_max)
101 (!stream->session->remote.accepting
102 && stream->id > stream->session->remote.accepted_max))
105 h2_stream_rst(stream, H2_ERR_NO_ERROR);
111 static void cleanup_unprocessed_streams(h2_session *session)
113 h2_mplx_stream_do(session->mplx, rst_unprocessed_stream, session);
116 static h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
120 apr_pool_t *stream_pool;
122 apr_pool_create(&stream_pool, session->pool);
123 apr_pool_tag(stream_pool, "h2_stream");
125 stream = h2_stream_create(stream_id, stream_pool, session,
126 session->monitor, initiated_on);
128 nghttp2_session_set_stream_user_data(session->ngh2, stream_id, stream);
134 * Determine the importance of streams when scheduling tasks.
135 * - if both stream depend on the same one, compare weights
136 * - if one stream is closer to the root, prioritize that one
137 * - if both are on the same level, use the weight of their root
140 static int spri_cmp(int sid1, nghttp2_stream *s1,
141 int sid2, nghttp2_stream *s2, h2_session *session)
143 nghttp2_stream *p1, *p2;
145 p1 = nghttp2_stream_get_parent(s1);
146 p2 = nghttp2_stream_get_parent(s2);
151 w1 = nghttp2_stream_get_weight(s1);
152 w2 = nghttp2_stream_get_weight(s2);
156 /* stream 1 closer to root */
160 /* stream 2 closer to root */
163 return spri_cmp(sid1, p1, sid2, p2, session);
166 static int stream_pri_cmp(int sid1, int sid2, void *ctx)
168 h2_session *session = ctx;
169 nghttp2_stream *s1, *s2;
171 s1 = nghttp2_session_find_stream(session->ngh2, sid1);
172 s2 = nghttp2_session_find_stream(session->ngh2, sid2);
183 return spri_cmp(sid1, s1, sid2, s2, session);
187 * Callback when nghttp2 wants to send bytes back to the client.
189 static ssize_t send_cb(nghttp2_session *ngh2,
190 const uint8_t *data, size_t length,
191 int flags, void *userp)
193 h2_session *session = (h2_session *)userp;
198 status = h2_conn_io_write(&session->io, (const char *)data, length);
199 if (status == APR_SUCCESS) {
202 if (APR_STATUS_IS_EAGAIN(status)) {
203 return NGHTTP2_ERR_WOULDBLOCK;
205 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03062)
206 "h2_session: send error");
207 return h2_session_status_from_apr_status(status);
210 static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
211 const nghttp2_frame *frame,
212 int error, void *userp)
214 h2_session *session = (h2_session *)userp;
217 if (APLOGcdebug(session->c)) {
220 h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
221 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
222 H2_SSSN_LOG(APLOGNO(03063), session,
223 "recv invalid FRAME[%s], frames=%ld/%ld (r/s)"),
224 buffer, (long)session->frames_received,
225 (long)session->frames_sent);
230 static h2_stream *get_stream(h2_session *session, int stream_id)
232 return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
235 static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
237 const uint8_t *data, size_t len, void *userp)
239 h2_session *session = (h2_session *)userp;
240 apr_status_t status = APR_EINVAL;
244 stream = get_stream(session, stream_id);
246 status = h2_stream_recv_DATA(stream, flags, data, len);
249 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03064)
250 "h2_stream(%ld-%d): on_data_chunk for unknown stream",
251 session->id, (int)stream_id);
252 rv = NGHTTP2_ERR_CALLBACK_FAILURE;
255 if (status != APR_SUCCESS) {
256 /* count this as consumed explicitly as no one will read it */
257 nghttp2_session_consume(session->ngh2, stream_id, len);
262 static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
263 uint32_t error_code, void *userp)
265 h2_session *session = (h2_session *)userp;
269 stream = get_stream(session, stream_id);
272 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
273 H2_STRM_LOG(APLOGNO(03065), stream,
274 "closing with err=%d %s"),
275 (int)error_code, h2_h2_err_description(error_code));
276 h2_stream_rst(stream, error_code);
282 static int on_begin_headers_cb(nghttp2_session *ngh2,
283 const nghttp2_frame *frame, void *userp)
285 h2_session *session = (h2_session *)userp;
288 /* We may see HEADERs at the start of a stream or after all DATA
289 * streams to carry trailers. */
291 s = get_stream(session, frame->hd.stream_id);
296 s = h2_session_open_stream(userp, frame->hd.stream_id, 0);
298 return s? 0 : NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
301 static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
302 const uint8_t *name, size_t namelen,
303 const uint8_t *value, size_t valuelen,
307 h2_session *session = (h2_session *)userp;
312 stream = get_stream(session, frame->hd.stream_id);
314 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(02920)
315 "h2_stream(%ld-%d): on_header unknown stream",
316 session->id, (int)frame->hd.stream_id);
317 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
320 status = h2_stream_add_header(stream, (const char *)name, namelen,
321 (const char *)value, valuelen);
322 if (status != APR_SUCCESS && !h2_stream_is_ready(stream)) {
323 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
329 * nghttp2 session has received a complete frame. Most, it uses
330 * for processing of internal state. HEADER and DATA frames however
331 * we need to handle ourself.
333 static int on_frame_recv_cb(nghttp2_session *ng2s,
334 const nghttp2_frame *frame,
337 h2_session *session = (h2_session *)userp;
340 if (APLOGcdebug(session->c)) {
343 h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
344 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
345 H2_SSSN_LOG(APLOGNO(03066), session,
346 "recv FRAME[%s], frames=%ld/%ld (r/s)"),
347 buffer, (long)session->frames_received,
348 (long)session->frames_sent);
351 ++session->frames_received;
352 switch (frame->hd.type) {
353 case NGHTTP2_HEADERS:
354 /* This can be HEADERS for a new stream, defining the request,
355 * or HEADER may come after DATA at the end of a stream as in
357 stream = get_stream(session, frame->hd.stream_id);
359 h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags);
363 stream = get_stream(session, frame->hd.stream_id);
365 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
366 H2_STRM_LOG(APLOGNO(02923), stream,
367 "DATA, len=%ld, flags=%d"),
368 (long)frame->hd.length, frame->hd.flags);
369 h2_stream_recv_frame(stream, NGHTTP2_DATA, frame->hd.flags);
372 case NGHTTP2_PRIORITY:
373 session->reprioritize = 1;
374 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
375 "h2_stream(%ld-%d): PRIORITY frame "
376 " weight=%d, dependsOn=%d, exclusive=%d",
377 session->id, (int)frame->hd.stream_id,
378 frame->priority.pri_spec.weight,
379 frame->priority.pri_spec.stream_id,
380 frame->priority.pri_spec.exclusive);
382 case NGHTTP2_WINDOW_UPDATE:
383 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
384 "h2_stream(%ld-%d): WINDOW_UPDATE incr=%d",
385 session->id, (int)frame->hd.stream_id,
386 frame->window_update.window_size_increment);
388 case NGHTTP2_RST_STREAM:
389 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03067)
390 "h2_stream(%ld-%d): RST_STREAM by client, errror=%d",
391 session->id, (int)frame->hd.stream_id,
392 (int)frame->rst_stream.error_code);
393 stream = get_stream(session, frame->hd.stream_id);
394 if (stream && stream->initiated_on) {
395 ++session->pushes_reset;
398 ++session->streams_reset;
402 if (frame->goaway.error_code == 0
403 && frame->goaway.last_stream_id == ((1u << 31) - 1)) {
404 /* shutdown notice. Should not come from a client... */
405 session->remote.accepting = 0;
408 session->remote.accepted_max = frame->goaway.last_stream_id;
409 dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY,
410 frame->goaway.error_code, NULL);
414 if (APLOGctrace2(session->c)) {
417 h2_util_frame_print(frame, buffer,
418 sizeof(buffer)/sizeof(buffer[0]));
419 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
420 H2_SSSN_MSG(session, "on_frame_rcv %s"), buffer);
427 static int h2_session_continue_data(h2_session *session) {
428 if (h2_mplx_has_master_events(session->mplx)) {
431 if (h2_conn_io_needs_flush(&session->io)) {
437 static char immortal_zeros[H2_MAX_PADLEN];
439 static int on_send_data_cb(nghttp2_session *ngh2,
440 nghttp2_frame *frame,
441 const uint8_t *framehd,
443 nghttp2_data_source *source,
446 apr_status_t status = APR_SUCCESS;
447 h2_session *session = (h2_session *)userp;
448 int stream_id = (int)frame->hd.stream_id;
449 unsigned char padlen;
453 apr_off_t len = length;
457 if (!h2_session_continue_data(session)) {
458 return NGHTTP2_ERR_WOULDBLOCK;
461 if (frame->data.padlen > H2_MAX_PADLEN) {
462 return NGHTTP2_ERR_PROTO;
464 padlen = (unsigned char)frame->data.padlen;
466 stream = get_stream(session, stream_id);
468 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
470 "h2_stream(%ld-%d): send_data, stream not found",
471 session->id, (int)stream_id);
472 return NGHTTP2_ERR_CALLBACK_FAILURE;
475 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
476 H2_STRM_MSG(stream, "send_data_cb for %ld bytes"),
479 status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
480 if (padlen && status == APR_SUCCESS) {
481 status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
484 if (status != APR_SUCCESS) {
485 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
486 H2_STRM_MSG(stream, "writing frame header"));
487 return NGHTTP2_ERR_CALLBACK_FAILURE;
490 status = h2_stream_read_to(stream, session->bbtmp, &len, &eos);
491 if (status != APR_SUCCESS) {
492 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
493 H2_STRM_MSG(stream, "send_data_cb, reading stream"));
494 apr_brigade_cleanup(session->bbtmp);
495 return NGHTTP2_ERR_CALLBACK_FAILURE;
497 else if (len != length) {
498 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
499 H2_STRM_MSG(stream, "send_data_cb, wanted %ld bytes, "
500 "got %ld from stream"), (long)length, (long)len);
501 apr_brigade_cleanup(session->bbtmp);
502 return NGHTTP2_ERR_CALLBACK_FAILURE;
506 b = apr_bucket_immortal_create(immortal_zeros, padlen,
507 session->c->bucket_alloc);
508 APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
511 status = h2_conn_io_pass(&session->io, session->bbtmp);
512 apr_brigade_cleanup(session->bbtmp);
514 if (status == APR_SUCCESS) {
515 stream->out_data_frames++;
516 stream->out_data_octets += length;
520 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
521 H2_STRM_LOG(APLOGNO(02925), stream, "failed send_data_cb"));
522 return NGHTTP2_ERR_CALLBACK_FAILURE;
526 static int on_frame_send_cb(nghttp2_session *ngh2,
527 const nghttp2_frame *frame,
530 h2_session *session = user_data;
532 int stream_id = frame->hd.stream_id;
534 ++session->frames_sent;
535 switch (frame->hd.type) {
536 case NGHTTP2_PUSH_PROMISE:
537 /* PUSH_PROMISE we report on the promised stream */
538 stream_id = frame->push_promise.promised_stream_id;
544 if (APLOGcdebug(session->c)) {
547 h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
548 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
549 H2_SSSN_LOG(APLOGNO(03068), session,
550 "sent FRAME[%s], frames=%ld/%ld (r/s)"),
551 buffer, (long)session->frames_received,
552 (long)session->frames_sent);
555 stream = get_stream(session, stream_id);
557 h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags);
562 #ifdef H2_NG2_INVALID_HEADER_CB
563 static int on_invalid_header_cb(nghttp2_session *ngh2,
564 const nghttp2_frame *frame,
565 const uint8_t *name, size_t namelen,
566 const uint8_t *value, size_t valuelen,
567 uint8_t flags, void *user_data)
569 h2_session *session = user_data;
572 if (APLOGcdebug(session->c)) {
573 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03456)
574 "h2_stream(%ld-%d): invalid header '%s: %s'",
575 session->id, (int)frame->hd.stream_id,
576 apr_pstrndup(session->pool, (const char *)name, namelen),
577 apr_pstrndup(session->pool, (const char *)value, valuelen));
579 stream = get_stream(session, frame->hd.stream_id);
581 h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
587 #define NGH2_SET_CALLBACK(callbacks, name, fn)\
588 nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
590 static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
592 int rv = nghttp2_session_callbacks_new(pcb);
594 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
595 APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
596 nghttp2_strerror(rv));
600 NGH2_SET_CALLBACK(*pcb, send, send_cb);
601 NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
602 NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
603 NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
604 NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
605 NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
606 NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
607 NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
608 NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
609 #ifdef H2_NG2_INVALID_HEADER_CB
610 NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb);
615 static apr_status_t h2_session_shutdown_notice(h2_session *session)
620 if (!session->local.accepting) {
624 nghttp2_submit_shutdown_notice(session->ngh2);
625 session->local.accepting = 0;
626 status = nghttp2_session_send(session->ngh2);
627 if (status == APR_SUCCESS) {
628 status = h2_conn_io_flush(&session->io);
630 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
631 H2_SSSN_LOG(APLOGNO(03457), session, "sent shutdown notice"));
635 static apr_status_t h2_session_shutdown(h2_session *session, int error,
636 const char *msg, int force_close)
638 apr_status_t status = APR_SUCCESS;
641 if (session->local.shutdown) {
645 msg = nghttp2_strerror(error);
648 if (error || force_close) {
649 /* not a graceful shutdown, we want to leave...
650 * Do not start further streams that are waiting to be scheduled.
651 * Find out the max stream id that we habe been processed or
652 * are still actively working on.
653 * Remove all streams greater than this number without submitting
654 * a RST_STREAM frame, since that should be clear from the GOAWAY
656 session->local.accepted_max = h2_mplx_shutdown(session->mplx);
657 session->local.error = error;
660 /* graceful shutdown. we will continue processing all streams
661 * we have, but no longer accept new ones. Report the max stream
662 * we have received and discard all new ones. */
665 session->local.accepting = 0;
666 session->local.shutdown = 1;
667 if (!session->c->aborted) {
668 nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
669 session->local.accepted_max,
670 error, (uint8_t*)msg, msg? strlen(msg):0);
671 status = nghttp2_session_send(session->ngh2);
672 if (status == APR_SUCCESS) {
673 status = h2_conn_io_flush(&session->io);
675 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
676 H2_SSSN_LOG(APLOGNO(03069), session,
677 "sent GOAWAY, err=%d, msg=%s"), error, msg? msg : "");
679 dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
683 static apr_status_t session_cleanup(h2_session *session, const char *trigger)
685 conn_rec *c = session->c;
686 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
687 H2_SSSN_MSG(session, "pool_cleanup"));
689 if (session->state != H2_SESSION_ST_DONE
690 && session->state != H2_SESSION_ST_INIT) {
691 /* Not good. The connection is being torn down and we have
692 * not sent a goaway. This is considered a protocol error and
693 * the client has to assume that any streams "in flight" may have
694 * been processed and are not safe to retry.
695 * As clients with idle connection may only learn about a closed
696 * connection when sending the next request, this has the effect
697 * that at least this one request will fail.
699 ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
700 H2_SSSN_LOG(APLOGNO(03199), session,
701 "connection disappeared without proper "
702 "goodbye, clients will be confused, should not happen"));
705 transit(session, trigger, H2_SESSION_ST_CLEANUP);
706 h2_mplx_set_consumed_cb(session->mplx, NULL, NULL);
707 h2_mplx_release_and_join(session->mplx, session->iowait);
708 session->mplx = NULL;
710 ap_assert(session->ngh2);
711 nghttp2_session_del(session->ngh2);
712 session->ngh2 = NULL;
719 static apr_status_t session_pool_cleanup(void *data)
723 h2_ctx *ctx = h2_ctx_get(c, 0);
725 if (ctx && (session = h2_ctx_session_get(ctx))) {
726 /* if the session is still there, now is the last chance
727 * to perform cleanup. Normally, cleanup should have happened
728 * earlier in the connection pre_close. Main reason is that
729 * any ongoing requests on slave connections might still access
730 * data which has, at this time, already been freed. An example
731 * is mod_ssl that uses request hooks. */
732 ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
733 H2_SSSN_LOG(APLOGNO(10020), session,
734 "session cleanup triggered by pool cleanup. "
735 "this should have happened earlier already."));
736 return session_cleanup(session, "pool cleanup");
741 static apr_status_t h2_session_create_int(h2_session **psession,
747 nghttp2_session_callbacks *callbacks = NULL;
748 nghttp2_option *options = NULL;
749 apr_allocator_t *allocator;
750 apr_thread_mutex_t *mutex;
752 apr_pool_t *pool = NULL;
758 status = apr_allocator_create(&allocator);
759 if (status != APR_SUCCESS) {
762 apr_allocator_max_free_set(allocator, ap_max_mem_free);
763 apr_pool_create_ex(&pool, c->pool, NULL, allocator);
765 apr_allocator_destroy(allocator);
768 apr_pool_tag(pool, "h2_session");
769 apr_allocator_owner_set(allocator, pool);
770 status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool);
771 if (status != APR_SUCCESS) {
772 apr_pool_destroy(pool);
775 apr_allocator_mutex_set(allocator, mutex);
777 session = apr_pcalloc(pool, sizeof(h2_session));
786 session->s = h2_ctx_server_get(ctx);
787 session->pool = pool;
788 session->config = h2_config_sget(session->s);
789 session->workers = workers;
791 session->state = H2_SESSION_ST_INIT;
792 session->local.accepting = 1;
793 session->remote.accepting = 1;
795 session->max_stream_count = h2_config_geti(session->config,
796 H2_CONF_MAX_STREAMS);
797 session->max_stream_mem = h2_config_geti(session->config,
798 H2_CONF_STREAM_MAX_MEM);
800 status = apr_thread_cond_create(&session->iowait, session->pool);
801 if (status != APR_SUCCESS) {
802 apr_pool_destroy(pool);
806 session->monitor = apr_pcalloc(pool, sizeof(h2_stream_monitor));
807 if (session->monitor == NULL) {
808 apr_pool_destroy(pool);
811 session->monitor->ctx = session;
812 session->monitor->on_state_enter = on_stream_state_enter;
813 session->monitor->on_state_event = on_stream_state_event;
815 session->mplx = h2_mplx_create(c, session->pool, session->config,
818 h2_mplx_set_consumed_cb(session->mplx, update_window, session);
820 /* Install the connection input filter that feeds the session */
821 session->cin = h2_filter_cin_create(session->pool,
822 h2_session_receive, session);
823 ap_add_input_filter("H2_IN", session->cin, r, c);
825 h2_conn_io_init(&session->io, c, session->config);
826 session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
828 status = init_callbacks(c, &callbacks);
829 if (status != APR_SUCCESS) {
830 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927)
831 "nghttp2: error in init_callbacks");
832 apr_pool_destroy(pool);
836 rv = nghttp2_option_new(&options);
838 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
839 APLOGNO(02928) "nghttp2_option_new: %s",
840 nghttp2_strerror(rv));
841 apr_pool_destroy(pool);
844 nghttp2_option_set_peer_max_concurrent_streams(
845 options, (uint32_t)session->max_stream_count);
846 /* We need to handle window updates ourself, otherwise we
847 * get flooded by nghttp2. */
848 nghttp2_option_set_no_auto_window_update(options, 1);
850 rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
852 nghttp2_session_callbacks_del(callbacks);
853 nghttp2_option_del(options);
856 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
857 APLOGNO(02929) "nghttp2_session_server_new: %s",
858 nghttp2_strerror(rv));
859 apr_pool_destroy(pool);
863 n = h2_config_geti(session->config, H2_CONF_PUSH_DIARY_SIZE);
864 session->push_diary = h2_push_diary_create(session->pool, n);
866 if (APLOGcdebug(c)) {
867 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
868 H2_SSSN_LOG(APLOGNO(03200), session,
869 "created, max_streams=%d, stream_mem=%d, "
870 "workers_limit=%d, workers_max=%d, "
871 "push_diary(type=%d,N=%d)"),
872 (int)session->max_stream_count,
873 (int)session->max_stream_mem,
874 session->mplx->workers_limit,
875 session->mplx->workers_max,
876 session->push_diary->dtype,
877 (int)session->push_diary->N);
880 apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
884 apr_status_t h2_session_create(h2_session **psession,
885 conn_rec *c, h2_ctx *ctx, h2_workers *workers)
887 return h2_session_create_int(psession, c, NULL, ctx, workers);
890 apr_status_t h2_session_rcreate(h2_session **psession,
891 request_rec *r, h2_ctx *ctx, h2_workers *workers)
893 return h2_session_create_int(psession, r->connection, r, ctx, workers);
896 static apr_status_t h2_session_start(h2_session *session, int *rv)
898 apr_status_t status = APR_SUCCESS;
899 nghttp2_settings_entry settings[3];
904 /* Start the conversation by submitting our SETTINGS frame */
911 /* 'h2c' mode: we should have a 'HTTP2-Settings' header with
912 * base64 encoded client settings. */
913 s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
915 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r,
917 "HTTP2-Settings header missing in request");
921 dlen = h2_util_base64url_decode(&cs, s, session->pool);
923 if (APLOGrdebug(session->r)) {
925 h2_util_hex_dump(buffer, 128, (char*)cs, dlen);
926 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r, APLOGNO(03070)
927 "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)",
928 s, buffer, (int)dlen);
931 *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL);
934 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
935 APLOGNO(02932) "nghttp2_session_upgrade: %s",
936 nghttp2_strerror(*rv));
940 /* Now we need to auto-open stream 1 for the request we got. */
941 stream = h2_session_open_stream(session, 1, 0);
943 status = APR_EGENERAL;
944 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
945 APLOGNO(02933) "open stream 1: %s",
946 nghttp2_strerror(*rv));
950 status = h2_stream_set_request_rec(stream, session->r, 1);
951 if (status != APR_SUCCESS) {
957 settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
958 settings[slen].value = (uint32_t)session->max_stream_count;
960 win_size = h2_config_geti(session->config, H2_CONF_WIN_SIZE);
961 if (win_size != H2_INITIAL_WINDOW_SIZE) {
962 settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
963 settings[slen].value = win_size;
967 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
968 H2_SSSN_LOG(APLOGNO(03201), session,
969 "start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"),
970 (long)win_size, (int)session->max_stream_count);
971 *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE,
974 status = APR_EGENERAL;
975 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
976 H2_SSSN_LOG(APLOGNO(02935), session,
977 "nghttp2_submit_settings: %s"), nghttp2_strerror(*rv));
980 /* use maximum possible value for connection window size. We are only
981 * interested in per stream flow control. which have the initial window
982 * size configured above.
983 * Therefore, for our use, the connection window can only get in the
984 * way. Example: if we allow 100 streams with a 32KB window each, we
985 * buffer up to 3.2 MB of data. Unless we do separate connection window
986 * interim updates, any smaller connection window will lead to blocking
989 *rv = nghttp2_submit_window_update(session->ngh2, NGHTTP2_FLAG_NONE,
990 0, NGHTTP2_MAX_WINDOW_SIZE - win_size);
992 status = APR_EGENERAL;
993 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
994 H2_SSSN_LOG(APLOGNO(02970), session,
995 "nghttp2_submit_window_update: %s"),
996 nghttp2_strerror(*rv));
1003 static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,
1004 h2_headers *headers, apr_off_t len,
1007 static ssize_t stream_data_cb(nghttp2_session *ng2s,
1011 uint32_t *data_flags,
1012 nghttp2_data_source *source,
1015 h2_session *session = (h2_session *)puser;
1016 apr_off_t nread = length;
1018 apr_status_t status;
1022 /* The session wants to send more DATA for the stream. We need
1023 * to find out how much of the requested length we can send without
1025 * Indicate EOS when we encounter it or DEFERRED if the stream
1026 * should be suspended. Beware of trailers.
1032 stream = get_stream(session, stream_id);
1034 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
1036 "h2_stream(%ld-%d): data_cb, stream not found",
1037 session->id, (int)stream_id);
1038 return NGHTTP2_ERR_CALLBACK_FAILURE;
1041 status = h2_stream_out_prepare(stream, &nread, &eos, NULL);
1043 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
1044 H2_STRM_MSG(stream, "prepared no_copy, len=%ld, eos=%d"),
1046 *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
1053 case APR_ECONNRESET:
1057 /* If there is no data available, our session will automatically
1058 * suspend this stream and not ask for more data until we resume
1059 * it. Remember at our h2_stream that we need to do this.
1062 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1063 H2_STRM_LOG(APLOGNO(03071), stream, "suspending"));
1064 return NGHTTP2_ERR_DEFERRED;
1068 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
1069 H2_STRM_LOG(APLOGNO(02938), stream, "reading data"));
1070 return NGHTTP2_ERR_CALLBACK_FAILURE;
1074 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1076 return (ssize_t)nread;
1079 struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
1086 ngh = h2_util_ngheader_make_req(is->pool, push->req);
1087 nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id,
1088 ngh->nv, ngh->nvlen, NULL);
1090 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1091 H2_STRM_LOG(APLOGNO(03075), is,
1092 "submitting push promise fail: %s"), nghttp2_strerror(nid));
1095 ++session->pushes_promised;
1097 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1098 H2_STRM_LOG(APLOGNO(03076), is, "SERVER_PUSH %d for %s %s on %d"),
1099 nid, push->req->method, push->req->path, is->id);
1101 stream = h2_session_open_stream(session, nid, is->id);
1103 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1104 H2_STRM_LOG(APLOGNO(03077), stream,
1105 "failed to create stream obj %d"), nid);
1106 /* kill the push_promise */
1107 nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid,
1108 NGHTTP2_INTERNAL_ERROR);
1112 h2_session_set_prio(session, stream, push->priority);
1113 h2_stream_set_request(stream, push->req);
1114 ++session->unsent_promises;
1118 static int valid_weight(float f)
1121 return (w < NGHTTP2_MIN_WEIGHT? NGHTTP2_MIN_WEIGHT :
1122 (w > NGHTTP2_MAX_WEIGHT)? NGHTTP2_MAX_WEIGHT : w);
1125 apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
1126 const h2_priority *prio)
1128 apr_status_t status = APR_SUCCESS;
1129 #ifdef H2_NG2_CHANGE_PRIO
1130 nghttp2_stream *s_grandpa, *s_parent, *s;
1133 /* we treat this as a NOP */
1136 s = nghttp2_session_find_stream(session->ngh2, stream->id);
1138 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
1139 H2_STRM_MSG(stream, "lookup of nghttp2_stream failed"));
1143 s_parent = nghttp2_stream_get_parent(s);
1145 nghttp2_priority_spec ps;
1146 int id_parent, id_grandpa, w_parent, w;
1148 const char *ptype = "AFTER";
1149 h2_dependency dep = prio->dependency;
1151 id_parent = nghttp2_stream_get_stream_id(s_parent);
1152 s_grandpa = nghttp2_stream_get_parent(s_parent);
1154 id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
1157 /* parent of parent does not exist,
1158 * only possible if parent == root */
1159 dep = H2_DEPENDANT_AFTER;
1163 case H2_DEPENDANT_INTERLEAVED:
1164 /* PUSHed stream is to be interleaved with initiating stream.
1165 * It is made a sibling of the initiating stream and gets a
1166 * proportional weight [1, MAX_WEIGHT] of the initiaing
1169 ptype = "INTERLEAVED";
1170 w_parent = nghttp2_stream_get_weight(s_parent);
1171 w = valid_weight(w_parent * ((float)prio->weight / NGHTTP2_MAX_WEIGHT));
1172 nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
1175 case H2_DEPENDANT_BEFORE:
1176 /* PUSHed stream os to be sent BEFORE the initiating stream.
1177 * It gets the same weight as the initiating stream, replaces
1178 * that stream in the dependency tree and has the initiating
1182 w = w_parent = nghttp2_stream_get_weight(s_parent);
1183 nghttp2_priority_spec_init(&ps, stream->id, w_parent, 0);
1184 id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
1185 rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps);
1187 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03202)
1188 "h2_stream(%ld-%d): PUSH BEFORE, weight=%d, "
1189 "depends=%d, returned=%d",
1190 session->id, id_parent, ps.weight, ps.stream_id, rv);
1191 return APR_EGENERAL;
1193 nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
1196 case H2_DEPENDANT_AFTER:
1197 /* The PUSHed stream is to be sent after the initiating stream.
1198 * Give if the specified weight and let it depend on the intiating
1201 /* fall through, it's the default */
1203 nghttp2_priority_spec_init(&ps, id_parent, valid_weight(prio->weight), 0);
1208 rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps);
1209 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1210 ""H2_STRM_LOG(APLOGNO(03203), stream,
1211 "PUSH %s, weight=%d, depends=%d, returned=%d"),
1212 ptype, ps.weight, ps.stream_id, rv);
1213 status = (rv < 0)? APR_EGENERAL : APR_SUCCESS;
1224 int h2_session_push_enabled(h2_session *session)
1226 /* iff we can and they can and want */
1227 return (session->remote.accepting /* remote GOAWAY received */
1228 && h2_config_geti(session->config, H2_CONF_PUSH)
1229 && nghttp2_session_get_remote_settings(session->ngh2,
1230 NGHTTP2_SETTINGS_ENABLE_PUSH));
1233 static apr_status_t h2_session_send(h2_session *session)
1235 apr_interval_time_t saved_timeout;
1237 apr_socket_t *socket;
1239 socket = ap_get_conn_socket(session->c);
1241 apr_socket_timeout_get(socket, &saved_timeout);
1242 apr_socket_timeout_set(socket, session->s->timeout);
1245 rv = nghttp2_session_send(session->ngh2);
1248 apr_socket_timeout_set(socket, saved_timeout);
1250 session->have_written = 1;
1251 if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) {
1252 if (nghttp2_is_fatal(rv)) {
1253 dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
1254 return APR_EGENERAL;
1258 session->unsent_promises = 0;
1259 session->unsent_submits = 0;
1265 * headers for the stream are ready.
1267 static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,
1268 h2_headers *headers, apr_off_t len,
1271 apr_status_t status = APR_SUCCESS;
1275 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
1276 H2_STRM_MSG(stream, "on_headers"));
1277 if (headers->status < 100) {
1278 h2_stream_rst(stream, headers->status);
1281 else if (stream->has_response) {
1284 nh = h2_util_ngheader_make(stream->pool, headers->headers);
1285 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1286 H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), (int)nh->nvlen);
1287 rv = nghttp2_submit_trailer(session->ngh2, stream->id, nh->nv, nh->nvlen);
1291 nghttp2_data_provider provider, *pprovider = NULL;
1296 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1297 H2_STRM_LOG(APLOGNO(03073), stream, "submit response %d, REMOTE_WINDOW_SIZE=%u"),
1299 (unsigned int)nghttp2_session_get_stream_remote_window_size(session->ngh2, stream->id));
1301 if (!eos || len > 0) {
1302 memset(&provider, 0, sizeof(provider));
1303 provider.source.fd = stream->id;
1304 provider.read_callback = stream_data_cb;
1305 pprovider = &provider;
1308 /* If this stream is not a pushed one itself,
1309 * and HTTP/2 server push is enabled here,
1310 * and the response HTTP status is not sth >= 400,
1311 * and the remote side has pushing enabled,
1312 * -> find and perform any pushes on this stream
1313 * *before* we submit the stream response itself.
1314 * This helps clients avoid opening new streams on Link
1315 * headers that get pushed right afterwards.
1317 * *) the response code is relevant, as we do not want to
1318 * make pushes on 401 or 403 codes and friends.
1319 * And if we see a 304, we do not push either
1320 * as the client, having this resource in its cache, might
1321 * also have the pushed ones as well.
1323 if (!stream->initiated_on
1324 && !stream->has_response
1325 && stream->request && stream->request->method
1326 && !strcmp("GET", stream->request->method)
1327 && (headers->status < 400)
1328 && (headers->status != 304)
1329 && h2_session_push_enabled(session)) {
1331 h2_stream_submit_pushes(stream, headers);
1334 if (!stream->pref_priority) {
1335 stream->pref_priority = h2_stream_get_priority(stream, headers);
1337 h2_session_set_prio(session, stream, stream->pref_priority);
1339 hout = headers->headers;
1340 note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE);
1341 if (note && !strcmp("on", note)) {
1342 int32_t connFlowIn, connFlowOut;
1344 connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2);
1345 connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
1346 hout = apr_table_clone(stream->pool, hout);
1347 apr_table_setn(hout, "conn-flow-in",
1348 apr_itoa(stream->pool, connFlowIn));
1349 apr_table_setn(hout, "conn-flow-out",
1350 apr_itoa(stream->pool, connFlowOut));
1353 if (headers->status == 103
1354 && !h2_config_geti(session->config, H2_CONF_EARLY_HINTS)) {
1355 /* suppress sending this to the client, it might have triggered
1356 * pushes and served its purpose nevertheless */
1361 ngh = h2_util_ngheader_make_res(stream->pool, headers->status, hout);
1362 rv = nghttp2_submit_response(session->ngh2, stream->id,
1363 ngh->nv, ngh->nvlen, pprovider);
1364 stream->has_response = h2_headers_are_response(headers);
1365 session->have_written = 1;
1367 if (stream->initiated_on) {
1368 ++session->pushes_submitted;
1371 ++session->responses_submitted;
1376 if (nghttp2_is_fatal(rv)) {
1377 status = APR_EGENERAL;
1378 dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
1379 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
1380 APLOGNO(02940) "submit_response: %s",
1381 nghttp2_strerror(rv));
1384 ++session->unsent_submits;
1386 /* Unsent push promises are written immediately, as nghttp2
1387 * 1.5.0 realizes internal stream data structures only on
1388 * send and we might need them for other submits.
1389 * Also, to conserve memory, we send at least every 10 submits
1390 * so that nghttp2 does not buffer all outbound items too
1393 if (status == APR_SUCCESS
1394 && (session->unsent_promises || session->unsent_submits > 10)) {
1395 status = h2_session_send(session);
1401 * A stream was resumed as new response/output data arrived.
1403 static apr_status_t on_stream_resume(void *ctx, h2_stream *stream)
1405 h2_session *session = ctx;
1406 apr_status_t status = APR_EAGAIN;
1410 h2_headers *headers;
1413 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
1414 H2_STRM_MSG(stream, "on_resume"));
1418 status = h2_stream_out_prepare(stream, &len, &eos, &headers);
1419 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
1420 H2_STRM_MSG(stream, "prepared len=%ld, eos=%d"),
1423 status = on_stream_headers(session, stream, headers, len, eos);
1424 if (status != APR_SUCCESS || stream->rst_error) {
1429 else if (status != APR_EAGAIN) {
1430 /* we have DATA to send */
1431 if (!stream->has_response) {
1432 /* but no response */
1433 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1434 H2_STRM_LOG(APLOGNO(03466), stream, "no response, RST_STREAM"));
1435 h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
1438 rv = nghttp2_session_resume_data(session->ngh2, stream->id);
1439 session->have_written = 1;
1440 ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
1441 APLOG_ERR : APLOG_DEBUG, 0, session->c,
1442 H2_STRM_LOG(APLOGNO(02936), stream, "resumed"));
1447 static apr_status_t h2_session_receive(void *ctx, const char *data,
1448 apr_size_t len, apr_size_t *readlen)
1450 h2_session *session = ctx;
1454 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
1455 H2_SSSN_MSG(session, "feeding %ld bytes to nghttp2"),
1457 n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
1459 if (nghttp2_is_fatal((int)n)) {
1460 dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, (int)n, nghttp2_strerror((int)n));
1461 return APR_EGENERAL;
1466 session->io.bytes_read += n;
1472 static apr_status_t h2_session_read(h2_session *session, int block)
1474 apr_status_t status, rstatus = APR_EAGAIN;
1475 conn_rec *c = session->c;
1476 apr_off_t read_start = session->io.bytes_read;
1479 /* H2_IN filter handles all incoming data against the session.
1480 * We just pull at the filter chain to make it happen */
1481 status = ap_get_brigade(c->input_filters,
1482 session->bbtmp, AP_MODE_READBYTES,
1483 block? APR_BLOCK_READ : APR_NONBLOCK_READ,
1484 APR_BUCKET_BUFF_SIZE);
1485 /* get rid of any possible data we do not expect to get */
1486 apr_brigade_cleanup(session->bbtmp);
1490 /* successful read, reset our idle timers */
1491 rstatus = APR_SUCCESS;
1493 /* successful blocked read, try unblocked to
1503 if (session->io.bytes_read == read_start) {
1504 /* first attempt failed */
1505 if (APR_STATUS_IS_ETIMEDOUT(status)
1506 || APR_STATUS_IS_ECONNABORTED(status)
1507 || APR_STATUS_IS_ECONNRESET(status)
1508 || APR_STATUS_IS_EOF(status)
1509 || APR_STATUS_IS_EBADF(status)) {
1510 /* common status for a client that has left */
1511 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
1512 H2_SSSN_MSG(session, "input gone"));
1515 /* uncommon status, log on INFO so that we see this */
1516 ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c,
1517 H2_SSSN_LOG(APLOGNO(02950), session,
1518 "error reading, terminating"));
1522 /* subsequent failure after success(es), return initial
1526 if ((session->io.bytes_read - read_start) > (64*1024)) {
1527 /* read enough in one go, give write a chance */
1528 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
1529 H2_SSSN_MSG(session, "read 64k, returning"));
1536 static const char *StateNames[] = {
1537 "INIT", /* H2_SESSION_ST_INIT */
1538 "DONE", /* H2_SESSION_ST_DONE */
1539 "IDLE", /* H2_SESSION_ST_IDLE */
1540 "BUSY", /* H2_SESSION_ST_BUSY */
1541 "WAIT", /* H2_SESSION_ST_WAIT */
1542 "CLEANUP", /* H2_SESSION_ST_CLEANUP */
1545 const char *h2_session_state_str(h2_session_state state)
1547 if (state >= (sizeof(StateNames)/sizeof(StateNames[0]))) {
1550 return StateNames[state];
1553 static void update_child_status(h2_session *session, int status, const char *msg)
1555 /* Assume that we also change code/msg when something really happened and
1556 * avoid updating the scoreboard in between */
1557 if (session->last_status_code != status
1558 || session->last_status_msg != msg) {
1559 apr_snprintf(session->status, sizeof(session->status),
1560 "%s, streams: %d/%d/%d/%d/%d (open/recv/resp/push/rst)",
1562 (int)session->open_streams,
1563 (int)session->remote.emitted_count,
1564 (int)session->responses_submitted,
1565 (int)session->pushes_submitted,
1566 (int)session->pushes_reset + session->streams_reset);
1567 ap_update_child_status_descr(session->c->sbh, status, session->status);
1571 static void transit(h2_session *session, const char *action, h2_session_state nstate)
1573 if (session->state != nstate) {
1574 int loglvl = APLOG_DEBUG;
1575 if ((session->state == H2_SESSION_ST_BUSY && nstate == H2_SESSION_ST_WAIT)
1576 || (session->state == H2_SESSION_ST_WAIT && nstate == H2_SESSION_ST_BUSY)){
1577 loglvl = APLOG_TRACE1;
1579 ap_log_cerror(APLOG_MARK, loglvl, 0, session->c,
1580 H2_SSSN_LOG(APLOGNO(03078), session,
1581 "transit [%s] -- %s --> [%s]"),
1582 h2_session_state_str(session->state), action,
1583 h2_session_state_str(nstate));
1584 session->state = nstate;
1585 switch (session->state) {
1586 case H2_SESSION_ST_IDLE:
1587 update_child_status(session, (session->open_streams == 0?
1588 SERVER_BUSY_KEEPALIVE
1589 : SERVER_BUSY_READ), "idle");
1591 case H2_SESSION_ST_DONE:
1592 update_child_status(session, SERVER_CLOSING, "done");
1601 static void h2_session_ev_init(h2_session *session, int arg, const char *msg)
1603 switch (session->state) {
1604 case H2_SESSION_ST_INIT:
1605 transit(session, "init", H2_SESSION_ST_BUSY);
1613 static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
1615 cleanup_unprocessed_streams(session);
1616 if (!session->remote.shutdown) {
1617 update_child_status(session, SERVER_CLOSING, "local goaway");
1619 transit(session, "local goaway", H2_SESSION_ST_DONE);
1622 static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char *msg)
1624 if (!session->remote.shutdown) {
1625 session->remote.error = arg;
1626 session->remote.accepting = 0;
1627 session->remote.shutdown = 1;
1628 cleanup_unprocessed_streams(session);
1629 update_child_status(session, SERVER_CLOSING, "remote goaway");
1630 transit(session, "remote goaway", H2_SESSION_ST_DONE);
1634 static void h2_session_ev_conn_error(h2_session *session, int arg, const char *msg)
1636 switch (session->state) {
1637 case H2_SESSION_ST_INIT:
1638 case H2_SESSION_ST_DONE:
1640 transit(session, "conn error", H2_SESSION_ST_DONE);
1644 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1645 H2_SSSN_LOG(APLOGNO(03401), session,
1646 "conn error -> shutdown"));
1647 h2_session_shutdown(session, arg, msg, 0);
1652 static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg)
1654 if (!session->local.shutdown) {
1655 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1656 H2_SSSN_LOG(APLOGNO(03402), session,
1657 "proto error -> shutdown"));
1658 h2_session_shutdown(session, arg, msg, 0);
1662 static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char *msg)
1664 transit(session, msg, H2_SESSION_ST_DONE);
1665 if (!session->local.shutdown) {
1666 h2_session_shutdown(session, arg, msg, 1);
1670 static void h2_session_ev_no_io(h2_session *session, int arg, const char *msg)
1672 switch (session->state) {
1673 case H2_SESSION_ST_BUSY:
1674 /* Nothing to READ, nothing to WRITE on the master connection.
1676 * - we wait for the client to send us sth
1677 * - we wait for started tasks to produce output
1678 * - we have finished all streams and the client has sent GO_AWAY
1680 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
1681 H2_SSSN_MSG(session, "NO_IO event, %d streams open"),
1682 session->open_streams);
1683 h2_conn_io_flush(&session->io);
1684 if (session->open_streams > 0) {
1685 if (h2_mplx_awaits_data(session->mplx)) {
1686 /* waiting for at least one stream to produce data */
1687 transit(session, "no io", H2_SESSION_ST_WAIT);
1690 /* we have streams open, and all are submitted and none
1691 * is suspended. The only thing keeping us from WRITEing
1692 * more must be the flow control.
1693 * This means we only wait for WINDOW_UPDATE from the
1694 * client and can block on READ. */
1695 transit(session, "no io (flow wait)", H2_SESSION_ST_IDLE);
1696 session->idle_until = apr_time_now() + session->s->timeout;
1697 session->keep_sync_until = session->idle_until;
1698 /* Make sure we have flushed all previously written output
1699 * so that the client will react. */
1700 if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
1701 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
1706 else if (session->local.accepting) {
1707 /* When we have no streams, but accept new, switch to idle */
1708 apr_time_t now = apr_time_now();
1709 transit(session, "no io (keepalive)", H2_SESSION_ST_IDLE);
1710 session->idle_until = (session->remote.emitted_count?
1711 session->s->keep_alive_timeout :
1712 session->s->timeout) + now;
1713 session->keep_sync_until = now + apr_time_from_sec(1);
1716 /* We are no longer accepting new streams and there are
1717 * none left. Time to leave. */
1718 h2_session_shutdown(session, arg, msg, 0);
1719 transit(session, "no io", H2_SESSION_ST_DONE);
1728 static void h2_session_ev_data_read(h2_session *session, int arg, const char *msg)
1730 switch (session->state) {
1731 case H2_SESSION_ST_IDLE:
1732 case H2_SESSION_ST_WAIT:
1733 transit(session, "data read", H2_SESSION_ST_BUSY);
1741 static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg)
1743 switch (session->state) {
1744 case H2_SESSION_ST_DONE:
1748 transit(session, "nghttp2 done", H2_SESSION_ST_DONE);
1753 static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char *msg)
1755 switch (session->state) {
1756 case H2_SESSION_ST_DONE:
1760 h2_session_shutdown_notice(session);
1765 static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
1767 h2_session_shutdown(session, arg, msg, 1);
1770 static void ev_stream_open(h2_session *session, h2_stream *stream)
1772 switch (session->state) {
1773 case H2_SESSION_ST_IDLE:
1774 if (session->open_streams == 1) {
1775 /* enter tiomeout, since we have a stream again */
1776 session->idle_until = (session->s->timeout + apr_time_now());
1783 ap_assert(!stream->scheduled);
1784 if (h2_stream_prep_processing(stream) == APR_SUCCESS) {
1785 h2_mplx_process(session->mplx, stream, stream_pri_cmp, session);
1788 h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
1792 static void ev_stream_closed(h2_session *session, h2_stream *stream)
1796 if (H2_STREAM_CLIENT_INITIATED(stream->id)
1797 && (stream->id > session->local.completed_max)) {
1798 session->local.completed_max = stream->id;
1800 switch (session->state) {
1801 case H2_SESSION_ST_IDLE:
1802 if (session->open_streams == 0) {
1803 /* enter keepalive timeout, since we no longer have streams */
1804 session->idle_until = (session->s->keep_alive_timeout
1812 /* The stream might have data in the buffers of the main connection.
1813 * We can only free the allocated resources once all had been written.
1814 * Send a special buckets on the connection that gets destroyed when
1815 * all preceding data has been handled. On its destruction, it is safe
1816 * to purge all resources of the stream. */
1817 b = h2_bucket_eos_create(session->c->bucket_alloc, stream);
1818 APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
1819 h2_conn_io_pass(&session->io, session->bbtmp);
1820 apr_brigade_cleanup(session->bbtmp);
1823 static void on_stream_state_enter(void *ctx, h2_stream *stream)
1825 h2_session *session = ctx;
1826 /* stream entered a new state */
1827 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
1828 H2_STRM_MSG(stream, "entered state"));
1829 switch (stream->state) {
1830 case H2_SS_IDLE: /* stream was created */
1831 ++session->open_streams;
1832 if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
1833 ++session->remote.emitted_count;
1834 if (stream->id > session->remote.emitted_max) {
1835 session->remote.emitted_max = stream->id;
1836 session->local.accepted_max = stream->id;
1840 if (stream->id > session->local.emitted_max) {
1841 ++session->local.emitted_count;
1842 session->remote.emitted_max = stream->id;
1846 case H2_SS_OPEN: /* stream has request headers */
1847 case H2_SS_RSVD_L: /* stream has request headers */
1848 ev_stream_open(session, stream);
1850 case H2_SS_CLOSED_L: /* stream output was closed */
1852 case H2_SS_CLOSED_R: /* stream input was closed */
1854 case H2_SS_CLOSED: /* stream in+out were closed */
1855 --session->open_streams;
1856 ev_stream_closed(session, stream);
1859 h2_mplx_stream_cleanup(session->mplx, stream);
1866 static void on_stream_state_event(void *ctx, h2_stream *stream,
1867 h2_stream_event_t ev)
1869 h2_session *session = ctx;
1871 case H2_SEV_CANCELLED:
1872 if (session->state != H2_SESSION_ST_DONE) {
1873 nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
1874 stream->id, stream->rst_error);
1883 static void dispatch_event(h2_session *session, h2_session_event_t ev,
1884 int arg, const char *msg)
1887 case H2_SESSION_EV_INIT:
1888 h2_session_ev_init(session, arg, msg);
1890 case H2_SESSION_EV_LOCAL_GOAWAY:
1891 h2_session_ev_local_goaway(session, arg, msg);
1893 case H2_SESSION_EV_REMOTE_GOAWAY:
1894 h2_session_ev_remote_goaway(session, arg, msg);
1896 case H2_SESSION_EV_CONN_ERROR:
1897 h2_session_ev_conn_error(session, arg, msg);
1899 case H2_SESSION_EV_PROTO_ERROR:
1900 h2_session_ev_proto_error(session, arg, msg);
1902 case H2_SESSION_EV_CONN_TIMEOUT:
1903 h2_session_ev_conn_timeout(session, arg, msg);
1905 case H2_SESSION_EV_NO_IO:
1906 h2_session_ev_no_io(session, arg, msg);
1908 case H2_SESSION_EV_DATA_READ:
1909 h2_session_ev_data_read(session, arg, msg);
1911 case H2_SESSION_EV_NGH2_DONE:
1912 h2_session_ev_ngh2_done(session, arg, msg);
1914 case H2_SESSION_EV_MPM_STOPPING:
1915 h2_session_ev_mpm_stopping(session, arg, msg);
1917 case H2_SESSION_EV_PRE_CLOSE:
1918 h2_session_ev_pre_close(session, arg, msg);
1921 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
1922 H2_SSSN_MSG(session, "unknown event %d"), ev);
1927 /* trigger window updates, stream resumes and submits */
1928 static apr_status_t dispatch_master(h2_session *session) {
1929 apr_status_t status;
1931 status = h2_mplx_dispatch_master_events(session->mplx,
1932 on_stream_resume, session);
1933 if (status == APR_EAGAIN) {
1934 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
1935 H2_SSSN_MSG(session, "no master event available"));
1937 else if (status != APR_SUCCESS) {
1938 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
1939 H2_SSSN_MSG(session, "dispatch error"));
1940 dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
1941 H2_ERR_INTERNAL_ERROR, "dispatch error");
1946 static const int MAX_WAIT_MICROS = 200 * 1000;
1948 apr_status_t h2_session_process(h2_session *session, int async)
1950 apr_status_t status = APR_SUCCESS;
1951 conn_rec *c = session->c;
1952 int rv, mpm_state, trace = APLOGctrace3(c);
1955 ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
1956 H2_SSSN_MSG(session, "process start, async=%d"), async);
1959 while (session->state != H2_SESSION_ST_DONE) {
1960 session->have_read = session->have_written = 0;
1962 if (session->local.accepting
1963 && !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
1964 if (mpm_state == AP_MPMQ_STOPPING) {
1965 dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
1969 session->status[0] = '\0';
1971 switch (session->state) {
1972 case H2_SESSION_ST_INIT:
1973 ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
1974 if (!h2_is_acceptable_connection(c, 1)) {
1975 update_child_status(session, SERVER_BUSY_READ,
1976 "inadequate security");
1977 h2_session_shutdown(session,
1978 NGHTTP2_INADEQUATE_SECURITY, NULL, 1);
1981 update_child_status(session, SERVER_BUSY_READ, "init");
1982 status = h2_session_start(session, &rv);
1983 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
1984 H2_SSSN_LOG(APLOGNO(03079), session,
1985 "started on %s:%d"),
1986 session->s->server_hostname,
1987 c->local_addr->port);
1988 if (status != APR_SUCCESS) {
1989 dispatch_event(session,
1990 H2_SESSION_EV_CONN_ERROR, 0, NULL);
1992 dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
1996 case H2_SESSION_ST_IDLE:
1997 /* We trust our connection into the default timeout/keepalive
1998 * handling of the core filters/mpm iff:
1999 * - keep_sync_until is not set
2000 * - we have an async mpm
2001 * - we have no open streams to process
2002 * - we are not sitting on a Upgrade: request
2003 * - we already have seen at least one request
2005 if (!session->keep_sync_until && async && !session->open_streams
2006 && !session->r && session->remote.emitted_count) {
2008 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
2009 H2_SSSN_MSG(session,
2010 "nonblock read, %d streams open"),
2011 session->open_streams);
2013 h2_conn_io_flush(&session->io);
2014 status = h2_session_read(session, 0);
2016 if (status == APR_SUCCESS) {
2017 session->have_read = 1;
2018 dispatch_event(session, H2_SESSION_EV_DATA_READ, 0, NULL);
2020 else if (APR_STATUS_IS_EAGAIN(status)
2021 || APR_STATUS_IS_TIMEUP(status)) {
2022 if (apr_time_now() > session->idle_until) {
2023 dispatch_event(session,
2024 H2_SESSION_EV_CONN_TIMEOUT, 0, NULL);
2027 status = APR_EAGAIN;
2032 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
2033 H2_SSSN_LOG(APLOGNO(03403), session,
2035 dispatch_event(session,
2036 H2_SESSION_EV_CONN_ERROR, 0, "timeout");
2040 /* make certain, we send everything before we idle */
2041 h2_conn_io_flush(&session->io);
2043 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
2044 H2_SSSN_MSG(session,
2045 "sync, stutter 1-sec, %d streams open"),
2046 session->open_streams);
2048 /* We wait in smaller increments, using a 1 second timeout.
2049 * That gives us the chance to check for MPMQ_STOPPING often.
2051 status = h2_mplx_idle(session->mplx);
2052 if (status != APR_SUCCESS) {
2053 dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
2054 H2_ERR_ENHANCE_YOUR_CALM, "less is more");
2056 h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1));
2057 status = h2_session_read(session, 1);
2058 if (status == APR_SUCCESS) {
2059 session->have_read = 1;
2060 dispatch_event(session, H2_SESSION_EV_DATA_READ, 0, NULL);
2062 else if (status == APR_EAGAIN) {
2063 /* nothing to read */
2065 else if (APR_STATUS_IS_TIMEUP(status)) {
2066 apr_time_t now = apr_time_now();
2067 if (now > session->keep_sync_until) {
2068 /* if we are on an async mpm, now is the time that
2069 * we may dare to pass control to it. */
2070 session->keep_sync_until = 0;
2072 if (now > session->idle_until) {
2074 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
2075 H2_SSSN_MSG(session,
2076 "keepalive timeout"));
2078 dispatch_event(session,
2079 H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
2082 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
2083 H2_SSSN_MSG(session,
2084 "keepalive, %f sec left"),
2085 (session->idle_until - now) / 1000000.0f);
2087 /* continue reading handling */
2089 else if (APR_STATUS_IS_ECONNABORTED(status)
2090 || APR_STATUS_IS_ECONNRESET(status)
2091 || APR_STATUS_IS_EOF(status)
2092 || APR_STATUS_IS_EBADF(status)) {
2093 ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
2094 H2_SSSN_MSG(session, "input gone"));
2095 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
2098 ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
2099 H2_SSSN_MSG(session,
2100 "(1 sec timeout) read failed"));
2101 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error");
2106 case H2_SESSION_ST_BUSY:
2107 if (nghttp2_session_want_read(session->ngh2)) {
2108 ap_update_child_status(session->c->sbh, SERVER_BUSY_READ, NULL);
2109 h2_filter_cin_timeout_set(session->cin, session->s->timeout);
2110 status = h2_session_read(session, 0);
2111 if (status == APR_SUCCESS) {
2112 session->have_read = 1;
2113 dispatch_event(session, H2_SESSION_EV_DATA_READ, 0, NULL);
2115 else if (status == APR_EAGAIN) {
2116 /* nothing to read */
2118 else if (APR_STATUS_IS_TIMEUP(status)) {
2119 dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, NULL);
2123 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
2127 status = dispatch_master(session);
2128 if (status != APR_SUCCESS && status != APR_EAGAIN) {
2132 if (nghttp2_session_want_write(session->ngh2)) {
2133 ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL);
2134 status = h2_session_send(session);
2135 if (status == APR_SUCCESS) {
2136 status = h2_conn_io_flush(&session->io);
2138 if (status != APR_SUCCESS) {
2139 dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
2140 H2_ERR_INTERNAL_ERROR, "writing");
2145 if (session->have_read || session->have_written) {
2146 if (session->wait_us) {
2147 session->wait_us = 0;
2150 else if (!nghttp2_session_want_write(session->ngh2)) {
2151 dispatch_event(session, H2_SESSION_EV_NO_IO, 0, NULL);
2155 case H2_SESSION_ST_WAIT:
2156 if (session->wait_us <= 0) {
2157 session->wait_us = 10;
2158 if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
2159 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
2164 /* repeating, increase timer for graceful backoff */
2165 session->wait_us = H2MIN(session->wait_us*2, MAX_WAIT_MICROS);
2169 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
2170 "h2_session: wait for data, %ld micros",
2171 (long)session->wait_us);
2173 status = h2_mplx_out_trywait(session->mplx, session->wait_us,
2175 if (status == APR_SUCCESS) {
2176 session->wait_us = 0;
2177 dispatch_event(session, H2_SESSION_EV_DATA_READ, 0, NULL);
2179 else if (APR_STATUS_IS_TIMEUP(status)) {
2180 /* go back to checking all inputs again */
2181 transit(session, "wait cycle", session->local.shutdown?
2182 H2_SESSION_ST_DONE : H2_SESSION_ST_BUSY);
2184 else if (APR_STATUS_IS_ECONNRESET(status)
2185 || APR_STATUS_IS_ECONNABORTED(status)) {
2186 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
2189 ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, c,
2190 H2_SSSN_LOG(APLOGNO(03404), session,
2191 "waiting on conditional"));
2192 h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR,
2193 "cond wait error", 0);
2198 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
2199 H2_SSSN_LOG(APLOGNO(03080), session,
2201 dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, 0, NULL);
2205 if (!nghttp2_session_want_read(session->ngh2)
2206 && !nghttp2_session_want_write(session->ngh2)) {
2207 dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL);
2209 if (session->reprioritize) {
2210 h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
2211 session->reprioritize = 0;
2217 ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
2218 H2_SSSN_MSG(session, "process returns"));
2221 if ((session->state != H2_SESSION_ST_DONE)
2222 && (APR_STATUS_IS_EOF(status)
2223 || APR_STATUS_IS_ECONNRESET(status)
2224 || APR_STATUS_IS_ECONNABORTED(status))) {
2225 dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
2228 return (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
2231 apr_status_t h2_session_pre_close(h2_session *session, int async)
2233 apr_status_t status;
2235 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
2236 H2_SSSN_MSG(session, "pre_close"));
2237 dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0,
2238 (session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL);
2239 status = session_cleanup(session, "pre_close");
2240 if (status == APR_SUCCESS) {
2241 /* no one should hold a reference to this session any longer and
2242 * the h2_ctx was removed from the connection.
2243 * Take the pool (and thus all subpools etc. down now, instead of
2244 * during cleanup of main connection pool. */
2245 apr_pool_destroy(session->pool);