/* h2_io_on_read_cb implementation that offers the data read * directly to the session for consumption. */ static apr_status_t session_receive(const char *data, apr_size_t len, apr_size_t *readlen, int *done, void *puser) { h2_session *session = (h2_session *)puser; AP_DEBUG_ASSERT(session); if (len > 0) { ssize_t n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len); if (n < 0) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, session->c, "h2_session: nghttp2_session_mem_recv error %d", (int)n); if (nghttp2_is_fatal((int)n)) { *done = 1; h2_session_abort_int(session, (int)n); return APR_EGENERAL; } } else { *readlen = n; } } return APR_SUCCESS; }
/* Start submitting the response to a stream request. This is possible * once we have all the response headers. The response body will be * read by the session using the callback we supply. */ apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream) { apr_status_t status = APR_SUCCESS; int rv = 0; AP_DEBUG_ASSERT(session); AP_DEBUG_ASSERT(stream); AP_DEBUG_ASSERT(stream->response); if (stream->response->ngheader) { rv = submit_response(session, stream->response); } else { rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, stream->id, NGHTTP2_PROTOCOL_ERROR); } if (nghttp2_is_fatal(rv)) { status = APR_EGENERAL; h2_session_abort_int(session, rv); ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02940) "submit_response: %s", nghttp2_strerror(rv)); } return status; }
apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv) { AP_DEBUG_ASSERT(session); if (rv == 0) { rv = NGHTTP2_ERR_PROTO; switch (reason) { case APR_ENOMEM: rv = NGHTTP2_ERR_NOMEM; break; case APR_SUCCESS: /* all fine, just... */ case APR_EOF: /* client closed its end... */ case APR_TIMEUP: /* got bored waiting... */ rv = 0; /* ...gracefully shut down */ break; case APR_EBADF: /* connection unusable, terminate silently */ default: if (APR_STATUS_IS_ECONNABORTED(reason) || APR_STATUS_IS_ECONNRESET(reason) || APR_STATUS_IS_EBADF(reason)) { rv = NGHTTP2_ERR_EOF; } break; } } return h2_session_abort_int(session, rv); }
apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout) { apr_status_t status = APR_EAGAIN; h2_response *response = NULL; int have_written = 0; assert(session); /* Check that any pending window updates are sent. */ status = h2_session_update_windows(session); if (status == APR_SUCCESS) { have_written = 1; } else if (status != APR_EAGAIN) { return status; } /* If we have responses ready, submit them now. */ while ((response = h2_session_pop_response(session)) != NULL) { h2_stream *stream = h2_session_get_stream(session, response->stream_id); if (stream) { status = h2_session_handle_response(session, stream, response); have_written = 1; } h2_response_destroy(response); response = NULL; } h2_session_resume_streams_with_data(session); if (!have_written && timeout > 0 && !h2_session_want_write(session)) { status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait); } h2_session_resume_streams_with_data(session); if (h2_session_want_write(session)) { status = APR_SUCCESS; int rv = nghttp2_session_send(session->ngh2); if (rv != 0) { ap_log_cerror( APLOG_MARK, APLOG_INFO, 0, session->c, "h2_session: send: %s", nghttp2_strerror(rv)); if (nghttp2_is_fatal(rv)) { h2_session_abort_int(session, rv); status = APR_ECONNABORTED; } } have_written = 1; } if (have_written) { h2_conn_io_flush(&session->io); } reap_zombies(session); return status; }
apr_status_t h2_session_close(h2_session *session) { AP_DEBUG_ASSERT(session); if (!session->aborted) { h2_session_abort_int(session, 0); } ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c, "h2_session: closing, writing eoc"); h2_conn_io_writeb(&session->io, h2_bucket_eoc_create(session->c->bucket_alloc, session)); return h2_conn_io_flush(&session->io); }
/* Start submitting the response to a stream request. This is possible * once we have all the response headers. The response body will be * read by the session using the callback we supply. */ apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream, h2_response *head) { assert(session); apr_status_t status = APR_SUCCESS; int rv = 0; if (head->http_status) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_stream(%ld-%d): submitting response %s with %d headers", session->id, stream->id, head->http_status, (int)head->nvlen); assert(head->nvlen); nghttp2_data_provider provider = { stream->id, stream_data_cb }; rv = nghttp2_submit_response(session->ngh2, stream->id, &head->nv, head->nvlen, &provider); if (rv != 0) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "h2_stream(%ld-%d): submit_response: %s", session->id, stream->id, nghttp2_strerror(rv)); } else { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): submitted response %s with %d " "headers, rv=%d", session->id, stream->id, head->http_status, (int)head->nvlen, rv); } } else { rv = nghttp2_submit_rst_stream(session->ngh2, 0, stream->id, NGHTTP2_ERR_INVALID_STATE); } if (nghttp2_is_fatal(rv)) { status = APR_EGENERAL; h2_session_abort_int(session, rv); ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "submit_response: %s", nghttp2_strerror(rv)); } return status; }
apr_status_t h2_session_abort(h2_session *session, apr_status_t reason) { assert(session); int err = NGHTTP2_ERR_PROTO; switch (reason) { case APR_ENOMEM: err = NGHTTP2_ERR_NOMEM; break; case APR_EOF: err = 0; break; case APR_ECONNABORTED: err = NGHTTP2_ERR_EOF; break; default: break; } return h2_session_abort_int(session, err); }
apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout) { apr_status_t status = APR_EAGAIN; h2_stream *stream = NULL; int flush_output = 0; AP_DEBUG_ASSERT(session); /* Check that any pending window updates are sent. */ status = h2_session_update_windows(session); if (status == APR_SUCCESS) { flush_output = 1; } else if (status != APR_EAGAIN) { return status; } if (h2_session_want_write(session)) { int rv; status = APR_SUCCESS; rv = nghttp2_session_send(session->ngh2); if (rv != 0) { ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_session: send: %s", nghttp2_strerror(rv)); if (nghttp2_is_fatal(rv)) { h2_session_abort_int(session, rv); status = APR_ECONNABORTED; } } flush_output = 1; } /* If we have responses ready, submit them now. */ while ((stream = h2_mplx_next_submit(session->mplx, session->streams)) != NULL) { status = h2_session_handle_response(session, stream); flush_output = 1; } if (h2_session_resume_streams_with_data(session) > 0) { flush_output = 1; } if (!flush_output && timeout > 0 && !h2_session_want_write(session)) { status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait); if (status != APR_TIMEUP && h2_session_resume_streams_with_data(session) > 0) { flush_output = 1; } else { /* nothing happened to ongoing streams, do some house-keeping */ } } if (h2_session_want_write(session)) { int rv; status = APR_SUCCESS; rv = nghttp2_session_send(session->ngh2); if (rv != 0) { ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_session: send2: %s", nghttp2_strerror(rv)); if (nghttp2_is_fatal(rv)) { h2_session_abort_int(session, rv); status = APR_ECONNABORTED; } } flush_output = 1; } if (flush_output) { h2_conn_io_flush(&session->io); } return status; }