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.
17 #include <apr_thread_cond.h>
18 #include <apr_base64.h>
19 #include <apr_strings.h>
22 #include <http_core.h>
23 #include <http_config.h>
26 #include "h2_private.h"
27 #include "h2_bucket_eoc.h"
28 #include "h2_bucket_eos.h"
29 #include "h2_config.h"
33 #include "h2_request.h"
34 #include "h2_response.h"
35 #include "h2_stream.h"
36 #include "h2_stream_set.h"
37 #include "h2_from_h1.h"
39 #include "h2_session.h"
41 #include "h2_version.h"
42 #include "h2_workers.h"
44 static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
46 static int h2_session_status_from_apr_status(apr_status_t rv)
48 if (rv == APR_SUCCESS) {
49 return NGHTTP2_NO_ERROR;
51 else if (APR_STATUS_IS_EAGAIN(rv)) {
52 return NGHTTP2_ERR_WOULDBLOCK;
54 else if (APR_STATUS_IS_EOF(rv)) {
55 return NGHTTP2_ERR_EOF;
57 return NGHTTP2_ERR_PROTO;
60 h2_stream *h2_session_open_stream(h2_session *session, int stream_id)
63 apr_pool_t *stream_pool;
64 if (session->aborted) {
69 stream_pool = session->spare;
70 session->spare = NULL;
73 apr_pool_create(&stream_pool, session->pool);
76 stream = h2_stream_open(stream_id, stream_pool, session);
78 h2_stream_set_add(session->streams, stream);
79 if (H2_STREAM_CLIENT_INITIATED(stream_id)
80 && stream_id > session->max_stream_received) {
81 session->max_stream_received = stream->id;
87 apr_status_t h2_session_flush(h2_session *session)
89 return h2_conn_io_flush(&session->io);
93 * Determine the importance of streams when scheduling tasks.
94 * - if both stream depend on the same one, compare weights
95 * - if one stream is closer to the root, prioritize that one
96 * - if both are on the same level, use the weight of their root
99 static int spri_cmp(int sid1, nghttp2_stream *s1,
100 int sid2, nghttp2_stream *s2, h2_session *session)
102 nghttp2_stream *p1, *p2;
104 p1 = nghttp2_stream_get_parent(s1);
105 p2 = nghttp2_stream_get_parent(s2);
110 w1 = nghttp2_stream_get_weight(s1);
111 w2 = nghttp2_stream_get_weight(s2);
115 /* stream 1 closer to root */
119 /* stream 2 closer to root */
122 return spri_cmp(sid1, p1, sid2, p2, session);
125 static int stream_pri_cmp(int sid1, int sid2, void *ctx)
127 h2_session *session = ctx;
128 nghttp2_stream *s1, *s2;
130 s1 = nghttp2_session_find_stream(session->ngh2, sid1);
131 s2 = nghttp2_session_find_stream(session->ngh2, sid2);
142 return spri_cmp(sid1, s1, sid2, s2, session);
145 static apr_status_t stream_end_headers(h2_session *session,
146 h2_stream *stream, int eos)
149 return h2_stream_schedule(stream, eos, stream_pri_cmp, session);
153 * Callback when nghttp2 wants to send bytes back to the client.
155 static ssize_t send_cb(nghttp2_session *ngh2,
156 const uint8_t *data, size_t length,
157 int flags, void *userp)
159 h2_session *session = (h2_session *)userp;
164 status = h2_conn_io_write(&session->io, (const char *)data, length);
165 if (status == APR_SUCCESS) {
168 if (APR_STATUS_IS_EAGAIN(status)) {
169 return NGHTTP2_ERR_WOULDBLOCK;
171 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
172 "h2_session: send error");
173 return h2_session_status_from_apr_status(status);
176 static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
177 const nghttp2_frame *frame,
178 int error, void *userp)
180 h2_session *session = (h2_session *)userp;
183 if (session->aborted) {
184 return NGHTTP2_ERR_CALLBACK_FAILURE;
186 if (APLOGctrace2(session->c)) {
189 frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
190 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
191 "h2_session: callback on_invalid_frame_recv error=%d %s",
197 static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
199 const uint8_t *data, size_t len, void *userp)
202 h2_session *session = (h2_session *)userp;
207 if (session->aborted) {
208 return NGHTTP2_ERR_CALLBACK_FAILURE;
210 stream = h2_session_get_stream(session, stream_id);
212 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
214 "h2_session: stream(%ld-%d): on_data_chunk for unknown stream",
215 session->id, (int)stream_id);
216 rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
217 NGHTTP2_INTERNAL_ERROR);
218 if (nghttp2_is_fatal(rv)) {
219 return NGHTTP2_ERR_CALLBACK_FAILURE;
224 status = h2_stream_write_data(stream, (const char *)data, len);
225 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
226 "h2_stream(%ld-%d): written DATA, length %d",
227 session->id, stream_id, (int)len);
228 if (status != APR_SUCCESS) {
229 rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
230 H2_STREAM_RST(stream, H2_ERR_INTERNAL_ERROR));
231 if (nghttp2_is_fatal(rv)) {
232 return NGHTTP2_ERR_CALLBACK_FAILURE;
238 static apr_status_t stream_release(h2_session *session,
243 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
244 "h2_stream(%ld-%d): handled, closing",
245 session->id, (int)stream->id);
246 if (stream->id > session->max_stream_handled) {
247 session->max_stream_handled = stream->id;
251 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
252 "h2_stream(%ld-%d): closing with err=%d %s",
253 session->id, (int)stream->id, (int)error_code,
254 h2_h2_err_description(error_code));
255 h2_stream_rst(stream, error_code);
258 return h2_conn_io_writeb(&session->io,
259 h2_bucket_eos_create(session->c->bucket_alloc,
263 static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
264 uint32_t error_code, void *userp)
266 h2_session *session = (h2_session *)userp;
270 if (session->aborted) {
271 return NGHTTP2_ERR_CALLBACK_FAILURE;
273 stream = h2_session_get_stream(session, stream_id);
275 stream_release(session, stream, error_code);
279 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
280 "h2_stream(%ld-%d): closed, error=%d",
281 session->id, (int)stream_id, error_code);
287 static int on_begin_headers_cb(nghttp2_session *ngh2,
288 const nghttp2_frame *frame, void *userp)
292 /* This starts a new stream. */
294 s = h2_session_open_stream((h2_session *)userp, frame->hd.stream_id);
295 return s? 0 : NGHTTP2_ERR_CALLBACK_FAILURE;
298 static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
299 const uint8_t *name, size_t namelen,
300 const uint8_t *value, size_t valuelen,
304 h2_session *session = (h2_session *)userp;
310 if (session->aborted) {
311 return NGHTTP2_ERR_CALLBACK_FAILURE;
314 stream = h2_session_get_stream(session, frame->hd.stream_id);
316 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
318 "h2_session: stream(%ld-%d): on_header for unknown stream",
319 session->id, (int)frame->hd.stream_id);
320 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
323 status = h2_stream_add_header(stream, (const char *)name, namelen,
324 (const char *)value, valuelen);
326 if (status != APR_SUCCESS) {
327 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
333 * nghttp2 session has received a complete frame. Most, it uses
334 * for processing of internal state. HEADER and DATA frames however
335 * we need to handle ourself.
337 static int on_frame_recv_cb(nghttp2_session *ng2s,
338 const nghttp2_frame *frame,
341 h2_session *session = (h2_session *)userp;
342 apr_status_t status = APR_SUCCESS;
345 if (session->aborted) {
346 return NGHTTP2_ERR_CALLBACK_FAILURE;
349 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
350 "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id,
351 (long)session->frames_received, frame->hd.type);
353 ++session->frames_received;
354 switch (frame->hd.type) {
355 case NGHTTP2_HEADERS:
356 stream = h2_session_get_stream(session, frame->hd.stream_id);
358 int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
359 status = stream_end_headers(session, stream, eos);
366 stream = h2_session_get_stream(session, frame->hd.stream_id);
368 int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
370 status = h2_stream_close_input(stream);
377 case NGHTTP2_PRIORITY:
378 session->reprioritize = 1;
379 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
380 "h2_session: stream(%ld-%d): PRIORITY frame "
381 " weight=%d, dependsOn=%d, exclusive=%d",
382 session->id, (int)frame->hd.stream_id,
383 frame->priority.pri_spec.weight,
384 frame->priority.pri_spec.stream_id,
385 frame->priority.pri_spec.exclusive);
387 case NGHTTP2_WINDOW_UPDATE:
388 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
389 "h2_session: stream(%ld-%d): WINDOW_UPDATE "
391 session->id, (int)frame->hd.stream_id,
392 frame->window_update.window_size_increment);
395 if (APLOGctrace2(session->c)) {
398 frame_print(frame, buffer,
399 sizeof(buffer)/sizeof(buffer[0]));
400 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
401 "h2_session: on_frame_rcv %s", buffer);
406 if (status != APR_SUCCESS) {
409 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
411 "h2_session: stream(%ld-%d): error handling frame",
412 session->id, (int)frame->hd.stream_id);
413 rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
415 NGHTTP2_INTERNAL_ERROR);
416 if (nghttp2_is_fatal(rv)) {
417 return NGHTTP2_ERR_CALLBACK_FAILURE;
424 static apr_status_t pass_data(void *ctx,
425 const char *data, apr_off_t length)
427 return h2_conn_io_write(&((h2_session*)ctx)->io, data, length);
431 static char immortal_zeros[H2_MAX_PADLEN];
433 static int on_send_data_cb(nghttp2_session *ngh2,
434 nghttp2_frame *frame,
435 const uint8_t *framehd,
437 nghttp2_data_source *source,
440 apr_status_t status = APR_SUCCESS;
441 h2_session *session = (h2_session *)userp;
442 int stream_id = (int)frame->hd.stream_id;
443 unsigned char padlen;
449 if (session->aborted) {
450 return NGHTTP2_ERR_CALLBACK_FAILURE;
453 if (frame->data.padlen > H2_MAX_PADLEN) {
454 return NGHTTP2_ERR_PROTO;
456 padlen = (unsigned char)frame->data.padlen;
458 stream = h2_session_get_stream(session, stream_id);
460 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
462 "h2_stream(%ld-%d): send_data",
463 session->id, (int)stream_id);
464 return NGHTTP2_ERR_CALLBACK_FAILURE;
467 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
468 "h2_stream(%ld-%d): send_data_cb for %ld bytes",
469 session->id, (int)stream_id, (long)length);
471 if (h2_conn_io_is_buffered(&session->io)) {
472 status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
473 if (status == APR_SUCCESS) {
475 status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
478 if (status == APR_SUCCESS) {
479 apr_off_t len = length;
480 status = h2_stream_readx(stream, pass_data, session, &len, &eos);
481 if (status == APR_SUCCESS && len != length) {
486 if (status == APR_SUCCESS && padlen) {
488 status = h2_conn_io_write(&session->io, immortal_zeros, padlen);
495 char *header = apr_pcalloc(stream->pool, 10);
496 memcpy(header, (const char *)framehd, 9);
498 header[9] = (char)padlen;
500 b = apr_bucket_pool_create(header, padlen? 10 : 9,
501 stream->pool, session->c->bucket_alloc);
502 status = h2_conn_io_writeb(&session->io, b);
504 if (status == APR_SUCCESS) {
505 apr_off_t len = length;
506 status = h2_stream_read_to(stream, session->io.output, &len, &eos);
507 if (status == APR_SUCCESS && len != length) {
512 if (status == APR_SUCCESS && padlen) {
513 b = apr_bucket_immortal_create(immortal_zeros, padlen,
514 session->c->bucket_alloc);
515 status = h2_conn_io_writeb(&session->io, b);
520 if (status == APR_SUCCESS) {
521 stream->data_frames_sent++;
522 h2_conn_io_consider_flush(&session->io);
526 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
528 "h2_stream(%ld-%d): failed send_data_cb",
529 session->id, (int)stream_id);
532 return h2_session_status_from_apr_status(status);
535 #define NGH2_SET_CALLBACK(callbacks, name, fn)\
536 nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
538 static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
540 int rv = nghttp2_session_callbacks_new(pcb);
542 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
543 APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
544 nghttp2_strerror(rv));
548 NGH2_SET_CALLBACK(*pcb, send, send_cb);
549 NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
550 NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
551 NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
552 NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
553 NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
554 NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
555 NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
560 static apr_status_t session_pool_cleanup(void *data)
562 h2_session *session = data;
564 /* keep us from destroying the pool, since that is already ongoing. */
565 session->pool = NULL;
566 h2_session_destroy(session);
570 static h2_session *h2_session_create_int(conn_rec *c,
575 nghttp2_session_callbacks *callbacks = NULL;
576 nghttp2_option *options = NULL;
578 apr_pool_t *pool = NULL;
579 apr_status_t status = apr_pool_create(&pool, r? r->pool : c->pool);
581 if (status != APR_SUCCESS) {
585 session = apr_pcalloc(pool, sizeof(h2_session));
592 apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup);
594 session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS);
595 session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM);
597 session->pool = pool;
599 status = apr_thread_cond_create(&session->iowait, session->pool);
600 if (status != APR_SUCCESS) {
604 session->streams = h2_stream_set_create(session->pool, session->max_stream_count);
606 session->workers = workers;
607 session->mplx = h2_mplx_create(c, session->pool, workers);
609 h2_conn_io_init(&session->io, c);
610 session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
612 status = init_callbacks(c, &callbacks);
613 if (status != APR_SUCCESS) {
614 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927)
615 "nghttp2: error in init_callbacks");
616 h2_session_destroy(session);
620 rv = nghttp2_option_new(&options);
622 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
623 APLOGNO(02928) "nghttp2_option_new: %s",
624 nghttp2_strerror(rv));
625 h2_session_destroy(session);
629 nghttp2_option_set_peer_max_concurrent_streams(options,
630 (uint32_t)session->max_stream_count);
632 /* We need to handle window updates ourself, otherwise we
633 * get flooded by nghttp2. */
634 nghttp2_option_set_no_auto_window_update(options, 1);
636 rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
638 nghttp2_session_callbacks_del(callbacks);
639 nghttp2_option_del(options);
642 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
643 APLOGNO(02929) "nghttp2_session_server_new: %s",
644 nghttp2_strerror(rv));
645 h2_session_destroy(session);
653 h2_session *h2_session_create(conn_rec *c, h2_config *config,
656 return h2_session_create_int(c, NULL, config, workers);
659 h2_session *h2_session_rcreate(request_rec *r, h2_config *config,
662 return h2_session_create_int(r->connection, r, config, workers);
665 void h2_session_cleanup(h2_session *session)
667 AP_DEBUG_ASSERT(session);
669 nghttp2_session_del(session->ngh2);
670 session->ngh2 = NULL;
672 if (session->spare) {
673 apr_pool_destroy(session->spare);
674 session->spare = NULL;
677 h2_mplx_release_and_join(session->mplx, session->iowait);
678 session->mplx = NULL;
682 void h2_session_destroy(h2_session *session)
684 AP_DEBUG_ASSERT(session);
685 h2_session_cleanup(session);
687 if (session->streams) {
688 if (!h2_stream_set_is_empty(session->streams)) {
689 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
690 "h2_session(%ld): destroy, %d streams open",
691 session->id, (int)h2_stream_set_size(session->streams));
693 h2_stream_set_destroy(session->streams);
694 session->streams = NULL;
697 apr_pool_destroy(session->pool);
702 void h2_session_eoc_callback(h2_session *session)
704 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
705 "session(%ld): cleanup and destroy", session->id);
706 apr_pool_cleanup_kill(session->pool, session, session_pool_cleanup);
707 h2_session_destroy(session);
710 static apr_status_t h2_session_abort_int(h2_session *session, int reason)
712 AP_DEBUG_ASSERT(session);
713 if (!session->aborted) {
714 session->aborted = 1;
717 if (NGHTTP2_ERR_EOF == reason) {
718 /* This is our way of indication that the connection is
719 * gone. No use to send any GOAWAY frames. */
720 nghttp2_session_terminate_session(session->ngh2, reason);
723 nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
724 session->max_stream_received,
726 nghttp2_session_send(session->ngh2);
729 const char *err = nghttp2_strerror(reason);
731 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
732 "session(%ld): aborting session, reason=%d %s",
733 session->id, reason, err);
735 /* The connection might still be there and we shut down
736 * with GOAWAY and reason information. */
737 nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
738 session->max_stream_received,
739 reason, (const uint8_t *)err,
741 nghttp2_session_send(session->ngh2);
744 h2_mplx_abort(session->mplx);
749 apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv)
751 AP_DEBUG_ASSERT(session);
753 rv = NGHTTP2_ERR_PROTO;
756 rv = NGHTTP2_ERR_NOMEM;
758 case APR_SUCCESS: /* all fine, just... */
759 case APR_EOF: /* client closed its end... */
760 case APR_TIMEUP: /* got bored waiting... */
761 rv = 0; /* ...gracefully shut down */
763 case APR_EBADF: /* connection unusable, terminate silently */
765 if (APR_STATUS_IS_ECONNABORTED(reason)
766 || APR_STATUS_IS_ECONNRESET(reason)
767 || APR_STATUS_IS_EBADF(reason)) {
768 rv = NGHTTP2_ERR_EOF;
773 return h2_session_abort_int(session, rv);
776 apr_status_t h2_session_start(h2_session *session, int *rv)
778 apr_status_t status = APR_SUCCESS;
780 nghttp2_settings_entry settings[3];
784 AP_DEBUG_ASSERT(session);
785 /* Start the conversation by submitting our SETTINGS frame */
787 config = h2_config_get(session->c);
793 /* better for vhost matching */
794 config = h2_config_rget(session->r);
796 /* 'h2c' mode: we should have a 'HTTP2-Settings' header with
797 * base64 encoded client settings. */
798 s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
800 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r,
802 "HTTP2-Settings header missing in request");
806 dlen = h2_util_base64url_decode(&cs, s, session->pool);
808 if (APLOGrdebug(session->r)) {
810 h2_util_hex_dump(buffer, 128, (char*)cs, dlen);
811 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r,
812 "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)",
813 s, buffer, (int)dlen);
816 *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL);
819 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
820 APLOGNO(02932) "nghttp2_session_upgrade: %s",
821 nghttp2_strerror(*rv));
825 /* Now we need to auto-open stream 1 for the request we got. */
826 stream = h2_session_open_stream(session, 1);
828 status = APR_EGENERAL;
829 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
830 APLOGNO(02933) "open stream 1: %s",
831 nghttp2_strerror(*rv));
835 status = h2_stream_set_request(stream, session->r);
836 if (status != APR_SUCCESS) {
839 status = stream_end_headers(session, stream, 1);
840 if (status != APR_SUCCESS) {
846 settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
847 settings[slen].value = (uint32_t)session->max_stream_count;
849 i = h2_config_geti(config, H2_CONF_WIN_SIZE);
850 if (i != H2_INITIAL_WINDOW_SIZE) {
851 settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
852 settings[slen].value = i;
856 *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE,
859 status = APR_EGENERAL;
860 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
861 APLOGNO(02935) "nghttp2_submit_settings: %s",
862 nghttp2_strerror(*rv));
873 static int resume_on_data(void *ctx, h2_stream *stream) {
874 resume_ctx *rctx = (resume_ctx*)ctx;
875 h2_session *session = rctx->session;
876 AP_DEBUG_ASSERT(session);
877 AP_DEBUG_ASSERT(stream);
879 if (h2_stream_is_suspended(stream)) {
880 if (h2_mplx_out_has_data_for(stream->session->mplx, stream->id)) {
882 h2_stream_set_suspended(stream, 0);
883 ++rctx->resume_count;
885 rv = nghttp2_session_resume_data(session->ngh2, stream->id);
886 ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
887 APLOG_ERR : APLOG_DEBUG, 0, session->c,
889 "h2_stream(%ld-%d): resuming stream %s",
890 session->id, stream->id, nghttp2_strerror(rv));
896 static int h2_session_resume_streams_with_data(h2_session *session) {
897 AP_DEBUG_ASSERT(session);
898 if (!h2_stream_set_is_empty(session->streams)
899 && session->mplx && !session->aborted) {
902 ctx.session = session;
903 ctx.resume_count = 0;
905 /* Resume all streams where we have data in the out queue and
906 * which had been suspended before. */
907 h2_stream_set_iter(session->streams, resume_on_data, &ctx);
908 return ctx.resume_count;
913 static void update_window(void *ctx, int stream_id, apr_off_t bytes_read)
915 h2_session *session = (h2_session*)ctx;
916 nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
919 h2_stream *h2_session_get_stream(h2_session *session, int stream_id)
921 if (!session->last_stream || stream_id != session->last_stream->id) {
922 session->last_stream = h2_stream_set_get(session->streams, stream_id);
924 return session->last_stream;
927 /* h2_io_on_read_cb implementation that offers the data read
928 * directly to the session for consumption.
930 static apr_status_t session_receive(const char *data, apr_size_t len,
931 apr_size_t *readlen, int *done,
934 h2_session *session = (h2_session *)puser;
935 AP_DEBUG_ASSERT(session);
937 ssize_t n = nghttp2_session_mem_recv(session->ngh2,
938 (const uint8_t *)data, len);
940 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL,
942 "h2_session: nghttp2_session_mem_recv error %d",
944 if (nghttp2_is_fatal((int)n)) {
946 h2_session_abort_int(session, (int)n);
957 apr_status_t h2_session_close(h2_session *session)
959 AP_DEBUG_ASSERT(session);
960 if (!session->aborted) {
961 h2_session_abort_int(session, 0);
963 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c,
964 "h2_session: closing, writing eoc");
966 h2_session_cleanup(session);
967 return h2_conn_io_writeb(&session->io,
968 h2_bucket_eoc_create(session->c->bucket_alloc,
972 static ssize_t stream_data_cb(nghttp2_session *ng2s,
976 uint32_t *data_flags,
977 nghttp2_data_source *source,
980 h2_session *session = (h2_session *)puser;
981 apr_off_t nread = length;
985 AP_DEBUG_ASSERT(session);
987 /* The session wants to send more DATA for the stream. We need
988 * to find out how much of the requested length we can send without
990 * Indicate EOS when we encounter it or DEFERRED if the stream
991 * should be suspended.
992 * TODO: for handling of TRAILERS, the EOF indication needs
993 * to be aware of that.
999 stream = h2_session_get_stream(session, stream_id);
1001 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
1003 "h2_stream(%ld-%d): data requested but stream not found",
1004 session->id, (int)stream_id);
1005 return NGHTTP2_ERR_CALLBACK_FAILURE;
1008 AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream));
1010 status = h2_stream_prep_read(stream, &nread, &eos);
1012 *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
1019 case APR_ECONNRESET:
1020 return nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
1021 stream->id, stream->rst_error);
1024 /* If there is no data available, our session will automatically
1025 * suspend this stream and not ask for more data until we resume
1026 * it. Remember at our h2_stream that we need to do this.
1029 h2_stream_set_suspended(stream, 1);
1030 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1031 "h2_stream(%ld-%d): suspending stream",
1032 session->id, (int)stream_id);
1033 return NGHTTP2_ERR_DEFERRED;
1042 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
1043 APLOGNO(02938) "h2_stream(%ld-%d): reading data",
1044 session->id, (int)stream_id);
1045 return NGHTTP2_ERR_CALLBACK_FAILURE;
1049 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1052 return (ssize_t)nread;
1062 * Start submitting the response to a stream request. This is possible
1063 * once we have all the response headers. The response body will be
1064 * read by the session using the callback we supply.
1066 static apr_status_t submit_response(h2_session *session, h2_stream *stream)
1068 apr_status_t status = APR_SUCCESS;
1070 AP_DEBUG_ASSERT(session);
1071 AP_DEBUG_ASSERT(stream);
1072 AP_DEBUG_ASSERT(stream->response || stream->rst_error);
1074 if (stream->submitted) {
1075 rv = NGHTTP2_PROTOCOL_ERROR;
1077 else if (stream->response && stream->response->header) {
1078 nghttp2_data_provider provider;
1079 h2_response *response = stream->response;
1082 memset(&provider, 0, sizeof(provider));
1083 provider.source.fd = stream->id;
1084 provider.read_callback = stream_data_cb;
1086 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1087 "h2_stream(%ld-%d): submit response %d",
1088 session->id, stream->id, response->http_status);
1090 ngh = h2_util_ngheader_make_res(stream->pool, response->http_status,
1092 rv = nghttp2_submit_response(session->ngh2, response->stream_id,
1093 ngh->nv, ngh->nvlen, &provider);
1095 /* If the submit worked,
1096 * and this stream is not a pushed one itself,
1097 * and HTTP/2 server push is enabled here,
1098 * and the response is in the range 200-299 *),
1099 * and the remote side has pushing enabled,
1100 * -> find and perform any pushes on this stream
1102 * *) the response code is relevant, as we do not want to
1103 * make pushes on 401 or 403 codes, neiterh on 301/302
1104 * and friends. And if we see a 304, we do not push either
1105 * as the client, having this resource in its cache, might
1106 * also have the pushed ones as well.
1109 && !stream->initiated_on
1110 && h2_config_geti(h2_config_get(session->c), H2_CONF_PUSH)
1111 && H2_HTTP_2XX(response->http_status)
1112 && h2_session_push_enabled(session)) {
1114 h2_stream_submit_pushes(stream);
1118 int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
1120 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1121 "h2_stream(%ld-%d): RST_STREAM, err=%d",
1122 session->id, stream->id, err);
1124 rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
1128 stream->submitted = 1;
1130 if (nghttp2_is_fatal(rv)) {
1131 status = APR_EGENERAL;
1132 h2_session_abort_int(session, rv);
1133 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
1134 APLOGNO(02940) "submit_response: %s",
1135 nghttp2_strerror(rv));
1141 struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
1144 apr_status_t status;
1149 ngh = h2_util_ngheader_make_req(is->pool, push->req);
1150 nid = nghttp2_submit_push_promise(session->ngh2, 0, push->initiating_id,
1151 ngh->nv, ngh->nvlen, NULL);
1154 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1155 "h2_stream(%ld-%d): submitting push promise fail: %s",
1156 session->id, push->initiating_id,
1157 nghttp2_strerror(nid));
1161 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1162 "h2_stream(%ld-%d): promised new stream %d for %s %s",
1163 session->id, push->initiating_id, nid,
1164 push->req->method, push->req->path);
1166 stream = h2_session_open_stream(session, nid);
1168 h2_stream_set_h2_request(stream, is->id, push->req);
1169 status = stream_end_headers(session, stream, 1);
1170 if (status != APR_SUCCESS) {
1171 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
1172 "h2_stream(%ld-%d): scheduling push stream",
1173 session->id, stream->id);
1174 h2_stream_cleanup(stream);
1179 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1180 "h2_stream(%ld-%d): failed to create stream obj %d",
1181 session->id, push->initiating_id, nid);
1185 /* try to tell the client that it should not wait. */
1186 nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid,
1187 NGHTTP2_INTERNAL_ERROR);
1193 apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream)
1195 apr_pool_t *pool = h2_stream_detach_pool(stream);
1197 h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error);
1198 if (session->last_stream == stream) {
1199 session->last_stream = NULL;
1201 h2_stream_set_remove(session->streams, stream->id);
1202 h2_stream_destroy(stream);
1205 apr_pool_clear(pool);
1206 if (session->spare) {
1207 apr_pool_destroy(session->spare);
1209 session->spare = pool;
1214 static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
1217 size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
1219 switch (frame->hd.type) {
1220 case NGHTTP2_DATA: {
1221 return apr_snprintf(buffer, maxlen,
1222 "DATA[length=%d, flags=%d, stream=%d, padlen=%d]",
1223 (int)frame->hd.length, frame->hd.flags,
1224 frame->hd.stream_id, (int)frame->data.padlen);
1226 case NGHTTP2_HEADERS: {
1227 return apr_snprintf(buffer, maxlen,
1228 "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]",
1229 (int)frame->hd.length,
1230 !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
1231 frame->hd.stream_id,
1232 !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
1234 case NGHTTP2_PRIORITY: {
1235 return apr_snprintf(buffer, maxlen,
1236 "PRIORITY[length=%d, flags=%d, stream=%d]",
1237 (int)frame->hd.length,
1238 frame->hd.flags, frame->hd.stream_id);
1240 case NGHTTP2_RST_STREAM: {
1241 return apr_snprintf(buffer, maxlen,
1242 "RST_STREAM[length=%d, flags=%d, stream=%d]",
1243 (int)frame->hd.length,
1244 frame->hd.flags, frame->hd.stream_id);
1246 case NGHTTP2_SETTINGS: {
1247 if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
1248 return apr_snprintf(buffer, maxlen,
1249 "SETTINGS[ack=1, stream=%d]",
1250 frame->hd.stream_id);
1252 return apr_snprintf(buffer, maxlen,
1253 "SETTINGS[length=%d, stream=%d]",
1254 (int)frame->hd.length, frame->hd.stream_id);
1256 case NGHTTP2_PUSH_PROMISE: {
1257 return apr_snprintf(buffer, maxlen,
1258 "PUSH_PROMISE[length=%d, hend=%d, stream=%d]",
1259 (int)frame->hd.length,
1260 !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
1261 frame->hd.stream_id);
1263 case NGHTTP2_PING: {
1264 return apr_snprintf(buffer, maxlen,
1265 "PING[length=%d, ack=%d, stream=%d]",
1266 (int)frame->hd.length,
1267 frame->hd.flags&NGHTTP2_FLAG_ACK,
1268 frame->hd.stream_id);
1270 case NGHTTP2_GOAWAY: {
1271 size_t len = (frame->goaway.opaque_data_len < s_len)?
1272 frame->goaway.opaque_data_len : s_len-1;
1273 memcpy(scratch, frame->goaway.opaque_data, len);
1274 scratch[len+1] = '\0';
1275 return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s']",
1276 frame->goaway.error_code, scratch);
1278 case NGHTTP2_WINDOW_UPDATE: {
1279 return apr_snprintf(buffer, maxlen,
1280 "WINDOW_UPDATE[length=%d, stream=%d]",
1281 (int)frame->hd.length, frame->hd.stream_id);
1284 return apr_snprintf(buffer, maxlen,
1285 "FRAME[type=%d, length=%d, flags=%d, stream=%d]",
1286 frame->hd.type, (int)frame->hd.length,
1287 frame->hd.flags, frame->hd.stream_id);
1291 int h2_session_push_enabled(h2_session *session)
1293 return nghttp2_session_get_remote_settings(session->ngh2,
1294 NGHTTP2_SETTINGS_ENABLE_PUSH);
1298 apr_status_t h2_session_process(h2_session *session)
1300 apr_status_t status = APR_SUCCESS;
1301 apr_interval_time_t wait_micros = 0;
1302 static const int MAX_WAIT_MICROS = 200 * 1000;
1303 int got_streams = 0;
1305 while (!session->aborted && (nghttp2_session_want_read(session->ngh2)
1306 || nghttp2_session_want_write(session->ngh2))) {
1307 int have_written = 0;
1310 /* Send data as long as we have it and window sizes allow. We are
1311 * a server after all.
1313 if (nghttp2_session_want_write(session->ngh2)) {
1316 rv = nghttp2_session_send(session->ngh2);
1318 ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
1319 "h2_session: send: %s", nghttp2_strerror(rv));
1320 if (nghttp2_is_fatal(rv)) {
1321 h2_session_abort(session, status, rv);
1331 if (wait_micros > 0) {
1332 ap_log_cerror( APLOG_MARK, APLOG_TRACE3, 0, session->c,
1333 "h2_session: wait for data, %ld micros", (long)(wait_micros));
1334 h2_conn_io_pass(&session->io);
1335 status = h2_mplx_out_trywait(session->mplx, wait_micros, session->iowait);
1337 if (status == APR_TIMEUP) {
1338 if (wait_micros < MAX_WAIT_MICROS) {
1344 if (nghttp2_session_want_read(session->ngh2))
1347 * - and have no streams at all
1348 * - or have streams, but none is suspended or needs submit and
1349 * have nothing written on the last try
1351 * or, the other way around
1352 * - have only streams where data can be sent, but could
1355 * then we are waiting on frames from the client (for
1356 * example WINDOW_UPDATE or HEADER) and without new frames
1357 * from the client, we cannot make any progress,
1359 * and *then* we can safely do a blocking read.
1361 int may_block = (session->frames_received <= 1);
1364 may_block = (!have_written
1365 && !h2_stream_set_has_unsubmitted(session->streams)
1366 && !h2_stream_set_has_suspended(session->streams));
1374 h2_conn_io_flush(&session->io);
1375 if (session->c->cs) {
1376 session->c->cs->state = (got_streams? CONN_STATE_HANDLER
1377 : CONN_STATE_WRITE_COMPLETION);
1379 status = h2_conn_io_read(&session->io, APR_BLOCK_READ,
1380 session_receive, session);
1383 if (session->c->cs) {
1384 session->c->cs->state = CONN_STATE_HANDLER;
1386 status = h2_conn_io_read(&session->io, APR_NONBLOCK_READ,
1387 session_receive, session);
1391 case APR_SUCCESS: /* successful read, reset our idle timers */
1395 case APR_EAGAIN: /* non-blocking read, nothing there */
1398 if (APR_STATUS_IS_ETIMEDOUT(status)
1399 || APR_STATUS_IS_ECONNABORTED(status)
1400 || APR_STATUS_IS_ECONNRESET(status)
1401 || APR_STATUS_IS_EOF(status)
1402 || APR_STATUS_IS_EBADF(status)) {
1403 /* common status for a client that has left */
1404 ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
1405 "h2_session(%ld): terminating",
1407 /* Stolen from mod_reqtimeout to speed up lingering when
1408 * a read timeout happened.
1410 apr_table_setn(session->c->notes, "short-lingering-close", "1");
1413 /* uncommon status, log on INFO so that we see this */
1414 ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c,
1416 "h2_session(%ld): error reading, terminating",
1419 h2_session_abort(session, status, 0);
1424 got_streams = !h2_stream_set_is_empty(session->streams);
1428 if (session->reprioritize) {
1429 h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
1430 session->reprioritize = 0;
1433 if (!have_read && !have_written) {
1434 /* Nothing read or written. That means no data yet ready to
1435 * be send out. Slowly back off...
1437 if (wait_micros == 0) {
1442 if (h2_stream_set_has_open_input(session->streams)) {
1443 /* Check that any pending window updates are sent. */
1444 status = h2_mplx_in_update_windows(session->mplx, update_window, session);
1445 if (APR_STATUS_IS_EAGAIN(status)) {
1446 status = APR_SUCCESS;
1450 h2_session_resume_streams_with_data(session);
1452 if (h2_stream_set_has_unsubmitted(session->streams)) {
1453 /* If we have responses ready, submit them now. */
1454 while ((stream = h2_mplx_next_submit(session->mplx, session->streams))) {
1455 status = submit_response(session, stream);