-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * http://www.apache.org/licenses/LICENSE-2.0
-
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
#include <assert.h>
#include <stddef.h>
else {
/* request HEADER */
ap_assert(stream->request == NULL);
- ap_assert(stream->rtmp != NULL);
+ if (stream->rtmp == NULL) {
+ /* This can only happen, if the stream has received no header
+ * name/value pairs at all. The lastest nghttp2 version have become
+ * pretty good at detecting this early. In any case, we have
+ * to abort the connection here, since this is clearly a protocol error */
+ return APR_EINVAL;
+ }
status = h2_request_end_headers(stream->rtmp, stream->pool, eos);
if (status != APR_SUCCESS) {
return status;
ap_assert(stream);
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c,
H2_STRM_MSG(stream, "destroy"));
- if (stream->pool) {
- apr_pool_destroy(stream->pool);
- stream->pool = NULL;
- }
-}
-
-apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
-{
- apr_pool_t *pool = stream->pool;
- stream->pool = NULL;
- return pool;
+ apr_pool_destroy(stream->pool);
}
apr_status_t h2_stream_prep_processing(h2_stream *stream)
status = h2_request_add_header(stream->rtmp, stream->pool,
name, nlen, value, vlen);
}
- else {
+ else if (H2_SS_OPEN == stream->state) {
status = add_trailer(stream, name, nlen, value, vlen);
}
+ else {
+ status = APR_EINVAL;
+ }
+
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
H2_STRM_MSG(stream, "header %s not accepted"), name);
return NULL;
}
+static apr_status_t add_buffered_data(h2_stream *stream, apr_off_t requested,
+ apr_off_t *plen, int *peos, int *is_all,
+ h2_headers **pheaders)
+{
+ apr_bucket *b, *e;
+
+ *peos = 0;
+ *plen = 0;
+ *is_all = 0;
+ if (pheaders) {
+ *pheaders = NULL;
+ }
+
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "add_buffered_data");
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_FLUSH(b)) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ *peos = 1;
+ return APR_SUCCESS;
+ }
+ else if (H2_BUCKET_IS_HEADERS(b)) {
+ if (*plen > 0) {
+ /* data before the response, can only return up to here */
+ return APR_SUCCESS;
+ }
+ else if (pheaders) {
+ *pheaders = h2_bucket_headers_get(b);
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+ H2_STRM_MSG(stream, "prep, -> response %d"),
+ (*pheaders)->status);
+ return APR_SUCCESS;
+ }
+ else {
+ return APR_EAGAIN;
+ }
+ }
+ }
+ else if (b->length == 0) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ else {
+ ap_assert(b->length != (apr_size_t)-1);
+ *plen += b->length;
+ if (*plen >= requested) {
+ *plen = requested;
+ return APR_SUCCESS;
+ }
+ }
+ b = e;
+ }
+ *is_all = 1;
+ return APR_SUCCESS;
+}
+
apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
- int *peos, h2_headers **presponse)
+ int *peos, h2_headers **pheaders)
{
apr_status_t status = APR_SUCCESS;
- apr_off_t requested, max_chunk = H2_DATA_CHUNK_SIZE;
- apr_bucket *b, *e;
+ apr_off_t requested, missing, max_chunk = H2_DATA_CHUNK_SIZE;
conn_rec *c;
+ int complete;
- if (presponse) {
- *presponse = NULL;
- }
-
ap_assert(stream);
if (stream->rst_error) {
if (stream->session->io.write_size > 0) {
max_chunk = stream->session->io.write_size - 9; /* header bits */
}
- *plen = requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
+ requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
+
+ /* count the buffered data until eos or a headers bucket */
+ status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
+
+ if (status == APR_EAGAIN) {
+ /* TODO: ugly, someone needs to retrieve the response first */
+ h2_mplx_keep_active(stream->session->mplx, stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ H2_STRM_MSG(stream, "prep, response eagain"));
+ return status;
+ }
+ else if (status != APR_SUCCESS) {
+ return status;
+ }
- h2_util_bb_avail(stream->out_buffer, plen, peos);
- if (!*peos && *plen < requested && *plen < stream->max_mem) {
- H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+ if (pheaders && *pheaders) {
+ return APR_SUCCESS;
+ }
+
+ /* If there we do not have enough buffered data to satisfy the requested
+ * length *and* we counted the _complete_ buffer (and did not stop in the middle
+ * because of meta data there), lets see if we can read more from the
+ * output beam */
+ missing = H2MIN(requested, stream->max_mem) - *plen;
+ if (complete && !*peos && missing > 0) {
+ apr_status_t rv = APR_EOF;
+
if (stream->output) {
- status = h2_beam_receive(stream->output, stream->out_buffer,
- APR_NONBLOCK_READ,
- stream->max_mem - *plen);
- }
- else {
- status = APR_EOF;
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+ rv = h2_beam_receive(stream->output, stream->out_buffer,
+ APR_NONBLOCK_READ, stream->max_mem - *plen);
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
}
- if (APR_STATUS_IS_EOF(status)) {
+ if (rv == APR_SUCCESS) {
+ /* count the buffer again, now that we have read output */
+ status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
+ }
+ else if (APR_STATUS_IS_EOF(rv)) {
apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
- status = APR_SUCCESS;
+ *peos = 1;
}
- else if (status == APR_EAGAIN) {
- status = APR_SUCCESS;
- }
- *plen = requested;
- h2_util_bb_avail(stream->out_buffer, plen, peos);
- H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
- }
- else {
- H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "ok");
- }
-
- b = APR_BRIGADE_FIRST(stream->out_buffer);
- while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
- e = APR_BUCKET_NEXT(b);
- if (APR_BUCKET_IS_FLUSH(b)
- || (!APR_BUCKET_IS_METADATA(b) && b->length == 0)) {
- APR_BUCKET_REMOVE(b);
- apr_bucket_destroy(b);
- }
- else {
- break;
- }
- b = e;
- }
-
- b = get_first_headers_bucket(stream->out_buffer);
- if (b) {
- /* there are HEADERS to submit */
- *peos = 0;
- *plen = 0;
- if (b == APR_BRIGADE_FIRST(stream->out_buffer)) {
- if (presponse) {
- *presponse = h2_bucket_headers_get(b);
- APR_BUCKET_REMOVE(b);
- apr_bucket_destroy(b);
- status = APR_SUCCESS;
- }
- else {
- /* someone needs to retrieve the response first */
- h2_mplx_keep_active(stream->session->mplx, stream->id);
- status = APR_EAGAIN;
- }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* we set this is the status of this call only if there
+ * is no buffered data, see check below */
}
else {
- apr_bucket *e = APR_BRIGADE_FIRST(stream->out_buffer);
- while (e != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
- if (e == b) {
- break;
- }
- else if (e->length != (apr_size_t)-1) {
- *plen += e->length;
- }
- e = APR_BUCKET_NEXT(e);
- }
+ /* real error reading. Give this back directly, even though
+ * we may have something buffered. */
+ status = rv;
}
}
-
+
if (status == APR_SUCCESS) {
- if (presponse && *presponse) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
- H2_STRM_MSG(stream, "prepare, response %d"),
- (*presponse)->status);
- }
- else if (*peos || *plen) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ if (*peos || *plen) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
H2_STRM_MSG(stream, "prepare, len=%ld eos=%d"),
(long)*plen, *peos);
}
else {
status = APR_EAGAIN;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
H2_STRM_MSG(stream, "prepare, no data"));
}
}
apr_off_t consumed = amount;
while (consumed > 0) {
- int len = (consumed > INT_MAX)? INT_MAX : consumed;
+ int len = (consumed > INT_MAX)? INT_MAX : (int)consumed;
nghttp2_session_consume(session->ngh2, stream->id, len);
consumed -= len;
}