apr_status_t h2_session_start(h2_session *session, int *rv) { apr_status_t status = APR_SUCCESS; h2_config *config; nghttp2_settings_entry settings[3]; AP_DEBUG_ASSERT(session); /* Start the conversation by submitting our SETTINGS frame */ *rv = 0; config = h2_config_get(session->c); if (session->r) { const char *s, *cs; apr_size_t dlen; h2_stream * stream; /* better for vhost matching */ config = h2_config_rget(session->r); /* 'h2c' mode: we should have a 'HTTP2-Settings' header with * base64 encoded client settings. */ s = apr_table_get(session->r->headers_in, "HTTP2-Settings"); if (!s) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r, APLOGNO(02931) "HTTP2-Settings header missing in request"); return APR_EINVAL; } cs = NULL; dlen = h2_util_base64url_decode(&cs, s, session->pool); if (APLOGrdebug(session->r)) { char buffer[128]; h2_util_hex_dump(buffer, 128, (char*)cs, dlen); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r, "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)", s, buffer, (int)dlen); } *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL); if (*rv != 0) { status = APR_EINVAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02932) "nghttp2_session_upgrade: %s", nghttp2_strerror(*rv)); return status; } /* Now we need to auto-open stream 1 for the request we got. */ *rv = stream_open(session, 1); if (*rv != 0) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02933) "open stream 1: %s", nghttp2_strerror(*rv)); return status; } stream = h2_stream_set_get(session->streams, 1); if (stream == NULL) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02934) "lookup of stream 1"); return status; } status = h2_stream_rwrite(stream, session->r); if (status != APR_SUCCESS) { return status; } status = stream_end_headers(session, stream, 1); if (status != APR_SUCCESS) { return status; } } settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; settings[0].value = (uint32_t)session->max_stream_count; settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; settings[1].value = h2_config_geti(config, H2_CONF_WIN_SIZE); settings[2].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE; settings[2].value = 64*1024; *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, sizeof(settings)/sizeof(settings[0])); if (*rv != 0) { status = APR_EGENERAL; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02935) "nghttp2_submit_settings: %s", nghttp2_strerror(*rv)); } return status; }
apr_status_t h2_session_start(h2_session *session) { assert(session); /* Start the conversation by submitting our SETTINGS frame */ apr_status_t status = APR_SUCCESS; h2_config *config = h2_config_get(session->c); int rv = 0; if (session->r) { /* 'h2c' mode: we should have a 'HTTP2-Settings' header with * base64 encoded client settings. */ const char *s = apr_table_get(session->r->headers_in, "HTTP2-Settings"); if (!s) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r, "HTTP2-Settings header missing in request"); return APR_EINVAL; } int cslen = apr_base64_decode_len(s); char *cs = apr_pcalloc(session->r->pool, cslen); --cslen; /* apr also counts the terminating 0 */ apr_base64_decode(cs, s); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r, "upgrading h2c session with nghttp2 from %s (%d)", s, cslen); rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, cslen, NULL); if (rv != 0) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, "nghttp2_session_upgrade: %s", nghttp2_strerror(rv)); return status; } /* Now we need to auto-open stream 1 for the request we got. */ rv = stream_open(session, 1); if (rv != 0) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, "open stream 1: %s", nghttp2_strerror(rv)); return status; } h2_stream * stream = h2_stream_set_get(session->streams, 1); if (stream == NULL) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, "lookup of stream 1"); return status; } status = h2_stream_rwrite(stream, session->r); if (status != APR_SUCCESS) { return status; } status = stream_end_headers(session, stream, 1); if (status != APR_SUCCESS) { return status; } status = h2_stream_write_eos(stream); if (status != APR_SUCCESS) { return status; } } nghttp2_settings_entry settings[] = { { NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, h2_config_geti(config, H2_CONF_MAX_HL_SIZE) }, { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, h2_config_geti(config, H2_CONF_WIN_SIZE) }, {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, h2_config_geti(config, H2_CONF_MAX_STREAMS) }, }; rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, sizeof(settings)/sizeof(settings[0])); if (rv != 0) { status = APR_EGENERAL; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "nghttp2_submit_settings: %s", nghttp2_strerror(rv)); } return status; }
/** * nghttp2 session has received a complete frame. Most, it uses * for processing of internal state. HEADER and DATA frames however * we need to handle ourself. */ static int on_frame_recv_cb(nghttp2_session *ng2s, const nghttp2_frame *frame, void *userp) { int rv; h2_session *session = (h2_session *)userp; apr_status_t status = APR_SUCCESS; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } ++session->frames_received; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id, (long)session->frames_received, frame->hd.type); switch (frame->hd.type) { case NGHTTP2_HEADERS: { int eos; h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream == NULL) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02921) "h2_session: stream(%ld-%d): HEADERS frame " "for unknown stream", session->id, (int)frame->hd.stream_id); rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); status = stream_end_headers(session, stream, eos); break; } case NGHTTP2_DATA: { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream == NULL) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02922) "h2_session: stream(%ld-%d): DATA frame " "for unknown stream", session->id, (int)frame->hd.stream_id); rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } break; } case NGHTTP2_PRIORITY: { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session: stream(%ld-%d): PRIORITY frame " " weight=%d, dependsOn=%d, exclusive=%d", session->id, (int)frame->hd.stream_id, frame->priority.pri_spec.weight, frame->priority.pri_spec.stream_id, frame->priority.pri_spec.exclusive); break; } default: if (APLOGctrace2(session->c)) { char buffer[256]; frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_session: on_frame_rcv %s", buffer); } break; } /* only DATA and HEADERS frame can bear END_STREAM flag. Other frame types may have other flag which has the same value, so we have to check the frame type first. */ if ((frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream != NULL) { status = h2_stream_write_eos(stream); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_stream(%ld-%d): input closed", session->id, (int)frame->hd.stream_id); } } if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02923) "h2_session: stream(%ld-%d): error handling frame", session->id, (int)frame->hd.stream_id); rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } return 0; }
/** * nghttp2 session has received a complete frame. Most, it uses * for processing of internal state. HEADER and DATA frames however * we need to handle ourself. */ static int on_frame_recv_cb(nghttp2_session *ng2s, const nghttp2_frame *frame, void *userp) { h2_session *session = (h2_session *)userp; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } apr_status_t status = APR_SUCCESS; ++session->frames_received; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id, session->frames_received, frame->hd.type); switch (frame->hd.type) { case NGHTTP2_HEADERS: { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream == NULL) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, "h2_session: stream(%ld-%d): HEADERS frame " "for unknown stream", session->id, (int)frame->hd.stream_id); return NGHTTP2_ERR_INVALID_STREAM_ID; } if (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) { int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); status = stream_end_headers(session, stream, eos); } break; } case NGHTTP2_DATA: { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream == NULL) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, "h2_session: stream(%ld-%d): DATA frame " "for unknown stream", session->id, (int)frame->hd.stream_id); return NGHTTP2_ERR_PROTO; } break; } default: if (APLOGctrace2(session->c)) { char buffer[256]; frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_session: on_frame_rcv %s", buffer); } break; } if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (stream != NULL) { status = h2_stream_write_eos(stream); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_stream(%ld-%d): input closed", session->id, (int)frame->hd.stream_id); } } if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "h2_session: stream(%ld-%d): error handling frame", session->id, (int)frame->hd.stream_id); return NGHTTP2_ERR_INVALID_STREAM_STATE; } return 0; }