h2_io.lo dnl
h2_io_set.lo dnl
h2_mplx.lo dnl
+h2_push.lo dnl
h2_request.lo dnl
h2_response.lo dnl
h2_session.lo dnl
h2_task_input.lo dnl
h2_task_output.lo dnl
h2_task_queue.lo dnl
-h2_to_h1.lo dnl
h2_util.lo dnl
h2_worker.lo dnl
h2_workers.lo dnl
return APR_SUCCESS;
}
-h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1)
-{
- return from_h1->state;
-}
-
static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state)
{
if (from_h1->state != state) {
static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
{
from_h1->response = h2_response_create(from_h1->stream_id, 0,
- from_h1->status, from_h1->hlines,
+ from_h1->http_status, from_h1->hlines,
from_h1->pool);
- if (from_h1->response == NULL) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
- APLOGNO(02915)
- "h2_from_h1(%d): unable to create resp_head",
- from_h1->stream_id);
- return APR_EINVAL;
- }
from_h1->content_length = from_h1->response->content_length;
from_h1->chunked = r->chunked;
}
if (from_h1->state == H2_RESP_ST_STATUS_LINE) {
/* instead of parsing, just take it directly */
- from_h1->status = apr_psprintf(from_h1->pool,
- "%d", f->r->status);
+ from_h1->http_status = f->r->status;
from_h1->state = H2_RESP_ST_HEADERS;
}
else if (line[0] == '\0') {
apr_off_t content_length;
int chunked;
- const char *status;
+ int http_status;
apr_array_header_t *hlines;
struct h2_response *response;
};
-typedef void h2_from_h1_state_change_cb(struct h2_from_h1 *resp,
- h2_from_h1_state_t prevstate,
- void *cb_ctx);
-
h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool);
apr_status_t h2_from_h1_destroy(h2_from_h1 *response);
-void h2_from_h1_set_state_change_cb(h2_from_h1 *from_h1,
- h2_from_h1_state_change_cb *callback,
- void *cb_ctx);
-
apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1,
ap_filter_t* f, apr_bucket_brigade* bb);
struct h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1);
-h2_from_h1_state_t h2_from_h1_get_state(h2_from_h1 *from_h1);
-
apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb);
#endif /* defined(__mod_h2__h2_from_h1__) */
/* Maximum number of padding bytes in a frame, rfc7540 */
#define H2_MAX_PADLEN 256
+#define H2_HTTP_2XX(a) ((a) >= 200 && (a) < 300)
+
+#define H2_STREAM_CLIENT_INITIATED(id) (id&0x01)
+
/**
* Provide a user readable description of the HTTP/2 error code-
* @param h2_error http/2 error code, as in rfc 7540, ch. 7
static void h2_io_cleanup(h2_io *io)
{
- if (io->task) {
- h2_task_destroy(io->task);
- io->task = NULL;
- }
}
void h2_io_destroy(h2_io *io)
struct h2_io {
int id; /* stream identifier */
apr_pool_t *pool; /* stream pool */
- apr_bucket_brigade *bbin; /* input data for stream */
- int eos_in;
- int task_done;
- int rst_error;
int zombie;
- struct h2_task *task; /* task created for this io */
+ int task_done;
+ struct h2_task *task; /* task created for this io */
- apr_size_t input_consumed; /* how many bytes have been read */
+ struct h2_response *response;/* submittable response created */
+ int rst_error;
+
+ int eos_in;
+ apr_bucket_brigade *bbin; /* input data for stream */
struct apr_thread_cond_t *input_arrived; /* block on reading */
+ apr_size_t input_consumed; /* how many bytes have been read */
- apr_bucket_brigade *bbout; /* output data from stream */
int eos_out;
+ apr_bucket_brigade *bbout; /* output data from stream */
struct apr_thread_cond_t *output_drained; /* block on writing */
- struct h2_response *response;/* submittable response created */
int files_handles_owned;
};
if (io) {
if (f) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
- "h2_mplx(%ld-%d): open response: %s, rst=%d",
- m->id, stream_id, response->status,
+ "h2_mplx(%ld-%d): open response: %d, rst=%d",
+ m->id, stream_id, response->http_status,
response->rst_error);
}
* reset.
*/
h2_response *r = h2_response_create(stream_id, 0,
- "500", NULL, m->pool);
+ 500, NULL, m->pool);
status = out_open(m, stream_id, r, NULL, NULL, NULL);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c,
"h2_mplx(%ld-%d): close, no response, no rst",
apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
- struct h2_request *r, int eos,
+ const h2_request *req, int eos,
h2_stream_pri_cmp *cmp, void *ctx)
{
apr_status_t status;
io = open_io(m, stream_id);
c = h2_conn_create(m->c, io->pool);
- io->task = h2_task_create(m->id, stream_id, io->pool, m, c);
-
- status = h2_request_end_headers(r, m, io->task, eos);
- if (status == APR_SUCCESS && eos) {
+ io->task = h2_task_create(m->id, req, io->pool, m, c, eos);
+
+ if (eos) {
status = h2_io_in_close(io);
}
- if (status == APR_SUCCESS) {
- x.cmp = cmp;
- x.ctx = ctx;
- h2_tq_add(m->q, io->task, task_cmp, &x);
- }
+ x.cmp = cmp;
+ x.ctx = ctx;
+ h2_tq_add(m->q, io->task, task_cmp, &x);
+
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
"h2_mplx(%ld-%d): process", m->c->id, stream_id);
apr_thread_mutex_unlock(m->lock);
* @param ctx context data for the compare function
*/
apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
- struct h2_request *r, int eos,
+ const struct h2_request *r, int eos,
h2_stream_pri_cmp *cmp, void *ctx);
/**
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * 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
+
+ * 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 <stdio.h>
+
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_h2.h"
+#include "h2_util.h"
+#include "h2_push.h"
+#include "h2_request.h"
+#include "h2_response.h"
+
+
+typedef struct {
+ apr_array_header_t *pushes;
+ apr_pool_t *pool;
+ const h2_request *req;
+} link_ctx;
+
+static size_t skip_ws(const char *s, size_t i, size_t max)
+{
+ char c;
+ while (i < max && (((c = s[i]) == ' ') || (c == '\t'))) {
+ ++i;
+ }
+ return i;
+}
+
+static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
+{
+ /* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+ Link = "Link" ":" #link-value
+ link-value = "<" URI-Reference ">" *( ";" link-param )
+ link-param = ( ( "rel" "=" relation-types )
+ | ( "anchor" "=" <"> URI-Reference <"> )
+ | ( "rev" "=" relation-types )
+ | ( "hreflang" "=" Language-Tag )
+ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
+ | ( "title" "=" quoted-string )
+ | ( "title*" "=" ext-value )
+ | ( "type" "=" ( media-type | quoted-mt ) )
+ | ( link-extension ) )
+ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
+ | ( ext-name-star "=" ext-value )
+ ext-name-star = parmname "*" ; reserved for RFC2231-profiled
+ ; extensions. Whitespace NOT
+ ; allowed in between.
+ ptoken = 1*ptokenchar
+ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
+ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
+ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
+ | "[" | "]" | "^" | "_" | "`" | "{" | "|"
+ | "}" | "~"
+ media-type = type-name "/" subtype-name
+ quoted-mt = <"> media-type <">
+ relation-types = relation-type
+ | <"> relation-type *( 1*SP relation-type ) <">
+ relation-type = reg-rel-type | ext-rel-type
+ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
+ ext-rel-type = URI
+ */
+ /* TODO */
+ (void)skip_ws;
+}
+
+apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
+ const h2_response *res)
+{
+ link_ctx ctx;
+
+ ctx.pushes = NULL;
+ ctx.pool = p;
+ ctx.req = req;
+
+ /* Collect push candidates from the request/response pair.
+ *
+ * One source for pushes are "rel=preload" link headers
+ * in the response.
+ *
+ * TODO: This may be extended in the future by hooks or callbacks
+ * where other modules can provide push information directly.
+ */
+ if (res->ngheader) {
+ int i;
+ for (i = 0; i < res->ngheader->nvlen; ++i) {
+ nghttp2_nv *nv = &res->ngheader->nv[i];
+ if (nv->namelen == 4
+ && apr_strnatcasecmp("link", (const char *)nv->name)) {
+ inspect_link(&ctx, (const char *)nv->value, nv->valuelen);
+ }
+ }
+ }
+
+ return ctx.pushes;
+}
--- /dev/null
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * 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
+
+ * 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.
+ */
+#ifndef __mod_h2__h2_push__
+#define __mod_h2__h2_push__
+
+struct h2_request;
+struct h2_response;
+struct h2_ngheader;
+
+typedef struct h2_push {
+ int initiating_id;
+ const struct h2_request *req;
+ const struct h2_ngheader *promise;
+ const char *as;
+} h2_push;
+
+
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ const struct h2_response *res);
+
+#endif /* defined(__mod_h2__h2_push__) */
#include "h2_private.h"
#include "h2_mplx.h"
-#include "h2_to_h1.h"
#include "h2_request.h"
#include "h2_task.h"
#include "h2_util.h"
-h2_request *h2_request_create(int id, apr_pool_t *pool,
- apr_bucket_alloc_t *bucket_alloc)
+h2_request *h2_request_create(int id, apr_pool_t *pool)
{
h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
- if (req) {
- req->id = id;
- req->pool = pool;
- req->bucket_alloc = bucket_alloc;
- }
+
+ req->id = id;
+ req->headers = apr_table_make(pool, 10);
+ req->content_length = -1;
+
return req;
}
void h2_request_destroy(h2_request *req)
{
- if (req->to_h1) {
- h2_to_h1_destroy(req->to_h1);
- req->to_h1 = NULL;
+}
+
+static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
+{
+ char *hname, *hvalue;
+
+ if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) {
+ if (!apr_strnatcasecmp("chunked", value)) {
+ /* This should never arrive here in a HTTP/2 request */
+ ap_log_perror(APLOG_MARK, APLOG_ERR, APR_BADARG, pool,
+ APLOGNO(02945)
+ "h2_request: 'transfer-encoding: chunked' received");
+ return APR_BADARG;
+ }
+ }
+ else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
+ char *end;
+ req->content_length = apr_strtoi64(value, &end, 10);
+ if (value == end) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
+ APLOGNO(02959)
+ "h2_request(%d): content-length value not parsed: %s",
+ req->id, value);
+ return APR_EINVAL;
+ }
+ req->chunked = 0;
+ }
+ else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
+ /* If we see a content-type and have no length (yet),
+ * we need to chunk. */
+ req->chunked = (req->content_length == -1);
+ }
+ else if ((req->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
+ || H2_HD_MATCH_LIT("expect", name, nlen)
+ || H2_HD_MATCH_LIT("upgrade", name, nlen)
+ || H2_HD_MATCH_LIT("connection", name, nlen)
+ || H2_HD_MATCH_LIT("proxy-connection", name, nlen)
+ || H2_HD_MATCH_LIT("keep-alive", name, nlen)
+ || H2_HD_MATCH_LIT("http2-settings", name, nlen)) {
+ /* ignore these. */
+ return APR_SUCCESS;
}
+ else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
+ const char *existing = apr_table_get(req->headers, "cookie");
+ if (existing) {
+ char *nval;
+
+ /* Cookie headers come separately in HTTP/2, but need
+ * to be merged by "; " (instead of default ", ")
+ */
+ hvalue = apr_pstrndup(pool, value, vlen);
+ nval = apr_psprintf(pool, "%s; %s", existing, hvalue);
+ apr_table_setn(req->headers, "Cookie", nval);
+ return APR_SUCCESS;
+ }
+ }
+ else if (H2_HD_MATCH_LIT("host", name, nlen)) {
+ req->seen_host = 1;
+ }
+
+ hname = apr_pstrndup(pool, name, nlen);
+ hvalue = apr_pstrndup(pool, value, vlen);
+ h2_util_camel_case_header(hname, nlen);
+ apr_table_mergen(req->headers, hname, hvalue);
+
+ return APR_SUCCESS;
}
-static apr_status_t insert_request_line(h2_request *req, h2_mplx *m);
+typedef struct {
+ h2_request *req;
+ apr_pool_t *pool;
+} h1_ctx;
-apr_status_t h2_request_rwrite(h2_request *req, request_rec *r, h2_mplx *m)
+static int set_h1_header(void *ctx, const char *key, const char *value)
+{
+ h1_ctx *x = ctx;
+ add_h1_header(x->req, x->pool, key, strlen(key), value, strlen(value));
+ return 1;
+}
+
+static apr_status_t add_h1_headers(h2_request *req, apr_pool_t *pool,
+ apr_table_t *headers)
+{
+ h1_ctx x;
+ x.req = req;
+ x.pool = pool;
+ apr_table_do(set_h1_header, &x, headers, NULL);
+ return APR_SUCCESS;
+}
+
+
+apr_status_t h2_request_rwrite(h2_request *req, request_rec *r)
{
apr_status_t status;
- req->method = r->method;
+
+ req->method = r->method;
req->authority = r->hostname;
- req->path = r->uri;
+ req->path = r->uri;
+ req->scheme = (r->parsed_uri.scheme? r->parsed_uri.scheme
+ : r->server->server_scheme);
+
if (!ap_strchr_c(req->authority, ':') && r->parsed_uri.port_str) {
- req->authority = apr_psprintf(req->pool, "%s:%s", req->authority,
+ req->authority = apr_psprintf(r->pool, "%s:%s", req->authority,
r->parsed_uri.port_str);
}
- req->scheme = NULL;
-
- status = insert_request_line(req, m);
- if (status == APR_SUCCESS) {
- status = h2_to_h1_add_headers(req->to_h1, r->headers_in);
- }
+ status = add_h1_headers(req, r->pool, r->headers_in);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
- "h2_request(%d): written request %s %s, host=%s",
- req->id, req->method, req->path, req->authority);
+ "h2_request(%d): rwrite %s host=%s://%s%s",
+ req->id, req->method, req->scheme, req->authority, req->path);
return status;
}
-apr_status_t h2_request_write_header(h2_request *req,
- const char *name, size_t nlen,
- const char *value, size_t vlen,
- h2_mplx *m)
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
{
apr_status_t status = APR_SUCCESS;
if (name[0] == ':') {
/* pseudo header, see ch. 8.1.2.3, always should come first */
- if (req->to_h1) {
- ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool,
+ if (!apr_is_empty_table(req->headers)) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
APLOGNO(02917)
"h2_request(%d): pseudo header after request start",
req->id);
if (H2_HEADER_METHOD_LEN == nlen
&& !strncmp(H2_HEADER_METHOD, name, nlen)) {
- req->method = apr_pstrndup(req->pool, value, vlen);
+ req->method = apr_pstrndup(pool, value, vlen);
}
else if (H2_HEADER_SCHEME_LEN == nlen
&& !strncmp(H2_HEADER_SCHEME, name, nlen)) {
- req->scheme = apr_pstrndup(req->pool, value, vlen);
+ req->scheme = apr_pstrndup(pool, value, vlen);
}
else if (H2_HEADER_PATH_LEN == nlen
&& !strncmp(H2_HEADER_PATH, name, nlen)) {
- req->path = apr_pstrndup(req->pool, value, vlen);
+ req->path = apr_pstrndup(pool, value, vlen);
}
else if (H2_HEADER_AUTH_LEN == nlen
&& !strncmp(H2_HEADER_AUTH, name, nlen)) {
- req->authority = apr_pstrndup(req->pool, value, vlen);
+ req->authority = apr_pstrndup(pool, value, vlen);
}
else {
char buffer[32];
memset(buffer, 0, 32);
strncpy(buffer, name, (nlen > 31)? 31 : nlen);
- ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool,
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
APLOGNO(02954)
"h2_request(%d): ignoring unknown pseudo header %s",
req->id, buffer);
}
else {
/* non-pseudo header, append to work bucket of stream */
- if (!req->to_h1) {
- status = insert_request_line(req, m);
- if (status != APR_SUCCESS) {
- return status;
- }
- }
-
- if (status == APR_SUCCESS) {
- status = h2_to_h1_add_header(req->to_h1,
- name, nlen, value, vlen);
- }
+ status = add_h1_header(req, pool, name, nlen, value, vlen);
}
return status;
}
-apr_status_t h2_request_write_data(h2_request *req,
- const char *data, size_t len)
-{
- return h2_to_h1_add_data(req->to_h1, data, len);
-}
-
-apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
- h2_task *task, int eos)
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
{
- apr_status_t status;
+ if (req->eoh) {
+ return APR_EINVAL;
+ }
- if (!req->to_h1) {
- status = insert_request_line(req, m);
- if (status != APR_SUCCESS) {
- return status;
+ if (!req->seen_host) {
+ /* Need to add a "Host" header if not already there to
+ * make virtual hosts work correctly. */
+ if (!req->authority) {
+ return APR_BADARG;
}
+ apr_table_set(req->headers, "Host", req->authority);
}
- status = h2_to_h1_end_headers(req->to_h1, eos);
- h2_task_set_request(task, req->to_h1->method,
- req->to_h1->scheme,
- req->to_h1->authority,
- req->to_h1->path,
- req->to_h1->headers, eos);
- return status;
-}
-apr_status_t h2_request_close(h2_request *req)
-{
- return h2_to_h1_close(req->to_h1);
+ if (eos && req->chunked) {
+ /* We had chunking figured out, but the EOS is already there.
+ * unmark chunking and set a definitive content-length.
+ */
+ req->chunked = 0;
+ apr_table_setn(req->headers, "Content-Length", "0");
+ }
+ else if (req->chunked) {
+ /* We have not seen a content-length. We therefore must
+ * pass any request content in chunked form.
+ */
+ apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
+ }
+
+ req->eoh = 1;
+
+ return APR_SUCCESS;
}
-static apr_status_t insert_request_line(h2_request *req, h2_mplx *m)
-{
- req->to_h1 = h2_to_h1_create(req->id, req->pool, req->bucket_alloc,
- req->method,
- req->scheme,
- req->authority,
- req->path, m);
- return req->to_h1? APR_SUCCESS : APR_ENOMEM;
-}
+#define OPT_COPY(p, s) ((s)? apr_pstrdup(p, s) : NULL)
-apr_status_t h2_request_flush(h2_request *req)
+void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src)
{
- return h2_to_h1_flush(req->to_h1);
+ /* keep the dst id */
+ dst->method = OPT_COPY(p, src->method);
+ dst->scheme = OPT_COPY(p, src->method);
+ dst->authority = OPT_COPY(p, src->method);
+ dst->path = OPT_COPY(p, src->method);
+ dst->headers = apr_table_clone(p, src->headers);
+ dst->content_length = src->content_length;
+ dst->chunked = src->chunked;
+ dst->eoh = src->eoh;
+ dst->seen_host = src->seen_host;
}
/* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal
* format that will be fed to various httpd input filters to finally
* become a request_rec to be handled by soemone.
- *
- * Ideally, we would make a request_rec without serializing the headers
- * we have only to make someone else parse them back.
*/
struct h2_to_h1;
struct h2_mplx;
typedef struct h2_request h2_request;
struct h2_request {
- int id; /* http2 stream id */
- apr_pool_t *pool;
- apr_bucket_alloc_t *bucket_alloc;
- struct h2_to_h1 *to_h1; /* Converter to HTTP/1.1 format*/
-
+ int id; /* stream id */
+
/* pseudo header values, see ch. 8.1.2.3 */
const char *method;
const char *scheme;
const char *authority;
const char *path;
+
+ apr_table_t *headers;
+
+ apr_off_t content_length;
+ int chunked;
+ int eoh;
+
+ int seen_host;
};
-h2_request *h2_request_create(int id, apr_pool_t *pool,
- apr_bucket_alloc_t *bucket_alloc);
+h2_request *h2_request_create(int id, apr_pool_t *pool);
+
void h2_request_destroy(h2_request *req);
-apr_status_t h2_request_flush(h2_request *req);
+apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
-apr_status_t h2_request_write_header(h2_request *req,
- const char *name, size_t nlen,
- const char *value, size_t vlen,
- struct h2_mplx *m);
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen);
-apr_status_t h2_request_write_data(h2_request *request,
- const char *data, size_t len);
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos);
-apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
- struct h2_task *task, int eos);
+void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src);
-apr_status_t h2_request_close(h2_request *req);
-apr_status_t h2_request_rwrite(h2_request *req, request_rec *r,
- struct h2_mplx *m);
#endif /* defined(__mod_h2__h2_request__) */
#include "h2_private.h"
#include "h2_h2.h"
#include "h2_util.h"
+#include "h2_push.h"
#include "h2_response.h"
-static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
- apr_table_t *header);
+static void make_ngheader(apr_pool_t *pool, h2_response *to,
+ apr_table_t *header);
static int ignore_header(const char *name)
{
h2_response *h2_response_create(int stream_id,
int rst_error,
- const char *http_status,
+ int http_status,
apr_array_header_t *hlines,
apr_pool_t *pool)
{
response->stream_id = stream_id;
response->rst_error = rst_error;
- response->status = http_status? http_status : "500";
+ response->http_status = http_status? http_status : 500;
response->content_length = -1;
if (hlines) {
}
response->stream_id = stream_id;
- response->status = apr_psprintf(pool, "%d", r->status);
+ response->http_status = r->status;
response->content_length = -1;
response->rheader = header;
- if (r->status == HTTP_FORBIDDEN) {
+ if (response->http_status == HTTP_FORBIDDEN) {
const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
if (cause) {
/* This request triggered a TLS renegotiation that is now allowed
* in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
*/
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r->status, r,
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, response->http_status, r,
"h2_response(%ld-%d): renegotiate forbidden, cause: %s",
(long)r->connection->id, stream_id, cause);
response->rst_error = H2_ERR_HTTP_1_1_REQUIRED;
{
h2_response *to = apr_pcalloc(pool, sizeof(h2_response));
to->stream_id = from->stream_id;
- to->status = apr_pstrdup(pool, from->status);
+ to->http_status = from->http_status;
to->content_length = from->content_length;
if (from->rheader) {
- to->ngheader = make_ngheader(pool, to->status, from->rheader);
+ make_ngheader(pool, to, from->rheader);
}
return to;
}
size_t offset;
char *strbuf;
apr_pool_t *pool;
+ apr_array_header_t *pushes;
} nvctx_t;
static int count_header(void *ctx, const char *key, const char *value)
#define NV_ADD_LIT_CS(nv, k, v) addnv_lit_cs(nv, k, sizeof(k) - 1, v, strlen(v))
#define NV_ADD_CS_CS(nv, k, v) addnv_cs_cs(nv, k, strlen(k), v, strlen(v))
#define NV_BUF_ADD(nv, s, len) memcpy(nv->strbuf, s, len); \
-s = nv->strbuf; \
-nv->strbuf += len + 1
+ s = nv->strbuf; \
+ nv->strbuf += len + 1
static void addnv_cs_cs(nvctx_t *ctx, const char *key, size_t key_len,
const char *value, size_t val_len)
ctx->offset++;
}
+static h2_push *h2_parse_preload(apr_pool_t *pool, const char *str)
+{
+ /* TODO: implement this */
+ return NULL;
+}
+
static int add_header(void *ctx, const char *key, const char *value)
{
if (!ignore_header(key)) {
nvctx_t *nvctx = (nvctx_t*)ctx;
NV_ADD_CS_CS(nvctx, key, value);
+
+ if (apr_strnatcasecmp("link", key)
+ && ap_strstr_c("preload", value)) {
+ /* Detect link headers with rel=preload to use as possible
+ * server push candidates. */
+ h2_push *push;
+ if ((push = h2_parse_preload(nvctx->pool, value))) {
+ if (!nvctx->pushes) {
+ nvctx->pushes = apr_array_make(nvctx->pool, 5,
+ sizeof(h2_push*));
+ }
+ APR_ARRAY_PUSH(nvctx->pushes, h2_push*) = push;
+ }
+ }
}
return 1;
}
-static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status,
- apr_table_t *header)
+static void make_ngheader(apr_pool_t *pool, h2_response *to,
+ apr_table_t *header)
{
size_t n;
h2_ngheader *h;
nvctx_t ctx;
+ char *status;
+ to->ngheader = NULL;
+ to->pushes = NULL;
+
+ status = apr_psprintf(pool, "%d", to->http_status);
ctx.nv = NULL;
ctx.nvlen = 1;
ctx.nvstrlen = strlen(status) + 1;
ctx.offset = 0;
ctx.strbuf = NULL;
ctx.pool = pool;
+ ctx.pushes = NULL;
apr_table_do(count_header, &ctx, header, NULL);
NV_ADD_LIT_CS(&ctx, ":status", status);
apr_table_do(add_header, &ctx, header, NULL);
- h->nv = ctx.nv;
+ h->nv = ctx.nv;
h->nvlen = ctx.nvlen;
+
+ to->ngheader = h;
+ to->pushes = ctx.pushes;
}
- return h;
}
+int h2_response_push_count(h2_response *response)
+{
+ return response->pushes? response->pushes->nelts : 0;
+}
+
+h2_push *h2_response_get_push(h2_response *response, size_t i)
+{
+ if (response->pushes && i < response->pushes->nelts) {
+ return APR_ARRAY_IDX(response->pushes, i, h2_push*);
+ }
+ return NULL;
+}
+
+
#ifndef __mod_h2__h2_response__
#define __mod_h2__h2_response__
+struct h2_push;
+
/* h2_response is just the data belonging the the head of a HTTP response,
* suitable prepared to be fed to nghttp2 for response submit.
*/
apr_size_t nvlen;
} h2_ngheader;
+struct h2_push;
+
typedef struct h2_response {
int stream_id;
int rst_error;
- const char *status;
+ int http_status;
apr_off_t content_length;
apr_table_t *rheader;
h2_ngheader *ngheader;
+ apr_array_header_t *pushes;
} h2_response;
h2_response *h2_response_create(int stream_id,
int rst_error,
- const char *http_status,
+ int http_status,
apr_array_header_t *hlines,
apr_pool_t *pool);
h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from);
+/**
+ * Get the number of push proposals included with the response.
+ * @return number of push proposals in this response
+ */
+int h2_response_push_count(h2_response *response);
+
+/**
+ * Get the ith h2_push contained in this response.
+ *
+ * @param response the response
+ * @param i the index of the push to get
+ * @return the ith h2_push or NULL if out of bounds
+ */
+struct h2_push *h2_response_get_push(h2_response *response, size_t i);
+
+
#endif /* defined(__mod_h2__h2_response__) */
#include "h2_config.h"
#include "h2_h2.h"
#include "h2_mplx.h"
+#include "h2_push.h"
#include "h2_response.h"
#include "h2_stream.h"
#include "h2_stream_set.h"
return NGHTTP2_ERR_PROTO;
}
-static int stream_open(h2_session *session, int stream_id)
+h2_stream *h2_session_open_stream(h2_session *session, int stream_id)
{
h2_stream * stream;
apr_pool_t *stream_pool;
if (session->aborted) {
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ return NULL;
}
if (session->spare) {
apr_pool_create(&stream_pool, session->pool);
}
- stream = h2_stream_create(stream_id, stream_pool, session);
- stream->state = H2_STREAM_ST_OPEN;
+ stream = h2_stream_open(stream_id, stream_pool, session);
h2_stream_set_add(session->streams, stream);
- if (stream->id > session->max_stream_received) {
+ if (H2_STREAM_CLIENT_INITIATED(stream_id)
+ && stream_id > session->max_stream_received) {
session->max_stream_received = stream->id;
}
"h2_session: stream(%ld-%d): opened",
session->id, stream_id);
- return 0;
+ return stream;
}
static apr_status_t h2_session_flush(h2_session *session)
static int on_begin_headers_cb(nghttp2_session *ngh2,
const nghttp2_frame *frame, void *userp)
{
+ h2_stream *s;
+
/* This starts a new stream. */
- int rv;
(void)ngh2;
- rv = stream_open((h2_session *)userp, frame->hd.stream_id);
- if (rv != NGHTTP2_ERR_CALLBACK_FAILURE) {
- /* on_header_cb or on_frame_recv_cb will dectect that stream
- does not exist and submit RST_STREAM. */
- return 0;
- }
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ s = h2_session_open_stream((h2_session *)userp, frame->hd.stream_id);
+ return s? 0 : NGHTTP2_ERR_CALLBACK_FAILURE;
}
static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
if (session->aborted) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
+
stream = h2_session_get_stream(session, frame->hd.stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
- status = h2_stream_write_header(stream,
- (const char *)name, namelen,
- (const char *)value, valuelen);
+ status = h2_stream_add_header(stream, (const char *)name, namelen,
+ (const char *)value, valuelen);
+
if (status != APR_SUCCESS) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
const nghttp2_frame *frame,
void *userp)
{
- int rv;
h2_session *session = (h2_session *)userp;
apr_status_t status = APR_SUCCESS;
+ h2_stream *stream;
+
if (session->aborted) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- ++session->frames_received;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
"h2_session(%ld): on_frame_rcv #%ld, type=%d", session->id,
(long)session->frames_received, frame->hd.type);
+
+ ++session->frames_received;
switch (frame->hd.type) {
- case NGHTTP2_HEADERS: {
- int eos;
- h2_stream * stream = h2_session_get_stream(session,
- 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;
+ case NGHTTP2_HEADERS:
+ stream = h2_session_get_stream(session, frame->hd.stream_id);
+ if (stream) {
+ int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+ status = stream_end_headers(session, stream, eos);
+ }
+ else {
+ status = APR_EINVAL;
}
-
- eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
- status = stream_end_headers(session, stream, eos);
-
break;
- }
- case NGHTTP2_DATA: {
- h2_stream * stream = h2_session_get_stream(session,
- 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;
+ case NGHTTP2_DATA:
+ stream = h2_session_get_stream(session, frame->hd.stream_id);
+ if (stream) {
+ int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+ if (eos) {
+ status = h2_stream_close_input(stream);
}
- return 0;
+ }
+ else {
+ status = APR_EINVAL;
}
break;
- }
- case NGHTTP2_PRIORITY: {
+ case NGHTTP2_PRIORITY:
session->reprioritize = 1;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 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.stream_id,
frame->priority.pri_spec.exclusive);
break;
- }
- case NGHTTP2_WINDOW_UPDATE: {
+ case NGHTTP2_WINDOW_UPDATE:
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
"h2_session: stream(%ld-%d): WINDOW_UPDATE "
"incr=%d",
session->id, (int)frame->hd.stream_id,
frame->window_update.window_size_increment);
break;
- }
default:
if (APLOGctrace2(session->c)) {
char buffer[256];
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_session_get_stream(session,
- 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,
+ int rv;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
APLOGNO(02923)
"h2_session: stream(%ld-%d): error handling frame",
session->id, (int)frame->hd.stream_id);
if (nghttp2_is_fatal(rv)) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- return 0;
}
return 0;
}
/* Now we need to auto-open stream 1 for the request we got. */
- *rv = stream_open(session, 1);
- if (*rv != 0) {
+ stream = h2_session_open_stream(session, 1);
+ if (!stream) {
status = APR_EGENERAL;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
APLOGNO(02933) "open stream 1: %s",
return status;
}
- stream = h2_session_get_stream(session, 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);
+ status = h2_stream_set_request(stream, session->r);
if (status != APR_SUCCESS) {
return 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);
+ if (!session->last_stream || stream_id != session->last_stream->id) {
+ session->last_stream = h2_stream_set_get(session->streams, stream_id);
+ }
+ return session->last_stream;
}
/* h2_io_on_read_cb implementation that offers the data read
provider.read_callback = stream_data_cb;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- "h2_stream(%ld-%d): submitting response %s",
- session->id, stream->id, response->status);
+ "h2_stream(%ld-%d): submitting response %d",
+ session->id, stream->id, response->http_status);
rv = nghttp2_submit_response(session->ngh2, response->stream_id,
response->ngheader->nv,
response->ngheader->nvlen, &provider);
-
- if (rv != 0) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
- APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s",
- session->id, response->stream_id, nghttp2_strerror(rv));
+
+ if (!rv
+ && !stream->promised_on
+ && H2_HTTP_2XX(response->http_status)
+ && h2_session_push_enabled(session)) {
+
+ h2_stream_submit_pushes(stream);
}
}
else {
+ int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_stream(%ld-%d): RST_STREAM, err=%d",
+ session->id, stream->id, err);
+
rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
- stream->id,
- H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR));
+ stream->id, err);
}
stream->submitted = 1;
return status;
}
+struct h2_stream *h2_session_push(h2_session *session, h2_push *push)
+{
+ apr_status_t status;
+ h2_stream *stream;
+ int nid;
+
+ nid = nghttp2_submit_push_promise(session->ngh2, 0, push->initiating_id,
+ push->promise->nv, push->promise->nvlen,
+ NULL);
+ if (nid <= 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): submitting push promise fail: %s",
+ session->id, push->initiating_id,
+ nghttp2_strerror(nid));
+ return NULL;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+ "h2_stream(%ld-%d): promised new stream %d",
+ session->id, push->initiating_id, nid);
+
+ stream = h2_session_open_stream(session, nid);
+ if (stream) {
+ h2_stream_set_h2_request(stream, push->req);
+ status = stream_end_headers(session, stream, 1);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c,
+ "h2_stream(%ld-%d): scheduling push stream",
+ session->id, stream->id);
+ h2_stream_cleanup(stream);
+ stream = NULL;
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, session->c,
+ "h2_stream(%ld-%d): failed to create stream obj %d",
+ session->id, push->initiating_id, nid);
+ }
+
+ if (!stream) {
+ /* try to tell the client that it should not wait. */
+ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid,
+ NGHTTP2_INTERNAL_ERROR);
+ }
+
+ return stream;
+}
+
apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream)
{
apr_pool_t *pool = h2_stream_detach_pool(stream);
h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error);
+ if (session->last_stream == stream) {
+ session->last_stream = NULL;
+ }
h2_stream_set_remove(session->streams, stream->id);
h2_stream_destroy(stream);
}
}
+int h2_session_push_enabled(h2_session *session)
+{
+ return nghttp2_session_get_remote_settings(session->ngh2,
+ NGHTTP2_SETTINGS_ENABLE_PUSH);
+}
+
+
apr_status_t h2_session_process(h2_session *session)
{
apr_status_t status = APR_SUCCESS;
struct apr_thread_cond_t;
struct h2_config;
struct h2_mplx;
+struct h2_push;
struct h2_response;
struct h2_session;
struct h2_stream;
h2_conn_io io; /* io on httpd conn filters */
struct h2_mplx *mplx; /* multiplexer for stream data */
+ struct h2_stream *last_stream; /* last stream worked with */
struct h2_stream_set *streams; /* streams handled by this session */
int max_stream_received; /* highest stream id created */
/* Get the h2_stream for the given stream idenrtifier. */
struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
+/**
+ * Create and register a new stream under the given id.
+ *
+ * @param session the session to register in
+ * @param stream_id the new stream identifier
+ * @return the new stream
+ */
+struct h2_stream *h2_session_open_stream(h2_session *session, int stream_id);
+
+/**
+ * Returns if client settings have push enabled.
+ * @param != 0 iff push is enabled in client settings
+ */
+int h2_session_push_enabled(h2_session *session);
+
/**
* Destroy the stream and release it everywhere. Reclaim all resources.
* @param session the session to which the stream belongs
apr_status_t h2_session_stream_destroy(h2_session *session,
struct h2_stream *stream);
+/**
+ * Submit a push promise on the stream and schedule the new steam for
+ * processing..
+ *
+ * @param session the session to work in
+ * @param stream the stream on which the push depends
+ * @param push the push to promise
+ * @return the new promised stream or NULL
+ */
+struct h2_stream *h2_session_push(h2_session *session, struct h2_push *push);
+
#endif /* defined(__mod_h2__h2_session__) */
#include "h2_private.h"
#include "h2_conn.h"
+#include "h2_h2.h"
#include "h2_mplx.h"
+#include "h2_push.h"
#include "h2_request.h"
#include "h2_response.h"
#include "h2_session.h"
#include "h2_util.h"
-static void set_state(h2_stream *stream, h2_stream_state_t state)
+static int state_transition[][7] = {
+ /* ID OP RL RR CI CO CL */
+/*ID*/{ 1, 0, 0, 0, 0, 0, 0 },
+/*OP*/{ 1, 1, 0, 0, 0, 0, 0 },
+/*RL*/{ 0, 0, 1, 0, 0, 0, 0 },
+/*RR*/{ 0, 0, 0, 1, 0, 0, 0 },
+/*CI*/{ 1, 1, 0, 0, 1, 0, 0 },
+/*CO*/{ 1, 1, 0, 0, 0, 1, 0 },
+/*CL*/{ 1, 1, 0, 0, 1, 1, 1 },
+};
+
+static int set_state(h2_stream *stream, h2_stream_state_t state)
{
- AP_DEBUG_ASSERT(stream);
- if (stream->state != state) {
+ int allowed = state_transition[state][stream->state];
+ if (allowed) {
stream->state = state;
+ return 1;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
+ "h2_stream(%ld-%d): invalid state transition from %d to %d",
+ stream->session->id, stream->id, stream->state, state);
+ return 0;
+}
+
+static int close_input(h2_stream *stream)
+{
+ switch (stream->state) {
+ case H2_STREAM_ST_CLOSED_INPUT:
+ case H2_STREAM_ST_CLOSED:
+ return 0; /* ignore, idempotent */
+ case H2_STREAM_ST_CLOSED_OUTPUT:
+ /* both closed now */
+ set_state(stream, H2_STREAM_ST_CLOSED);
+ break;
+ default:
+ /* everything else we jump to here */
+ set_state(stream, H2_STREAM_ST_CLOSED_INPUT);
+ break;
+ }
+ return 1;
+}
+
+static int input_closed(h2_stream *stream)
+{
+ switch (stream->state) {
+ case H2_STREAM_ST_OPEN:
+ case H2_STREAM_ST_CLOSED_OUTPUT:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static int close_output(h2_stream *stream)
+{
+ switch (stream->state) {
+ case H2_STREAM_ST_CLOSED_OUTPUT:
+ case H2_STREAM_ST_CLOSED:
+ return 0; /* ignore, idempotent */
+ case H2_STREAM_ST_CLOSED_INPUT:
+ /* both closed now */
+ set_state(stream, H2_STREAM_ST_CLOSED);
+ break;
+ default:
+ /* everything else we jump to here */
+ set_state(stream, H2_STREAM_ST_CLOSED_OUTPUT);
+ break;
+ }
+ return 1;
+}
+
+static int input_open(h2_stream *stream)
+{
+ switch (stream->state) {
+ case H2_STREAM_ST_OPEN:
+ case H2_STREAM_ST_CLOSED_OUTPUT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int output_open(h2_stream *stream)
+{
+ switch (stream->state) {
+ case H2_STREAM_ST_OPEN:
+ case H2_STREAM_ST_CLOSED_INPUT:
+ return 1;
+ default:
+ return 0;
}
}
h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session)
{
h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
- if (stream != NULL) {
- stream->id = id;
- stream->state = H2_STREAM_ST_IDLE;
- stream->pool = pool;
- stream->session = session;
- stream->bbout = apr_brigade_create(stream->pool,
+ stream->id = id;
+ stream->state = H2_STREAM_ST_IDLE;
+ stream->pool = pool;
+ stream->session = session;
+ return stream;
+}
+
+h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session)
+{
+ h2_stream *stream = h2_stream_create(id, pool, session);
+ set_state(stream, H2_STREAM_ST_OPEN);
+ stream->request = h2_request_create(id, pool);
+ stream->bbout = apr_brigade_create(stream->pool,
stream->session->c->bucket_alloc);
- stream->request = h2_request_create(id, pool, session->c->bucket_alloc);
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- "h2_stream(%ld-%d): created", session->id, stream->id);
- }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): opened", session->id, stream->id);
return stream;
}
void h2_stream_rst(h2_stream *stream, int error_code)
{
stream->rst_error = error_code;
+ close_input(stream);
+ close_output(stream);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
"h2_stream(%ld-%d): reset, error=%d",
stream->session->id, stream->id, error_code);
apr_bucket_brigade *bb)
{
apr_status_t status = APR_SUCCESS;
+ if (!output_open(stream)) {
+ return APR_ECONNRESET;
+ }
stream->response = response;
if (bb && !APR_BRIGADE_EMPTY(bb)) {
int eos = 0;
h2_util_bb_avail(stream->bbout, &len, &eos);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
- "h2_stream(%ld-%d): set_response(%s), len=%ld, eos=%d",
- stream->session->id, stream->id, response->status,
+ "h2_stream(%ld-%d): set_response(%d), len=%ld, eos=%d",
+ stream->session->id, stream->id, response->http_status,
(long)len, (int)eos);
}
return status;
}
-static int set_closed(h2_stream *stream)
-{
- switch (stream->state) {
- case H2_STREAM_ST_CLOSED_INPUT:
- case H2_STREAM_ST_CLOSED:
- return 0; /* ignore, idempotent */
- case H2_STREAM_ST_CLOSED_OUTPUT:
- /* both closed now */
- set_state(stream, H2_STREAM_ST_CLOSED);
- break;
- default:
- /* everything else we jump to here */
- set_state(stream, H2_STREAM_ST_CLOSED_INPUT);
- break;
- }
- return 1;
-}
-
-apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r)
+apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r)
{
apr_status_t status;
AP_DEBUG_ASSERT(stream);
return APR_ECONNRESET;
}
set_state(stream, H2_STREAM_ST_OPEN);
- status = h2_request_rwrite(stream->request, r, stream->session->mplx);
+ status = h2_request_rwrite(stream->request, r);
return status;
}
+void h2_stream_set_h2_request(h2_stream *stream, const h2_request *req)
+{
+ h2_request_copy(stream->pool, stream->request, req);
+}
+
+apr_status_t h2_stream_add_header(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
+{
+ AP_DEBUG_ASSERT(stream);
+ if (!input_open(stream)) {
+ return APR_ECONNRESET;
+ }
+ return h2_request_add_header(stream->request, stream->pool,
+ name, nlen, value, vlen);
+}
+
apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
h2_stream_pri_cmp *cmp, void *ctx)
{
apr_status_t status;
AP_DEBUG_ASSERT(stream);
- if (stream->rst_error) {
+ if (!output_open(stream)) {
return APR_ECONNRESET;
}
+ if (eos) {
+ close_input(stream);
+ }
+
/* Seeing the end-of-headers, we have everything we need to
* start processing it.
*/
- status = h2_mplx_process(stream->session->mplx, stream->id,
- stream->request, eos, cmp, ctx);
- if (eos) {
- set_closed(stream);
+ status = h2_request_end_headers(stream->request, stream->pool, eos);
+ if (status == APR_SUCCESS) {
+ if (!eos) {
+ stream->bbin = apr_brigade_create(stream->pool,
+ stream->session->c->bucket_alloc);
+ }
+ stream->input_remaining = stream->request->content_length;
+
+ status = h2_mplx_process(stream->session->mplx, stream->id,
+ stream->request, eos, cmp, ctx);
+ }
+ else {
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
}
+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
"h2_mplx(%ld-%d): start stream, task %s %s (%s)",
stream->session->id, stream->id,
return status;
}
-apr_status_t h2_stream_write_eos(h2_stream *stream)
+static apr_status_t h2_stream_input_flush(h2_stream *stream)
{
- AP_DEBUG_ASSERT(stream);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
- "h2_stream(%ld-%d): closing input",
- stream->session->id, stream->id);
- if (stream->rst_error) {
- return APR_ECONNRESET;
+ apr_status_t status = APR_SUCCESS;
+ if (stream->bbin && !APR_BRIGADE_EMPTY(stream->bbin)) {
+
+ status = h2_mplx_in_write(stream->session->mplx, stream->id, stream->bbin);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->mplx->c,
+ "h2_stream(%ld-%d): flushing input data",
+ stream->session->mplx->id, stream->id);
+ }
}
- if (set_closed(stream)) {
- return h2_request_close(stream->request);
+ return status;
+}
+
+static apr_status_t input_flush(apr_bucket_brigade *bb, void *ctx)
+{
+ (void)bb;
+ return h2_stream_input_flush(ctx);
+}
+
+static apr_status_t input_add_data(h2_stream *stream,
+ const char *data, size_t len)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ status = apr_brigade_write(stream->bbin, input_flush, stream, data, len);
+ if (status == APR_SUCCESS) {
+ status = h2_stream_input_flush(stream);
}
- return APR_SUCCESS;
+ return status;
}
-apr_status_t h2_stream_write_header(h2_stream *stream,
- const char *name, size_t nlen,
- const char *value, size_t vlen)
+
+apr_status_t h2_stream_close_input(h2_stream *stream)
{
+ apr_status_t status = APR_SUCCESS;
+
AP_DEBUG_ASSERT(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
+ "h2_stream(%ld-%d): closing input",
+ stream->session->id, stream->id);
+
if (stream->rst_error) {
return APR_ECONNRESET;
}
- switch (stream->state) {
- case H2_STREAM_ST_IDLE:
- set_state(stream, H2_STREAM_ST_OPEN);
- break;
- case H2_STREAM_ST_OPEN:
- break;
- default:
- return APR_EINVAL;
+ if (close_input(stream) && stream->bbin) {
+ if (stream->request->chunked) {
+ status = input_add_data(stream, "0\r\n\r\n", 5);
+ }
+
+ if (status == APR_SUCCESS) {
+ status = h2_stream_input_flush(stream);
+ }
+ if (status == APR_SUCCESS) {
+ status = h2_mplx_in_close(stream->session->mplx, stream->id);
+ }
}
- return h2_request_write_header(stream->request, name, nlen,
- value, vlen, stream->session->mplx);
+ return status;
}
apr_status_t h2_stream_write_data(h2_stream *stream,
const char *data, size_t len)
{
+ apr_status_t status;
+
AP_DEBUG_ASSERT(stream);
- if (stream->rst_error) {
- return APR_ECONNRESET;
+ if (input_closed(stream) || !stream->request->eoh || !stream->bbin) {
+ return APR_EINVAL;
}
- switch (stream->state) {
- case H2_STREAM_ST_OPEN:
- break;
- default:
- return APR_EINVAL;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
+ "h2_stream(%ld-%d): add %ld input bytes",
+ stream->session->id, stream->id, (long)len);
+
+ if (stream->request->chunked) {
+ /* if input may have a body and we have not seen any
+ * content-length header, we need to chunk the input data.
+ */
+ status = apr_brigade_printf(stream->bbin, NULL, NULL,
+ "%lx\r\n", (unsigned long)len);
+ if (status == APR_SUCCESS) {
+ status = input_add_data(stream, data, len);
+ if (status == APR_SUCCESS) {
+ status = apr_brigade_puts(stream->bbin, NULL, NULL, "\r\n");
+ }
+ }
+ return status;
+ }
+ else {
+ stream->input_remaining -= len;
+ if (stream->input_remaining < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
+ APLOGNO(02961)
+ "h2_stream(%ld-%d): got %ld more content bytes than announced "
+ "in content-length header: %ld",
+ stream->session->id, stream->id,
+ (long)stream->request->content_length,
+ -(long)stream->input_remaining);
+ h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
+ return APR_ECONNABORTED;
+ }
+ return input_add_data(stream, data, len);
}
- return h2_request_write_data(stream->request, data, len);
}
apr_status_t h2_stream_prep_read(h2_stream *stream,
int h2_stream_input_is_open(h2_stream *stream)
{
- switch (stream->state) {
- case H2_STREAM_ST_OPEN:
- case H2_STREAM_ST_CLOSED_OUTPUT:
- return 1;
- default:
- return 0;
- }
+ return input_open(stream);
}
int h2_stream_needs_submit(h2_stream *stream)
}
}
+apr_status_t h2_stream_submit_pushes(h2_stream *stream)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_array_header_t *pushes;
+ int i;
+
+ pushes = h2_push_collect(stream->pool, stream->request, stream->response);
+ if (pushes && !apr_is_empty_array(pushes)) {
+ for (i = 0; i < pushes->nelts; ++i) {
+ h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*);
+ h2_stream *s = h2_session_push(stream->session, push);
+ if (!s) {
+ status = APR_ECONNRESET;
+ break;
+ }
+ }
+ }
+ return status;
+}
/**
* A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms.
*
- * Ok, not quite, but close enough, since we do not implement server
- * pushes yet.
- *
* A stream always belongs to a h2_session, the one managing the
* connection to the client. The h2_session writes to the h2_stream,
* adding HEADERS and DATA and finally an EOS. When headers are done,
- * h2_stream can create a h2_task that can be scheduled to fullfill the
- * request.
+ * h2_stream is scheduled for handling, which is expected to produce
+ * a h2_response.
*
- * This response headers are added directly to the h2_mplx of the session,
- * but the response DATA can be read via h2_stream. Reading data will
- * never block but return APR_EAGAIN when there currently is no data (and
- * no eos) in the multiplexer for this stream.
+ * The h2_response gives the HEADER frames to sent to the client, followed
+ * by DATA frames read from the h2_stream until EOS is reached.
*/
#include "h2_io.h"
struct h2_stream {
int id; /* http2 stream id */
+ int promised_on; /* http2 stream this was a promise on, or 0 */
h2_stream_state_t state; /* http/2 state of this stream */
struct h2_session *session; /* the session this stream belongs to */
+ apr_pool_t *pool; /* the memory pool for this stream */
+ struct h2_request *request; /* the request made in this stream */
+ struct h2_response *response; /* the response, once ready */
+
int aborted; /* was aborted */
int suspended; /* DATA sending has been suspended */
int rst_error; /* stream error for RST_STREAM */
+ int req_eoh; /* request HEADERs have been received */
int submitted; /* response HEADER has been sent */
- apr_pool_t *pool; /* the memory pool for this stream */
- struct h2_request *request; /* the request made in this stream */
-
- struct h2_response *response; /* the response, once ready */
+ apr_off_t input_remaining; /* remaining bytes on input as advertised via content-length */
+ apr_bucket_brigade *bbin; /* input DATA */
apr_bucket_brigade *bbout; /* output DATA */
apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */
#define H2_STREAM_RST(s, def) (s->rst_error? s->rst_error : (def))
+/**
+ * Create a stream in IDLE state.
+ * @param id the stream identifier
+ * @param pool the memory pool to use for this stream
+ * @param session the session this stream belongs to
+ * @return the newly created IDLE stream
+ */
h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_session *session);
+/**
+ * Create a stream in OPEN state.
+ * @param id the stream identifier
+ * @param pool the memory pool to use for this stream
+ * @param session the session this stream belongs to
+ * @return the newly opened stream
+ */
+h2_stream *h2_stream_open(int id, apr_pool_t *pool, struct h2_session *session);
+
+/**
+ * Destroy any resources held by this stream. Will destroy memory pool
+ * if still owned by the stream.
+ *
+ * @param stream the stream to destroy
+ */
apr_status_t h2_stream_destroy(h2_stream *stream);
+/**
+ * Removes stream from h2_session and destroys it.
+ *
+ * @param stream the stream to cleanup
+ */
void h2_stream_cleanup(h2_stream *stream);
-void h2_stream_rst(h2_stream *streamm, int error_code);
-
+/**
+ * Detach the memory pool from the stream. Will prevent stream
+ * destruction to take the pool with it.
+ *
+ * @param stream the stream to detach the pool from
+ * @param the detached memmory pool or NULL if stream no longer has one
+ */
apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
-apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r);
-apr_status_t h2_stream_write_eos(h2_stream *stream);
+/**
+ * Initialize stream->request with the given request_rec.
+ *
+ * @param stream stream to write request to
+ * @param r the request with all the meta data
+ */
+apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r);
+
+/**
+ * Initialize stream->request with the given h2_request.
+ *
+ * @param stream the stream to init the request for
+ * @param req the request for initializing, will be copied
+ */
+void h2_stream_set_h2_request(h2_stream *stream, const struct h2_request *req);
-apr_status_t h2_stream_write_header(h2_stream *stream,
- const char *name, size_t nlen,
- const char *value, size_t vlen);
+/*
+ * Add a HTTP/2 header (including pseudo headers) to the given stream.
+ *
+ * @param stream stream to write the header to
+ * @param name the name of the HTTP/2 header
+ * @param nlen the number of characters in name
+ * @param value the header value
+ * @param vlen the number of characters in value
+ */
+apr_status_t h2_stream_add_header(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen);
-apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
- h2_stream_pri_cmp *cmp, void *ctx);
+/**
+ * Closes the stream's input.
+ *
+ * @param stream stream to close intput of
+ */
+apr_status_t h2_stream_close_input(h2_stream *stream);
+/*
+ * Write a chunk of DATA to the stream.
+ *
+ * @param stream stream to write the data to
+ * @param data the beginning of the bytes to write
+ * @param len the number of bytes to write
+ */
apr_status_t h2_stream_write_data(h2_stream *stream,
const char *data, size_t len);
+/**
+ * Reset the stream. Stream write/reads will return errors afterwards.
+ *
+ * @param stream the stream to reset
+ * @param error_code the HTTP/2 error code
+ */
+void h2_stream_rst(h2_stream *streamm, int error_code);
+
+/**
+ * Schedule the stream for execution. All header information must be
+ * present. Use the given priority comparision callback to determine
+ * order in queued streams.
+ *
+ * @param stream the stream to schedule
+ * @param eos != 0 iff no more input will arrive
+ * @param cmp priority comparision
+ * @param ctx context for comparision
+ */
+apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
+ h2_stream_pri_cmp *cmp, void *ctx);
+
+/**
+ * Set the response for this stream. Invoked when all meta data for
+ * the stream response has been collected.
+ *
+ * @param stream the stream to set the response for
+ * @param resonse the response data for the stream
+ * @param bb bucket brigade with output data for the stream. Optional,
+ * may be incomplete.
+ */
apr_status_t h2_stream_set_response(h2_stream *stream,
struct h2_response *response,
apr_bucket_brigade *bb);
+/**
+ * Do a speculative read on the stream output to determine the
+ * amount of data that can be read.
+ *
+ * @param stream the stream to speculatively read from
+ * @param plen (in-/out) number of bytes requested and on return amount of bytes that
+ * may be read without blocking
+ * @param peos (out) != 0 iff end of stream will be reached when reading plen
+ * bytes (out value).
+ * @return APR_SUCCESS if out information was computed successfully.
+ * APR_EAGAIN if not data is available and end of stream has not been
+ * reached yet.
+ */
apr_status_t h2_stream_prep_read(h2_stream *stream,
apr_off_t *plen, int *peos);
+/**
+ * Read data from the stream output.
+ *
+ * @param stream the stream to read from
+ * @param cb callback to invoke for byte chunks read. Might be invoked
+ * multiple times (with different values) for one read operation.
+ * @param ctx context data for callback
+ * @param plen (in-/out) max. number of bytes to read and on return actual
+ * number of bytes read
+ * @param peos (out) != 0 iff end of stream has been reached while reading
+ * @return APR_SUCCESS if out information was computed successfully.
+ * APR_EAGAIN if not data is available and end of stream has not been
+ * reached yet.
+ */
apr_status_t h2_stream_readx(h2_stream *stream, h2_io_data_cb *cb,
void *ctx, apr_off_t *plen, int *peos);
+/**
+ * Read a maximum number of bytes into the bucket brigade.
+ *
+ * @param stream the stream to read from
+ * @param bb the brigade to append output to
+ * @param plen (in-/out) max. number of bytes to append and on return actual
+ * number of bytes appended to brigade
+ * @param peos (out) != 0 iff end of stream has been reached while reading
+ * @return APR_SUCCESS if out information was computed successfully.
+ * APR_EAGAIN if not data is available and end of stream has not been
+ * reached yet.
+ */
apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
apr_off_t *plen, int *peos);
-
+/**
+ * Set the suspended state of the stream.
+ * @param stream the stream to change state on
+ * @param suspended boolean value if stream is suspended
+ */
void h2_stream_set_suspended(h2_stream *stream, int suspended);
+
+/**
+ * Check if the stream has been suspended.
+ * @param stream the stream to check
+ * @return != 0 iff stream is suspended.
+ */
int h2_stream_is_suspended(h2_stream *stream);
+
+/**
+ * Check if the stream has open input.
+ * @param stream the stream to check
+ * @return != 0 iff stream has open input.
+ */
int h2_stream_input_is_open(h2_stream *stream);
+
+/**
+ * Check if the stream has not submitted a response or RST yet.
+ * @param stream the stream to check
+ * @return != 0 iff stream has not submitted a response or RST.
+ */
int h2_stream_needs_submit(h2_stream *stream);
+/**
+ * Submit any server push promises on this stream and schedule
+ * the tasks connection with these.
+ *
+ * @param stream the stream for which to submit
+ */
+apr_status_t h2_stream_submit_pushes(h2_stream *stream);
+
#endif /* defined(__mod_h2__h2_stream__) */
static unsigned int stream_hash(const char *key, apr_ssize_t *klen)
{
- /* we use the "int stream_id" has key, which always odd from
- * client and even from server. As long as we do not mix them
- * in one set, snip off the lsb. */
- return (unsigned int)(*((int*)key)) >> 1;
+ return (unsigned int)(*((int*)key));
}
h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max)
#include "h2_from_h1.h"
#include "h2_h2.h"
#include "h2_mplx.h"
+#include "h2_request.h"
#include "h2_session.h"
#include "h2_stream.h"
#include "h2_task_input.h"
}
-h2_task *h2_task_create(long session_id,
- int stream_id,
- apr_pool_t *stream_pool,
- h2_mplx *mplx, conn_rec *c)
+h2_task *h2_task_create(long session_id, const h2_request *req,
+ apr_pool_t *pool, h2_mplx *mplx,
+ conn_rec *c, int eos)
{
- h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task));
+ h2_task *task = apr_pcalloc(pool, sizeof(h2_task));
if (task == NULL) {
- ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool,
+ ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool,
APLOGNO(02941) "h2_task(%ld-%d): create stream task",
- session_id, stream_id);
- h2_mplx_out_close(mplx, stream_id);
+ session_id, req->id);
+ h2_mplx_out_close(mplx, req->id);
return NULL;
}
- task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id);
- task->stream_id = stream_id;
+ task->id = apr_psprintf(pool, "%ld-%d", session_id, req->id);
+ task->stream_id = req->id;
task->mplx = mplx;
-
task->c = c;
+
+ task->request = req;
+ task->input_eos = eos;
- ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, stream_pool,
- "h2_task(%s): created", task->id);
return task;
}
-void h2_task_set_request(h2_task *task,
- const char *method,
- const char *scheme,
- const char *authority,
- const char *path,
- apr_table_t *headers, int eos)
-{
- task->method = method;
- task->scheme = scheme;
- task->authority = authority;
- task->path = path;
- task->headers = headers;
- task->input_eos = eos;
-}
-
apr_status_t h2_task_destroy(h2_task *task)
{
(void)task;
if (status == APR_SUCCESS) {
task->input = h2_task_input_create(task, task->pool,
task->c->bucket_alloc);
- task->output = h2_task_output_create(task, task->pool,
- task->c->bucket_alloc);
+ task->output = h2_task_output_create(task, task->pool);
ap_process_connection(task->c, h2_worker_get_socket(worker));
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
"h2_task(%s): processing done", task->id);
r->allowed_methods = ap_make_method_list(p, 2);
- r->headers_in = apr_table_copy(r->pool, task->headers);
+ r->headers_in = apr_table_copy(r->pool, task->request->headers);
r->trailers_in = apr_table_make(r->pool, 5);
r->subprocess_env = apr_table_make(r->pool, 25);
r->headers_out = apr_table_make(r->pool, 12);
/* Time to populate r with the data we have. */
r->request_time = apr_time_now();
- r->method = task->method;
+ r->method = task->request->method;
/* Provide quick information about the request method as soon as known */
r->method_number = ap_method_number_of(r->method);
if (r->method_number == M_GET && r->method[0] == 'H') {
r->header_only = 1;
}
- ap_parse_uri(r, task->path);
+ ap_parse_uri(r, task->request->path);
r->protocol = (char*)"HTTP/2";
r->proto_num = HTTP_VERSION(2, 0);
r->the_request = apr_psprintf(r->pool, "%s %s %s",
- r->method, task->path, r->protocol);
+ r->method, task->request->path, r->protocol);
/* update what we think the virtual host is based on the headers we've
* now read. may update status.
struct h2_conn;
struct h2_mplx;
struct h2_task;
+struct h2_request;
struct h2_resp_head;
struct h2_worker;
int stream_id;
struct h2_mplx *mplx;
- const char *method;
- const char *scheme;
- const char *authority;
- const char *path;
- apr_table_t *headers;
+ const struct h2_request *request;
int input_eos;
int serialize_headers;
struct apr_thread_cond_t *io; /* used to wait for events on */
};
-h2_task *h2_task_create(long session_id, int stream_id,
+h2_task *h2_task_create(long session_id, const struct h2_request *req,
apr_pool_t *pool, struct h2_mplx *mplx,
- conn_rec *c);
+ conn_rec *c, int eos);
apr_status_t h2_task_destroy(h2_task *task);
-void h2_task_set_request(h2_task *task,
- const char *method,
- const char *scheme,
- const char *authority,
- const char *path,
- apr_table_t *headers, int eos);
-
-
apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker);
void h2_task_register_hooks(void);
#include "h2_private.h"
#include "h2_conn.h"
#include "h2_mplx.h"
+#include "h2_request.h"
#include "h2_session.h"
#include "h2_stream.h"
#include "h2_task_input.h"
if (task->serialize_headers) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
"h2_task_input(%s): serialize request %s %s",
- task->id, task->method, task->path);
+ task->id, task->request->method, task->request->path);
input->bb = apr_brigade_create(pool, bucket_alloc);
apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n",
- task->method, task->path);
- apr_table_do(ser_header, input, task->headers, NULL);
+ task->request->method, task->request->path);
+ apr_table_do(ser_header, input, task->request->headers, NULL);
apr_brigade_puts(input->bb, NULL, NULL, "\r\n");
if (input->task->input_eos) {
APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc));
#include "h2_private.h"
#include "h2_conn.h"
#include "h2_mplx.h"
+#include "h2_request.h"
#include "h2_session.h"
#include "h2_stream.h"
#include "h2_from_h1.h"
#include "h2_util.h"
-h2_task_output *h2_task_output_create(h2_task *task, apr_pool_t *pool,
- apr_bucket_alloc_t *bucket_alloc)
+h2_task_output *h2_task_output_create(h2_task *task, apr_pool_t *pool)
{
h2_task_output *output = apr_pcalloc(pool, sizeof(h2_task_output));
- (void)bucket_alloc;
if (output) {
output->task = task;
output->state = H2_TASK_OUT_INIT;
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
"h2_task_output(%s): write without response "
"for %s %s %s",
- output->task->id, output->task->method,
- output->task->authority, output->task->path);
+ output->task->id, output->task->request->method,
+ output->task->request->authority,
+ output->task->request->path);
f->c->aborted = 1;
}
if (output->task->io) {
struct h2_from_h1 *from_h1;
};
-h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool,
- apr_bucket_alloc_t *bucket_alloc);
+h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool);
void h2_task_output_destroy(h2_task_output *output);
+++ /dev/null
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * 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
-
- * 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 <stdio.h>
-
-#include <apr_strings.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_log.h>
-#include <http_connection.h>
-
-#include "h2_private.h"
-#include "h2_mplx.h"
-#include "h2_response.h"
-#include "h2_task.h"
-#include "h2_to_h1.h"
-#include "h2_util.h"
-
-
-h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool,
- apr_bucket_alloc_t *bucket_alloc,
- const char *method,
- const char *scheme,
- const char *authority,
- const char *path,
- struct h2_mplx *m)
-{
- h2_to_h1 *to_h1;
- if (!method) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
- APLOGNO(02943)
- "h2_to_h1: header start but :method missing");
- return NULL;
- }
- if (!path) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
- APLOGNO(02944)
- "h2_to_h1: header start but :path missing");
- return NULL;
- }
-
- to_h1 = apr_pcalloc(pool, sizeof(h2_to_h1));
- if (to_h1) {
- to_h1->stream_id = stream_id;
- to_h1->pool = pool;
- to_h1->method = method;
- to_h1->scheme = scheme;
- to_h1->authority = authority;
- to_h1->path = path;
- to_h1->m = m;
- to_h1->headers = apr_table_make(to_h1->pool, 10);
- to_h1->bb = apr_brigade_create(pool, bucket_alloc);
- to_h1->chunked = 0; /* until we see a content-type and no length */
- to_h1->content_len = -1;
- }
- return to_h1;
-}
-
-void h2_to_h1_destroy(h2_to_h1 *to_h1)
-{
- to_h1->bb = NULL;
-}
-
-apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
- const char *name, size_t nlen,
- const char *value, size_t vlen)
-{
- char *hname, *hvalue;
- if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) {
- if (!apr_strnatcasecmp("chunked", value)) {
- /* This should never arrive here in a HTTP/2 request */
- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c,
- APLOGNO(02945)
- "h2_to_h1: 'transfer-encoding: chunked' received");
- return APR_BADARG;
- }
- }
- else if (H2_HD_MATCH_LIT("content-length", name, nlen)) {
- char *end;
- to_h1->content_len = apr_strtoi64(value, &end, 10);
- if (value == end) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
- APLOGNO(02959)
- "h2_request(%d): content-length value not parsed: %s",
- to_h1->stream_id, value);
- return APR_EINVAL;
- }
- to_h1->remain_len = to_h1->content_len;
- to_h1->chunked = 0;
- }
- else if (H2_HD_MATCH_LIT("content-type", name, nlen)) {
- /* If we see a content-type and have no length (yet),
- * we need to chunk. */
- to_h1->chunked = (to_h1->content_len == -1);
- }
- else if ((to_h1->seen_host && H2_HD_MATCH_LIT("host", name, nlen))
- || H2_HD_MATCH_LIT("expect", name, nlen)
- || H2_HD_MATCH_LIT("upgrade", name, nlen)
- || H2_HD_MATCH_LIT("connection", name, nlen)
- || H2_HD_MATCH_LIT("proxy-connection", name, nlen)
- || H2_HD_MATCH_LIT("keep-alive", name, nlen)
- || H2_HD_MATCH_LIT("http2-settings", name, nlen)) {
- /* ignore these. */
- return APR_SUCCESS;
- }
- else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
- const char *existing = apr_table_get(to_h1->headers, "cookie");
- if (existing) {
- char *nval;
-
- /* Cookie headers come separately in HTTP/2, but need
- * to be merged by "; " (instead of default ", ")
- */
- hvalue = apr_pstrndup(to_h1->pool, value, vlen);
- nval = apr_psprintf(to_h1->pool, "%s; %s", existing, hvalue);
- apr_table_setn(to_h1->headers, "Cookie", nval);
- return APR_SUCCESS;
- }
- }
- else if (H2_HD_MATCH_LIT("host", name, nlen)) {
- to_h1->seen_host = 1;
- }
-
- hname = apr_pstrndup(to_h1->pool, name, nlen);
- hvalue = apr_pstrndup(to_h1->pool, value, vlen);
- h2_util_camel_case_header(hname, nlen);
- apr_table_mergen(to_h1->headers, hname, hvalue);
-
- return APR_SUCCESS;
-}
-
-static int set_header(void *ctx, const char *key, const char *value)
-{
- h2_to_h1 *to_h1 = (h2_to_h1*)ctx;
- h2_to_h1_add_header(to_h1, key, strlen(key), value, strlen(value));
- return 1;
-}
-
-apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers)
-{
- apr_table_do(set_header, to_h1, headers, NULL);
- return APR_SUCCESS;
-}
-
-apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos)
-{
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
- "h2_to_h1(%ld-%d): end headers",
- to_h1->m->id, to_h1->stream_id);
-
- if (to_h1->eoh) {
- return APR_EINVAL;
- }
-
- if (!to_h1->seen_host) {
- /* Need to add a "Host" header if not already there to
- * make virtual hosts work correctly. */
- if (!to_h1->authority) {
- return APR_BADARG;
- }
- apr_table_set(to_h1->headers, "Host", to_h1->authority);
- }
-
- if (eos && to_h1->chunked) {
- /* We had chunking figured out, but the EOS is already there.
- * unmark chunking and set a definitive content-length.
- */
- to_h1->chunked = 0;
- apr_table_setn(to_h1->headers, "Content-Length", "0");
- }
- else if (to_h1->chunked) {
- /* We have not seen a content-length. We therefore must
- * pass any request content in chunked form.
- */
- apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
- }
-
- to_h1->eoh = 1;
-
- return APR_SUCCESS;
-}
-
-static apr_status_t flush(apr_bucket_brigade *bb, void *ctx)
-{
- (void)bb;
- return h2_to_h1_flush((h2_to_h1*)ctx);
-}
-
-static apr_status_t h2_to_h1_add_data_raw(h2_to_h1 *to_h1,
- const char *data, size_t len)
-{
- apr_status_t status = APR_SUCCESS;
-
- if (to_h1->eos || !to_h1->eoh) {
- return APR_EINVAL;
- }
-
- status = apr_brigade_write(to_h1->bb, flush, to_h1, data, len);
- if (status == APR_SUCCESS) {
- status = h2_to_h1_flush(to_h1);
- }
- return status;
-}
-
-
-apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
- const char *data, size_t len)
-{
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
- "h2_to_h1(%ld-%d): add %ld data bytes",
- to_h1->m->id, to_h1->stream_id, (long)len);
-
- if (to_h1->chunked) {
- /* if input may have a body and we have not seen any
- * content-length header, we need to chunk the input data.
- */
- apr_status_t status = apr_brigade_printf(to_h1->bb, NULL, NULL,
- "%lx\r\n", (unsigned long)len);
- if (status == APR_SUCCESS) {
- status = h2_to_h1_add_data_raw(to_h1, data, len);
- if (status == APR_SUCCESS) {
- status = apr_brigade_puts(to_h1->bb, NULL, NULL, "\r\n");
- }
- }
- return status;
- }
- else {
- to_h1->remain_len -= len;
- if (to_h1->remain_len < 0) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, to_h1->m->c,
- APLOGNO(02961)
- "h2_to_h1(%ld-%d): got %ld more content bytes than announced "
- "in content-length header: %ld",
- to_h1->m->id, to_h1->stream_id,
- (long)to_h1->content_len, -(long)to_h1->remain_len);
- }
- return h2_to_h1_add_data_raw(to_h1, data, len);
- }
-}
-
-apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1)
-{
- apr_status_t status = APR_SUCCESS;
- if (!APR_BRIGADE_EMPTY(to_h1->bb)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, to_h1->m->c,
- "h2_to_h1(%ld-%d): flush request bytes",
- to_h1->m->id, to_h1->stream_id);
-
- status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb);
- if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, to_h1->m->c,
- APLOGNO(02946) "h2_request(%d): pushing request data",
- to_h1->stream_id);
- }
- }
- return status;
-}
-
-apr_status_t h2_to_h1_close(h2_to_h1 *to_h1)
-{
- apr_status_t status = APR_SUCCESS;
- if (!to_h1->eos) {
- if (to_h1->chunked) {
- status = h2_to_h1_add_data_raw(to_h1, "0\r\n\r\n", 5);
- }
- to_h1->eos = 1;
- status = h2_to_h1_flush(to_h1);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
- "h2_to_h1(%d): close", to_h1->stream_id);
-
- status = h2_mplx_in_close(to_h1->m, to_h1->stream_id);
- }
- return status;
-}
-
-
+++ /dev/null
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * 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
-
- * 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.
- */
-
-#ifndef __mod_h2__h2_to_h1__
-#define __mod_h2__h2_to_h1__
-
-struct h2_mplx;
-struct h2_task;
-typedef struct h2_to_h1 h2_to_h1;
-
-struct h2_to_h1 {
- int stream_id;
- apr_pool_t *pool;
- h2_mplx *m;
-
- const char *method;
- const char *scheme;
- const char *authority;
- const char *path;
-
- int chunked;
- int eoh;
- int eos;
- int flushed;
- int seen_host;
-
- apr_off_t content_len;
- apr_off_t remain_len;
- apr_table_t *headers;
- apr_bucket_brigade *bb;
-};
-
-/* Create a converter from a HTTP/2 request to a serialzation in
- * HTTP/1.1 format. The serialized data will be written onto the
- * given h2_mplx instance.
- */
-h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool,
- apr_bucket_alloc_t *bucket_alloc,
- const char *method,
- const char *scheme,
- const char *authority,
- const char *path,
- struct h2_mplx *m);
-
-/* Destroy the converter and free resources. */
-void h2_to_h1_destroy(h2_to_h1 *to_h1);
-
-/* Add a header to the serialization. Only valid to call after start
- * and before end_headers.
- */
-apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
- const char *name, size_t nlen,
- const char *value, size_t vlen);
-
-apr_status_t h2_to_h1_add_headers(h2_to_h1 *to_h1, apr_table_t *headers);
-
-/** End the request headers.
- */
-apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos);
-
-/* Add request body data.
- */
-apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
- const char *data, size_t len);
-
-/* Flush the converted data onto the h2_mplx instance.
- */
-apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1);
-
-/* Close the request, flushed automatically.
- */
-apr_status_t h2_to_h1_close(h2_to_h1 *to_h1);
-
-#endif /* defined(__mod_h2__h2_to_h1__) */
* @macro
* Version number of the h2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.0.4-DEV"
+#define MOD_HTTP2_VERSION "1.0.5-DEV"
/**
* @macro
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x010004
+#define MOD_HTTP2_VERSION_NUM 0x010005
#endif /* mod_h2_h2_version_h */
# End Source File
# Begin Source File
+SOURCE=./h2_push.c
+# End Source File
+# Begin Source File
+
SOURCE=./h2_request.c
# End Source File
# Begin Source File
# End Source File
# Begin Source File
-SOURCE=./h2_to_h1.c
-# End Source File
-# Begin Source File
-
SOURCE=./h2_util.c
# End Source File
# Begin Source File