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_config.h"
30 #include "h2_response.h"
31 #include "h2_stream.h"
32 #include "h2_stream_set.h"
33 #include "h2_from_h1.h"
35 #include "h2_session.h"
37 #include "h2_version.h"
38 #include "h2_workers.h"
40 static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
42 static int h2_session_status_from_apr_status(apr_status_t rv)
46 return NGHTTP2_NO_ERROR;
49 return NGHTTP2_ERR_WOULDBLOCK;
51 return NGHTTP2_ERR_EOF;
53 return NGHTTP2_ERR_PROTO;
57 static int stream_open(h2_session *session, int stream_id)
60 if (session->aborted) {
61 return NGHTTP2_ERR_CALLBACK_FAILURE;
64 stream = h2_mplx_open_io(session->mplx, stream_id);
66 h2_stream_set_add(session->streams, stream);
68 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
69 "h2_session: stream(%ld-%d): opened",
70 session->id, stream_id);
75 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
77 "h2_session: stream(%ld-%d): unable to create",
78 session->id, stream_id);
79 return NGHTTP2_ERR_INVALID_STREAM_ID;
82 static apr_status_t stream_end_headers(h2_session *session,
83 h2_stream *stream, int eos)
86 return h2_stream_write_eoh(stream, eos);
89 static apr_status_t send_data(h2_session *session, const char *data,
93 * Callback when nghttp2 wants to send bytes back to the client.
95 static ssize_t send_cb(nghttp2_session *ngh2,
96 const uint8_t *data, size_t length,
97 int flags, void *userp)
99 h2_session *session = (h2_session *)userp;
100 apr_status_t status = send_data(session, (const char *)data, length);
102 if (status == APR_SUCCESS) {
105 if (status == APR_EAGAIN || status == APR_TIMEUP) {
106 return NGHTTP2_ERR_WOULDBLOCK;
108 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
109 "h2_session: send error");
110 return h2_session_status_from_apr_status(status);
113 static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
114 const nghttp2_frame *frame,
115 int error, void *userp)
117 h2_session *session = (h2_session *)userp;
120 if (session->aborted) {
121 return NGHTTP2_ERR_CALLBACK_FAILURE;
123 if (APLOGctrace2(session->c)) {
126 frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
127 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
128 "h2_session: callback on_invalid_frame_recv error=%d %s",
134 static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
136 const uint8_t *data, size_t len, void *userp)
139 h2_session *session = (h2_session *)userp;
143 if (session->aborted) {
144 return NGHTTP2_ERR_CALLBACK_FAILURE;
146 stream = h2_stream_set_get(session->streams, stream_id);
148 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
150 "h2_session: stream(%ld-%d): on_data_chunk for unknown stream",
151 session->id, (int)stream_id);
152 rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
153 NGHTTP2_INTERNAL_ERROR);
154 if (nghttp2_is_fatal(rv)) {
155 return NGHTTP2_ERR_CALLBACK_FAILURE;
160 status = h2_stream_write_data(stream, (const char *)data, len);
161 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
162 "h2_stream(%ld-%d): written DATA, length %d",
163 session->id, stream_id, (int)len);
164 if (status != APR_SUCCESS) {
165 rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
166 NGHTTP2_INTERNAL_ERROR);
167 if (nghttp2_is_fatal(rv)) {
168 return NGHTTP2_ERR_CALLBACK_FAILURE;
174 static int before_frame_send_cb(nghttp2_session *ngh2,
175 const nghttp2_frame *frame,
178 h2_session *session = (h2_session *)userp;
181 if (session->aborted) {
182 return NGHTTP2_ERR_CALLBACK_FAILURE;
184 if (APLOGctrace2(session->c)) {
186 frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
187 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
188 "h2_session(%ld): before_frame_send %s",
189 session->id, buffer);
194 static int on_frame_send_cb(nghttp2_session *ngh2,
195 const nghttp2_frame *frame,
198 h2_session *session = (h2_session *)userp;
199 (void)ngh2; (void)frame;
200 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
201 "h2_session(%ld): on_frame_send", session->id);
205 static int on_frame_not_send_cb(nghttp2_session *ngh2,
206 const nghttp2_frame *frame,
207 int lib_error_code, void *userp)
209 h2_session *session = (h2_session *)userp;
212 if (APLOGctrace2(session->c)) {
215 frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
216 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
217 "h2_session: callback on_frame_not_send error=%d %s",
218 lib_error_code, buffer);
223 static apr_status_t stream_destroy(h2_session *session, h2_stream *stream)
225 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
226 "h2_stream(%ld-%d): closing", session->id, (int)stream->id);
227 h2_stream_set_remove(session->streams, stream);
228 return h2_mplx_cleanup_stream(session->mplx, stream);
231 static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
232 uint32_t error_code, void *userp)
234 h2_session *session = (h2_session *)userp;
237 if (session->aborted) {
238 return NGHTTP2_ERR_CALLBACK_FAILURE;
240 stream = h2_stream_set_get(session->streams, stream_id);
242 stream_destroy(session, stream);
246 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
247 "h2_stream(%ld-%d): close error %d",
248 session->id, (int)stream_id, error_code);
254 static int on_begin_headers_cb(nghttp2_session *ngh2,
255 const nghttp2_frame *frame, void *userp)
257 /* This starts a new stream. */
258 int rv = stream_open((h2_session *)userp, frame->hd.stream_id);
259 if (rv != NGHTTP2_ERR_CALLBACK_FAILURE) {
260 /* on_header_cb or on_frame_recv_cb will dectect that stream
261 does not exist and submit RST_STREAM. */
264 return NGHTTP2_ERR_CALLBACK_FAILURE;
267 static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
268 const uint8_t *name, size_t namelen,
269 const uint8_t *value, size_t valuelen,
273 h2_session *session = (h2_session *)userp;
277 if (session->aborted) {
278 return NGHTTP2_ERR_CALLBACK_FAILURE;
280 stream = h2_stream_set_get(session->streams,
281 frame->hd.stream_id);
283 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
285 "h2_session: stream(%ld-%d): on_header for unknown stream",
286 session->id, (int)frame->hd.stream_id);
287 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
290 status = h2_stream_write_header(stream,
291 (const char *)name, namelen,
292 (const char *)value, valuelen);
293 if (status != APR_SUCCESS) {
294 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
300 * nghttp2 session has received a complete frame. Most, it uses
301 * for processing of internal state. HEADER and DATA frames however
302 * we need to handle ourself.
304 static int on_frame_recv_cb(nghttp2_session *ng2s,
305 const nghttp2_frame *frame,
309 h2_session *session = (h2_session *)userp;
310 apr_status_t status = APR_SUCCESS;
311 if (session->aborted) {
312 return NGHTTP2_ERR_CALLBACK_FAILURE;
315 ++session->frames_received;
316 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
317 "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id,
318 (long)session->frames_received, frame->hd.type);
319 switch (frame->hd.type) {
320 case NGHTTP2_HEADERS: {
322 h2_stream * stream = h2_stream_set_get(session->streams,
323 frame->hd.stream_id);
324 if (stream == NULL) {
325 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
327 "h2_session: stream(%ld-%d): HEADERS frame "
328 "for unknown stream", session->id,
329 (int)frame->hd.stream_id);
330 rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
332 NGHTTP2_INTERNAL_ERROR);
333 if (nghttp2_is_fatal(rv)) {
334 return NGHTTP2_ERR_CALLBACK_FAILURE;
339 eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
340 status = stream_end_headers(session, stream, eos);
345 h2_stream * stream = h2_stream_set_get(session->streams,
346 frame->hd.stream_id);
347 if (stream == NULL) {
348 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
350 "h2_session: stream(%ld-%d): DATA frame "
351 "for unknown stream", session->id,
352 (int)frame->hd.stream_id);
353 rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
355 NGHTTP2_INTERNAL_ERROR);
356 if (nghttp2_is_fatal(rv)) {
357 return NGHTTP2_ERR_CALLBACK_FAILURE;
363 case NGHTTP2_PRIORITY: {
364 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
365 "h2_session: stream(%ld-%d): PRIORITY frame "
366 " weight=%d, dependsOn=%d, exclusive=%d",
367 session->id, (int)frame->hd.stream_id,
368 frame->priority.pri_spec.weight,
369 frame->priority.pri_spec.stream_id,
370 frame->priority.pri_spec.exclusive);
374 if (APLOGctrace2(session->c)) {
377 frame_print(frame, buffer,
378 sizeof(buffer)/sizeof(buffer[0]));
379 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
380 "h2_session: on_frame_rcv %s", buffer);
385 /* only DATA and HEADERS frame can bear END_STREAM flag. Other
386 frame types may have other flag which has the same value, so we
387 have to check the frame type first. */
388 if ((frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) &&
389 frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
390 h2_stream * stream = h2_stream_set_get(session->streams,
391 frame->hd.stream_id);
392 if (stream != NULL) {
393 status = h2_stream_write_eos(stream);
394 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
395 "h2_stream(%ld-%d): input closed",
396 session->id, (int)frame->hd.stream_id);
400 if (status != APR_SUCCESS) {
401 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
403 "h2_session: stream(%ld-%d): error handling frame",
404 session->id, (int)frame->hd.stream_id);
405 rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
407 NGHTTP2_INTERNAL_ERROR);
408 if (nghttp2_is_fatal(rv)) {
409 return NGHTTP2_ERR_CALLBACK_FAILURE;
417 static apr_status_t send_data(h2_session *session, const char *data,
420 return h2_conn_io_write(&session->io, data, length);
423 static apr_status_t pass_data(void *ctx,
424 const char *data, apr_size_t length)
426 return send_data((h2_session*)ctx, data, length);
429 static int on_send_data_cb(nghttp2_session *ngh2,
430 nghttp2_frame *frame,
431 const uint8_t *framehd,
433 nghttp2_data_source *source,
436 apr_status_t status = APR_SUCCESS;
437 h2_session *session = (h2_session *)userp;
438 int stream_id = (int)frame->hd.stream_id;
439 const unsigned char padlen = frame->data.padlen;
445 if (session->aborted) {
446 return NGHTTP2_ERR_CALLBACK_FAILURE;
449 stream = h2_stream_set_get(session->streams, stream_id);
451 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
453 "h2_stream(%ld-%d): send_data",
454 session->id, (int)stream_id);
455 return NGHTTP2_ERR_CALLBACK_FAILURE;
458 status = send_data(session, (const char *)framehd, 9);
459 if (status == APR_SUCCESS) {
461 status = send_data(session, (const char *)&padlen, 1);
464 if (status == APR_SUCCESS) {
465 apr_size_t len = length;
466 status = h2_stream_readx(stream, pass_data, session,
468 if (status == APR_SUCCESS && len != length) {
473 if (status == APR_SUCCESS && padlen) {
476 memset(pad, 0, padlen);
477 status = send_data(session, pad, padlen);
482 if (status == APR_SUCCESS) {
485 else if (status != APR_EOF) {
486 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
488 "h2_stream(%ld-%d): failed send_data_cb",
489 session->id, (int)stream_id);
490 return NGHTTP2_ERR_CALLBACK_FAILURE;
493 return h2_session_status_from_apr_status(status);
497 #define NGH2_SET_CALLBACK(callbacks, name, fn)\
498 nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
500 static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
502 int rv = nghttp2_session_callbacks_new(pcb);
504 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
505 APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
506 nghttp2_strerror(rv));
510 NGH2_SET_CALLBACK(*pcb, send, send_cb);
511 NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
512 NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
513 NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
514 NGH2_SET_CALLBACK(*pcb, before_frame_send, before_frame_send_cb);
515 NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
516 NGH2_SET_CALLBACK(*pcb, on_frame_not_send, on_frame_not_send_cb);
517 NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
518 NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
519 NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
520 NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
525 static h2_session *h2_session_create_int(conn_rec *c,
530 nghttp2_session_callbacks *callbacks = NULL;
531 nghttp2_option *options = NULL;
533 apr_pool_t *pool = NULL;
534 apr_status_t status = apr_pool_create(&pool, r? r->pool : c->pool);
536 if (status != APR_SUCCESS) {
540 session = apr_pcalloc(pool, sizeof(h2_session));
547 session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS);
548 session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM);
550 session->pool = pool;
552 status = apr_thread_cond_create(&session->iowait, session->pool);
553 if (status != APR_SUCCESS) {
557 session->streams = h2_stream_set_create(session->pool);
559 session->workers = workers;
560 session->mplx = h2_mplx_create(c, session->pool, workers);
562 h2_conn_io_init(&session->io, c);
563 session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
565 status = init_callbacks(c, &callbacks);
566 if (status != APR_SUCCESS) {
567 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927)
568 "nghttp2: error in init_callbacks");
569 h2_session_destroy(session);
573 rv = nghttp2_option_new(&options);
575 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
576 APLOGNO(02928) "nghttp2_option_new: %s",
577 nghttp2_strerror(rv));
578 h2_session_destroy(session);
582 nghttp2_option_set_peer_max_concurrent_streams(options,
583 (uint32_t)session->max_stream_count);
585 /* We need to handle window updates ourself, otherwise we
586 * get flooded by nghttp2. */
587 nghttp2_option_set_no_auto_window_update(options, 1);
589 rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
591 nghttp2_session_callbacks_del(callbacks);
592 nghttp2_option_del(options);
595 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
596 APLOGNO(02929) "nghttp2_session_server_new: %s",
597 nghttp2_strerror(rv));
598 h2_session_destroy(session);
606 h2_session *h2_session_create(conn_rec *c, h2_config *config,
609 return h2_session_create_int(c, NULL, config, workers);
612 h2_session *h2_session_rcreate(request_rec *r, h2_config *config,
615 return h2_session_create_int(r->connection, r, config, workers);
618 void h2_session_destroy(h2_session *session)
620 AP_DEBUG_ASSERT(session);
622 h2_mplx_release_and_join(session->mplx, session->iowait);
623 session->mplx = NULL;
625 if (session->streams) {
626 if (h2_stream_set_size(session->streams)) {
627 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
628 "h2_session(%ld): destroy, %d streams open",
629 session->id, (int)h2_stream_set_size(session->streams));
631 h2_stream_set_destroy(session->streams);
632 session->streams = NULL;
635 nghttp2_session_del(session->ngh2);
636 session->ngh2 = NULL;
638 h2_conn_io_destroy(&session->io);
640 if (session->iowait) {
641 apr_thread_cond_destroy(session->iowait);
642 session->iowait = NULL;
646 apr_pool_destroy(session->pool);
650 apr_status_t h2_session_goaway(h2_session *session, apr_status_t reason)
652 apr_status_t status = APR_SUCCESS;
654 AP_DEBUG_ASSERT(session);
655 if (session->aborted) {
660 if (reason == APR_SUCCESS) {
661 rv = nghttp2_submit_shutdown_notice(session->ngh2);
665 int last_id = nghttp2_session_get_last_proc_stream_id(session->ngh2);
666 rv = nghttp2_submit_goaway(session->ngh2, last_id,
667 NGHTTP2_FLAG_NONE, err, NULL, 0);
670 status = APR_EGENERAL;
671 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
672 APLOGNO(02930) "session(%ld): submit goaway: %s",
673 session->id, nghttp2_strerror(rv));
678 static apr_status_t h2_session_abort_int(h2_session *session, int reason)
680 AP_DEBUG_ASSERT(session);
681 if (!session->aborted) {
682 session->aborted = 1;
685 ap_log_cerror(APLOG_MARK, (reason == NGHTTP2_ERR_EOF)?
686 APLOG_DEBUG : APLOG_INFO, 0, session->c,
687 "session(%ld): aborting session, reason=%d %s",
688 session->id, reason, nghttp2_strerror(reason));
690 nghttp2_session_terminate_session(session->ngh2, reason);
691 nghttp2_submit_goaway(session->ngh2, 0, 0, reason, NULL, 0);
692 nghttp2_session_send(session->ngh2);
693 h2_conn_io_flush(&session->io);
695 h2_mplx_abort(session->mplx);
700 apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv)
702 AP_DEBUG_ASSERT(session);
704 rv = NGHTTP2_ERR_PROTO;
707 rv = NGHTTP2_ERR_NOMEM;
713 case APR_ECONNABORTED:
714 rv = NGHTTP2_ERR_EOF;
720 return h2_session_abort_int(session, rv);
723 apr_status_t h2_session_start(h2_session *session, int *rv)
725 apr_status_t status = APR_SUCCESS;
727 nghttp2_settings_entry settings[3];
729 AP_DEBUG_ASSERT(session);
730 /* Start the conversation by submitting our SETTINGS frame */
732 config = h2_config_get(session->c);
738 /* better for vhost matching */
739 config = h2_config_rget(session->r);
741 /* 'h2c' mode: we should have a 'HTTP2-Settings' header with
742 * base64 encoded client settings. */
743 s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
745 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r,
747 "HTTP2-Settings header missing in request");
751 dlen = h2_util_base64url_decode(&cs, s, session->pool);
753 if (APLOGrdebug(session->r)) {
755 h2_util_hex_dump(buffer, 128, (char*)cs, dlen);
756 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r,
757 "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)",
758 s, buffer, (int)dlen);
761 *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL);
764 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
765 APLOGNO(02932) "nghttp2_session_upgrade: %s",
766 nghttp2_strerror(*rv));
770 /* Now we need to auto-open stream 1 for the request we got. */
771 *rv = stream_open(session, 1);
773 status = APR_EGENERAL;
774 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
775 APLOGNO(02933) "open stream 1: %s",
776 nghttp2_strerror(*rv));
780 stream = h2_stream_set_get(session->streams, 1);
781 if (stream == NULL) {
782 status = APR_EGENERAL;
783 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
784 APLOGNO(02934) "lookup of stream 1");
788 status = h2_stream_rwrite(stream, session->r);
789 if (status != APR_SUCCESS) {
792 status = stream_end_headers(session, stream, 1);
793 if (status != APR_SUCCESS) {
798 settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
799 settings[0].value = (uint32_t)session->max_stream_count;
800 settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
801 settings[1].value = h2_config_geti(config, H2_CONF_WIN_SIZE);
802 settings[2].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
803 settings[2].value = 64*1024;
805 *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE,
807 sizeof(settings)/sizeof(settings[0]));
809 status = APR_EGENERAL;
810 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
811 APLOGNO(02935) "nghttp2_submit_settings: %s",
812 nghttp2_strerror(*rv));
818 static int h2_session_want_write(h2_session *session)
820 return nghttp2_session_want_write(session->ngh2);
828 static int resume_on_data(void *ctx, h2_stream *stream) {
829 resume_ctx *rctx = (resume_ctx*)ctx;
830 h2_session *session = rctx->session;
831 AP_DEBUG_ASSERT(session);
832 AP_DEBUG_ASSERT(stream);
834 if (h2_stream_is_suspended(stream)) {
835 if (h2_mplx_out_has_data_for(stream->m, stream->id)) {
837 h2_stream_set_suspended(stream, 0);
838 ++rctx->resume_count;
840 rv = nghttp2_session_resume_data(session->ngh2, stream->id);
841 ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
842 APLOG_ERR : APLOG_DEBUG, 0, session->c,
844 "h2_stream(%ld-%d): resuming stream %s",
845 session->id, stream->id, nghttp2_strerror(rv));
851 static int h2_session_resume_streams_with_data(h2_session *session) {
852 AP_DEBUG_ASSERT(session);
853 if (!h2_stream_set_is_empty(session->streams)
854 && session->mplx && !session->aborted) {
857 ctx.session = session;
858 ctx.resume_count = 0;
860 /* Resume all streams where we have data in the out queue and
861 * which had been suspended before. */
862 h2_stream_set_iter(session->streams, resume_on_data, &ctx);
863 return ctx.resume_count;
868 static void update_window(void *ctx, int stream_id, apr_size_t bytes_read)
870 h2_session *session = (h2_session*)ctx;
871 nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
874 static apr_status_t h2_session_update_windows(h2_session *session)
876 return h2_mplx_in_update_windows(session->mplx, update_window, session);
879 apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout)
881 apr_status_t status = APR_EAGAIN;
882 h2_stream *stream = NULL;
883 int flush_output = 0;
885 AP_DEBUG_ASSERT(session);
887 /* Check that any pending window updates are sent. */
888 status = h2_session_update_windows(session);
889 if (status == APR_SUCCESS) {
892 else if (status != APR_EAGAIN) {
896 if (h2_session_want_write(session)) {
898 status = APR_SUCCESS;
899 rv = nghttp2_session_send(session->ngh2);
901 ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
902 "h2_session: send: %s", nghttp2_strerror(rv));
903 if (nghttp2_is_fatal(rv)) {
904 h2_session_abort_int(session, rv);
905 status = APR_ECONNABORTED;
911 /* If we have responses ready, submit them now. */
912 while ((stream = h2_mplx_next_submit(session->mplx,
913 session->streams)) != NULL) {
914 status = h2_session_handle_response(session, stream);
918 if (h2_session_resume_streams_with_data(session) > 0) {
922 if (!flush_output && timeout > 0 && !h2_session_want_write(session)) {
923 status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);
925 if (status != APR_TIMEUP
926 && h2_session_resume_streams_with_data(session) > 0) {
930 /* nothing happened to ongoing streams, do some house-keeping */
934 if (h2_session_want_write(session)) {
936 status = APR_SUCCESS;
937 rv = nghttp2_session_send(session->ngh2);
939 ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
940 "h2_session: send2: %s", nghttp2_strerror(rv));
941 if (nghttp2_is_fatal(rv)) {
942 h2_session_abort_int(session, rv);
943 status = APR_ECONNABORTED;
950 h2_conn_io_flush(&session->io);
956 h2_stream *h2_session_get_stream(h2_session *session, int stream_id)
958 AP_DEBUG_ASSERT(session);
959 return h2_stream_set_get(session->streams, stream_id);
962 /* h2_io_on_read_cb implementation that offers the data read
963 * directly to the session for consumption.
965 static apr_status_t session_receive(const char *data, apr_size_t len,
966 apr_size_t *readlen, int *done,
969 h2_session *session = (h2_session *)puser;
970 AP_DEBUG_ASSERT(session);
972 ssize_t n = nghttp2_session_mem_recv(session->ngh2,
973 (const uint8_t *)data, len);
975 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL,
977 "h2_session: nghttp2_session_mem_recv error %d",
979 if (nghttp2_is_fatal((int)n)) {
981 h2_session_abort_int(session, (int)n);
992 apr_status_t h2_session_read(h2_session *session, apr_read_type_e block)
994 AP_DEBUG_ASSERT(session);
995 return h2_conn_io_read(&session->io, block, session_receive, session);
998 apr_status_t h2_session_close(h2_session *session)
1000 AP_DEBUG_ASSERT(session);
1001 return h2_conn_io_flush(&session->io);
1004 /* The session wants to send more DATA for the given stream.
1006 static ssize_t stream_data_cb(nghttp2_session *ng2s,
1010 uint32_t *data_flags,
1011 nghttp2_data_source *source,
1014 h2_session *session = (h2_session *)puser;
1015 apr_size_t nread = length;
1017 apr_status_t status;
1019 AP_DEBUG_ASSERT(session);
1021 stream = h2_stream_set_get(session->streams, stream_id);
1023 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
1025 "h2_stream(%ld-%d): data requested but stream not found",
1026 session->id, (int)stream_id);
1027 return NGHTTP2_ERR_CALLBACK_FAILURE;
1030 AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream));
1032 status = h2_stream_prep_read(stream, &nread, &eos);
1034 *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
1042 /* If there is no data available, our session will automatically
1043 * suspend this stream and not ask for more data until we resume
1044 * it. Remember at our h2_stream that we need to do this.
1047 h2_stream_set_suspended(stream, 1);
1048 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1049 "h2_stream(%ld-%d): suspending stream",
1050 session->id, (int)stream_id);
1051 return NGHTTP2_ERR_DEFERRED;
1060 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
1061 APLOGNO(02938) "h2_stream(%ld-%d): reading data",
1062 session->id, (int)stream_id);
1063 return NGHTTP2_ERR_CALLBACK_FAILURE;
1067 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1070 return (ssize_t)nread;
1079 static int submit_response(h2_session *session, h2_response *response)
1081 nghttp2_data_provider provider;
1084 memset(&provider, 0, sizeof(provider));
1085 provider.source.fd = response->stream_id;
1086 provider.read_callback = stream_data_cb;
1088 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
1089 "h2_stream(%ld-%d): submitting response %s",
1090 session->id, response->stream_id, response->status);
1092 rv = nghttp2_submit_response(session->ngh2, response->stream_id,
1093 response->ngheader->nv,
1094 response->ngheader->nvlen, &provider);
1097 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
1098 APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s",
1099 session->id, response->stream_id, nghttp2_strerror(rv));
1102 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
1103 "h2_stream(%ld-%d): submitted response %s, rv=%d",
1104 session->id, response->stream_id,
1105 response->status, rv);
1110 /* Start submitting the response to a stream request. This is possible
1111 * once we have all the response headers. The response body will be
1112 * read by the session using the callback we supply.
1114 apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream)
1116 apr_status_t status = APR_SUCCESS;
1118 AP_DEBUG_ASSERT(session);
1119 AP_DEBUG_ASSERT(stream);
1120 AP_DEBUG_ASSERT(stream->response);
1122 if (stream->response->ngheader) {
1123 rv = submit_response(session, stream->response);
1126 rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
1127 stream->id, NGHTTP2_PROTOCOL_ERROR);
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));
1140 int h2_session_is_done(h2_session *session)
1142 AP_DEBUG_ASSERT(session);
1143 return (session->aborted
1145 || (!nghttp2_session_want_read(session->ngh2)
1146 && !nghttp2_session_want_write(session->ngh2)));
1149 static int log_stream(void *ctx, h2_stream *stream)
1151 h2_session *session = (h2_session *)ctx;
1152 AP_DEBUG_ASSERT(session);
1153 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
1154 "h2_stream(%ld-%d): in set, suspended=%d, aborted=%d, "
1156 session->id, stream->id, stream->suspended, stream->aborted,
1157 h2_mplx_out_has_data_for(session->mplx, stream->id));
1161 void h2_session_log_stats(h2_session *session)
1163 AP_DEBUG_ASSERT(session);
1164 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
1165 "h2_session(%ld): %d open streams",
1166 session->id, (int)h2_stream_set_size(session->streams));
1167 h2_stream_set_iter(session->streams, log_stream, session);
1170 static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
1173 size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
1175 switch (frame->hd.type) {
1176 case NGHTTP2_DATA: {
1177 return apr_snprintf(buffer, maxlen,
1178 "DATA[length=%d, flags=%d, stream=%d, padlen=%d]",
1179 (int)frame->hd.length, frame->hd.flags,
1180 frame->hd.stream_id, (int)frame->data.padlen);
1182 case NGHTTP2_HEADERS: {
1183 return apr_snprintf(buffer, maxlen,
1184 "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]",
1185 (int)frame->hd.length,
1186 !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
1187 frame->hd.stream_id,
1188 !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
1190 case NGHTTP2_PRIORITY: {
1191 return apr_snprintf(buffer, maxlen,
1192 "PRIORITY[length=%d, flags=%d, stream=%d]",
1193 (int)frame->hd.length,
1194 frame->hd.flags, frame->hd.stream_id);
1196 case NGHTTP2_RST_STREAM: {
1197 return apr_snprintf(buffer, maxlen,
1198 "RST_STREAM[length=%d, flags=%d, stream=%d]",
1199 (int)frame->hd.length,
1200 frame->hd.flags, frame->hd.stream_id);
1202 case NGHTTP2_SETTINGS: {
1203 if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
1204 return apr_snprintf(buffer, maxlen,
1205 "SETTINGS[ack=1, stream=%d]",
1206 frame->hd.stream_id);
1208 return apr_snprintf(buffer, maxlen,
1209 "SETTINGS[length=%d, stream=%d]",
1210 (int)frame->hd.length, frame->hd.stream_id);
1212 case NGHTTP2_PUSH_PROMISE: {
1213 return apr_snprintf(buffer, maxlen,
1214 "PUSH_PROMISE[length=%d, hend=%d, stream=%d]",
1215 (int)frame->hd.length,
1216 !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
1217 frame->hd.stream_id);
1219 case NGHTTP2_PING: {
1220 return apr_snprintf(buffer, maxlen,
1221 "PING[length=%d, ack=%d, stream=%d]",
1222 (int)frame->hd.length,
1223 frame->hd.flags&NGHTTP2_FLAG_ACK,
1224 frame->hd.stream_id);
1226 case NGHTTP2_GOAWAY: {
1227 size_t len = (frame->goaway.opaque_data_len < s_len)?
1228 frame->goaway.opaque_data_len : s_len-1;
1229 memcpy(scratch, frame->goaway.opaque_data, len);
1230 scratch[len+1] = '\0';
1231 return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s']",
1232 frame->goaway.error_code, scratch);
1234 case NGHTTP2_WINDOW_UPDATE: {
1235 return apr_snprintf(buffer, maxlen,
1236 "WINDOW_UPDATE[length=%d, stream=%d]",
1237 (int)frame->hd.length, frame->hd.stream_id);
1240 return apr_snprintf(buffer, maxlen,
1241 "FRAME[type=%d, length=%d, flags=%d, stream=%d]",
1242 frame->hd.type, (int)frame->hd.length,
1243 frame->hd.flags, frame->hd.stream_id);