h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) { apr_status_t status; h2_stream *stream = NULL; AP_DEBUG_ASSERT(m); if (m->aborted) { return NULL; } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get_highest_prio(m->ready_ios); if (io) { h2_response *response = io->response; h2_io_set_remove(m->ready_ios, io); stream = h2_stream_set_get(streams, response->stream_id); if (stream) { h2_stream_set_response(stream, response, io->bbout); if (io->output_drained) { apr_thread_cond_signal(io->output_drained); } } else { ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c, APLOGNO(02953) "h2_mplx(%ld): stream for response %d", m->id, response->stream_id); } } apr_thread_mutex_unlock(m->lock); } return stream; }
static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp) { h2_session *session = (h2_session *)userp; h2_stream * stream; apr_status_t status; (void)ngh2; (void)flags; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02920) "h2_session: stream(%ld-%d): on_header for unknown stream", session->id, (int)frame->hd.stream_id); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } status = h2_stream_write_header(stream, (const char *)name, namelen, (const char *)value, valuelen); if (status != APR_SUCCESS) { return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } return 0; }
h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) { apr_status_t status; h2_stream *stream = NULL; AP_DEBUG_ASSERT(m); if (m->aborted) { return NULL; } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios); if (io) { stream = h2_stream_set_get(streams, io->id); if (stream) { if (io->rst_error) { h2_stream_rst(stream, io->rst_error); } else { AP_DEBUG_ASSERT(io->response); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_pre"); h2_stream_set_response(stream, io->response, io->bbout); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_post"); } } else { /* We have the io ready, but the stream has gone away, maybe * reset by the client. Should no longer happen since such * streams should clear io's from the ready queue. */ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c, APLOGNO(02953) "h2_mplx(%ld): stream for response %d closed, " "resetting io to close request processing", m->id, io->id); io->orphaned = 1; if (io->task_done) { io_destroy(m, io, 1); } else { /* hang around until the h2_task is done, but * shutdown input and send out any events (e.g. window * updates) asap. */ h2_io_in_shutdown(io); h2_io_rst(io, H2_ERR_STREAM_CLOSED); io_process_events(m, io); } } if (io->output_drained) { apr_thread_cond_signal(io->output_drained); } } apr_thread_mutex_unlock(m->lock); } return stream; }
h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) { apr_status_t status; h2_stream *stream = NULL; int acquired; AP_DEBUG_ASSERT(m); if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios); if (io && !m->aborted) { stream = h2_stream_set_get(streams, io->id); if (stream) { if (io->rst_error) { h2_stream_rst(stream, io->rst_error); } else { AP_DEBUG_ASSERT(io->response); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_pre"); h2_stream_set_response(stream, io->response, io->bbout); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_next_submit_post"); } } else { /* We have the io ready, but the stream has gone away, maybe * reset by the client. Should no longer happen since such * streams should clear io's from the ready queue. */ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, "h2_mplx(%ld): stream for response %d closed, " "resetting io to close request processing", m->id, io->id); h2_io_make_orphaned(io, H2_ERR_STREAM_CLOSED); if (!io->worker_started || io->worker_done) { io_destroy(m, io, 1); } else { /* hang around until the h2_task is done, but * shutdown input and send out any events (e.g. window * updates) asap. */ h2_io_in_shutdown(io); io_process_events(m, io); } } h2_io_signal(io, H2_IO_WRITE); } leave_mutex(m, acquired); } return stream; }
apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream) { h2_stream *existing = h2_stream_set_get(sp, stream->id); if (!existing) { int last; APR_ARRAY_PUSH(sp->list, h2_stream*) = stream; /* Normally, streams get added in ascending order if id. We * keep the array sorted, so we just need to check of the newly * appended stream has a lower id than the last one. if not, * sorting is not necessary. */ last = sp->list->nelts - 1; if (last > 0 && (H2_STREAM_IDX(sp->list, last)->id < H2_STREAM_IDX(sp->list, last-1)->id)) { h2_stream_set_sort(sp); } }
static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id, uint32_t error_code, void *userp) { h2_session *session = (h2_session *)userp; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } h2_stream *stream = h2_stream_set_get(session->streams, stream_id); if (stream) { apr_status_t status = close_active_stream(session, stream, 0); } if (error_code) { ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c, "h2_stream(%ld-%d): close error %d", session->id, (int)stream_id, error_code); } return 0; }
static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *userp) { int rv; h2_session *session = (h2_session *)userp; h2_stream * stream; apr_status_t status; (void)flags; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02919) "h2_session: stream(%ld-%d): on_data_chunk for unknown stream", session->id, (int)stream_id); rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } status = h2_stream_write_data(stream, (const char *)data, len); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, "h2_stream(%ld-%d): written DATA, length %d", session->id, stream_id, (int)len); if (status != APR_SUCCESS) { rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR); if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } } return 0; }
static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *userp) { h2_session *session = (h2_session *)userp; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } h2_stream * stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, "h2_session: stream(%ld-%d): on_data_chunk for unknown stream", session->id, (int)stream_id); return NGHTTP2_ERR_INVALID_STREAM_ID; } apr_status_t status = h2_stream_write_data(stream, (const char *)data, len); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, "h2_stream(%ld-%d): written DATA, length %ld", session->id, stream_id, len); return (status == APR_SUCCESS)? 0 : NGHTTP2_ERR_PROTO; }
void h2_mplx_task_done(h2_mplx *m, int stream_id) { apr_status_t status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_stream *stream = h2_stream_set_get(m->closed, stream_id); h2_io *io = h2_io_set_get(m->stream_ios, stream_id); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, "h2_mplx(%ld): task(%d) done", m->id, stream_id); if (stream) { /* stream was already closed by main connection and is in * zombie state. Now that the task is done with it, we * can free its resources. */ h2_stream_set_remove(m->closed, stream); stream_destroy(m, stream, io); } else if (io) { /* main connection has not finished stream. Mark task as done * so that eventual cleanup can start immediately. */ io->task_done = 1; } apr_thread_mutex_unlock(m->lock); } }
static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp) { h2_session *session = (h2_session *)userp; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } h2_stream * stream = h2_stream_set_get(session->streams, frame->hd.stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, "h2_session: stream(%ld-%d): on_header for unknown stream", session->id, (int)frame->hd.stream_id); return NGHTTP2_ERR_INVALID_STREAM_ID; } apr_status_t status = h2_stream_write_header(stream, (const char *)name, namelen, (const char *)value, valuelen); return (status == APR_SUCCESS)? 0 : NGHTTP2_ERR_PROTO; }
/* The session wants to send more DATA for the given stream. */ static ssize_t stream_data_cb(nghttp2_session *ng2s, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *puser) { h2_session *session = (h2_session *)puser; apr_size_t nread = length; int eos = 0; apr_status_t status; h2_stream *stream; AP_DEBUG_ASSERT(session); (void)ng2s; (void)buf; (void)source; stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, APLOGNO(02937) "h2_stream(%ld-%d): data requested but stream not found", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream)); status = h2_stream_prep_read(stream, &nread, &eos); if (nread) { *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; } switch (status) { case APR_SUCCESS: break; case APR_EAGAIN: /* If there is no data available, our session will automatically * suspend this stream and not ask for more data until we resume * it. Remember at our h2_stream that we need to do this. */ nread = 0; h2_stream_set_suspended(stream, 1); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): suspending stream", session->id, (int)stream_id); return NGHTTP2_ERR_DEFERRED; case APR_EOF: nread = 0; eos = 1; break; default: nread = 0; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02938) "h2_stream(%ld-%d): reading data", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } if (eos) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } return (ssize_t)nread; }
static int on_send_data_cb(nghttp2_session *ngh2, nghttp2_frame *frame, const uint8_t *framehd, size_t length, nghttp2_data_source *source, void *userp) { apr_status_t status = APR_SUCCESS; h2_session *session = (h2_session *)userp; int stream_id = (int)frame->hd.stream_id; const unsigned char padlen = frame->data.padlen; int eos; h2_stream *stream; (void)ngh2; (void)source; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, APLOGNO(02924) "h2_stream(%ld-%d): send_data", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_stream(%ld-%d): send_data_cb for %ld bytes", session->id, (int)stream_id, (long)length); if (h2_conn_io_is_buffered(&session->io)) { status = h2_conn_io_write(&session->io, (const char *)framehd, 9); if (status == APR_SUCCESS) { if (padlen) { status = h2_conn_io_write(&session->io, (const char *)&padlen, 1); } if (status == APR_SUCCESS) { apr_size_t len = length; status = h2_stream_readx(stream, pass_data, session, &len, &eos); if (status == APR_SUCCESS && len != length) { status = APR_EINVAL; } } if (status == APR_SUCCESS && padlen) { if (padlen) { status = h2_conn_io_write(&session->io, immortal_zeros, padlen); } } } } else { apr_bucket *b; char *header = apr_pcalloc(stream->pool, 10); memcpy(header, (const char *)framehd, 9); if (padlen) { header[9] = (char)padlen; } b = apr_bucket_pool_create(header, padlen? 10 : 9, stream->pool, session->c->bucket_alloc); status = h2_conn_io_writeb(&session->io, b); if (status == APR_SUCCESS) { apr_size_t len = length; status = h2_stream_read_to(stream, session->io.output, &len, &eos); session->io.unflushed = 1; if (status == APR_SUCCESS && len != length) { status = APR_EINVAL; } } if (status == APR_SUCCESS && padlen) { b = apr_bucket_immortal_create(immortal_zeros, padlen, session->c->bucket_alloc); status = h2_conn_io_writeb(&session->io, b); } } if (status == APR_SUCCESS) { stream->data_frames_sent++; h2_conn_io_consider_flush(&session->io); return 0; } else { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(02925) "h2_stream(%ld-%d): failed send_data_cb", session->id, (int)stream_id); } return h2_session_status_from_apr_status(status); }
h2_stream *h2_session_get_stream(h2_session *session, int stream_id) { AP_DEBUG_ASSERT(session); return h2_stream_set_get(session->streams, stream_id); }
static int on_send_data_cb(nghttp2_session *ngh2, nghttp2_frame *frame, const uint8_t *framehd, size_t length, nghttp2_data_source *source, void *userp) { apr_status_t status = APR_SUCCESS; h2_session *session = (h2_session *)userp; int stream_id = (int)frame->hd.stream_id; const unsigned char padlen = frame->data.padlen; int eos; h2_stream *stream; (void)ngh2; (void)source; if (session->aborted) { return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, APLOGNO(02924) "h2_stream(%ld-%d): send_data", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } status = send_data(session, (const char *)framehd, 9); if (status == APR_SUCCESS) { if (padlen) { status = send_data(session, (const char *)&padlen, 1); } if (status == APR_SUCCESS) { apr_size_t len = length; status = h2_stream_readx(stream, pass_data, session, &len, &eos); if (status == APR_SUCCESS && len != length) { status = APR_EINVAL; } } if (status == APR_SUCCESS && padlen) { if (padlen) { char pad[256]; memset(pad, 0, padlen); status = send_data(session, pad, padlen); } } } if (status == APR_SUCCESS) { return 0; } else if (status != APR_EOF) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02925) "h2_stream(%ld-%d): failed send_data_cb", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } return h2_session_status_from_apr_status(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) { 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; }
/** * 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; }
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; }
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; }
h2_stream *h2_session_get_stream(h2_session *session, int stream_id) { assert(session); return h2_stream_set_get(session->streams, stream_id); }
/* The session wants to send more DATA for the given stream. */ static ssize_t stream_data_cb(nghttp2_session *ng2s, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *puser) { h2_session *session = (h2_session *)puser; assert(session); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_stream(%ld-%d): requesting %ld bytes", session->id, (int)stream_id, (long)length); h2_stream *stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, "h2_stream(%ld-%d): data requested but stream not found", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } assert(!h2_stream_is_suspended(stream)); /* Try to pop data buckets from our queue for this stream * until we see EOS or the buffer is full. */ ssize_t total_read = 0; int eos = 0; int done = 0; size_t left = length; while (left > 0 && !done && !eos) { apr_status_t status = APR_SUCCESS; h2_bucket *bucket = stream->cur_out; stream->cur_out = NULL; if (!bucket) { status = h2_stream_read(stream, &bucket, &eos); } switch (status) { case APR_SUCCESS: { /* This copies out the data and modifies the bucket to * reflect the amount "moved". This is easy, as this callback * runs in the connection thread alone and is the sole owner * of data in this queue. */ assert(bucket); size_t nread = h2_bucket_move(bucket, (char*)buf, left); if (bucket->data_len > 0) { /* we could not move all, remember it for next time */ stream->cur_out = bucket; eos = 0; } else { h2_bucket_destroy(bucket); } total_read += nread; buf += nread; left -= nread; } case APR_EAGAIN: /* If there is no data available, our session will automatically * suspend this stream and not ask for more data until we resume * it. Remember at our h2_stream that we need to do this. */ done = 1; if (total_read == 0) { h2_stream_set_suspended(stream, 1); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): suspending stream", session->id, (int)stream_id); return NGHTTP2_ERR_DEFERRED; } break; case APR_EOF: eos = 1; done = 1; break; default: ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "h2_stream(%ld-%d): reading data", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } } if (eos) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_stream(%ld-%d): requested %ld, " "sending %ld data bytes (eos=%d)", session->id, (int)stream_id, (long)length, (long)total_read, eos); return total_read; }
static ssize_t stream_data_cb(nghttp2_session *ng2s, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *puser) { h2_session *session = (h2_session *)puser; apr_size_t nread = length; int eos = 0; apr_status_t status; h2_stream *stream; AP_DEBUG_ASSERT(session); /* The session wants to send more DATA for the stream. We need * to find out how much of the requested length we can send without * blocking. * Indicate EOS when we encounter it or DEFERRED if the stream * should be suspended. * TODO: for handling of TRAILERS, the EOF indication needs * to be aware of that. */ (void)ng2s; (void)buf; (void)source; stream = h2_stream_set_get(session->streams, stream_id); if (!stream) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, APLOGNO(02937) "h2_stream(%ld-%d): data requested but stream not found", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream)); status = h2_stream_prep_read(stream, &nread, &eos); if (nread) { *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; } switch (status) { case APR_SUCCESS: break; case APR_ECONNRESET: return nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, stream->id, stream->rst_error); case APR_EAGAIN: /* If there is no data available, our session will automatically * suspend this stream and not ask for more data until we resume * it. Remember at our h2_stream that we need to do this. */ nread = 0; h2_stream_set_suspended(stream, 1); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): suspending stream", session->id, (int)stream_id); return NGHTTP2_ERR_DEFERRED; case APR_EOF: nread = 0; eos = 1; break; default: nread = 0; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02938) "h2_stream(%ld-%d): reading data", session->id, (int)stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } if (eos) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } return (ssize_t)nread; }