From: Stefan Eissing Date: Wed, 11 Nov 2015 16:40:18 +0000 (+0000) Subject: some rework for server push X-Git-Tag: 2.5.0-alpha~2650 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=62e2350bcb8c8e01ff296ccf0c86c2cf3ee7e25b;p=apache some rework for server push git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1713887 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/http2/config.m4 b/modules/http2/config.m4 index 35b25e11a3..f383b3cd83 100644 --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@ -31,6 +31,7 @@ h2_h2.lo dnl 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 @@ -41,7 +42,6 @@ h2_task.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 diff --git a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c index ed7fff4a27..43a4f0822b 100644 --- a/modules/http2/h2_from_h1.c +++ b/modules/http2/h2_from_h1.c @@ -59,11 +59,6 @@ apr_status_t h2_from_h1_destroy(h2_from_h1 *from_h1) 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) { @@ -79,15 +74,8 @@ h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1) 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; @@ -202,8 +190,7 @@ apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1, ap_filter_t* f, } 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') { diff --git a/modules/http2/h2_from_h1.h b/modules/http2/h2_from_h1.h index 9a0eba0cec..4f5ebad618 100644 --- a/modules/http2/h2_from_h1.h +++ b/modules/http2/h2_from_h1.h @@ -51,32 +51,22 @@ struct h2_from_h1 { 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__) */ diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h index 112fce9a22..7cf06ea940 100644 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@ -52,6 +52,10 @@ extern const char *H2_MAGIC_TOKEN; /* 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 diff --git a/modules/http2/h2_io.c b/modules/http2/h2_io.c index 9aed64ee95..6bd9637151 100644 --- a/modules/http2/h2_io.c +++ b/modules/http2/h2_io.c @@ -40,10 +40,6 @@ h2_io *h2_io_create(int id, apr_pool_t *pool, apr_bucket_alloc_t *bucket_alloc) 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) diff --git a/modules/http2/h2_io.h b/modules/http2/h2_io.h index 2cfa2980e5..71cca986d0 100644 --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@ -31,22 +31,23 @@ typedef struct h2_io h2_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; }; diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 7d052c53ea..723ea268f2 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -584,8 +584,8 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response, 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); } @@ -678,7 +678,7 @@ apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id) * 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", @@ -861,7 +861,7 @@ static h2_io *open_io(h2_mplx *m, int stream_id) 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; @@ -878,18 +878,16 @@ apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, 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); diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h index e8ba965fa7..fc68b1addc 100644 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@ -157,7 +157,7 @@ apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout, * @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); /** diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c new file mode 100644 index 0000000000..cced862dbd --- /dev/null +++ b/modules/http2/h2_push.c @@ -0,0 +1,114 @@ +/* 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 +#include + +#include + +#include +#include +#include + +#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 + 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; +} diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h new file mode 100644 index 0000000000..7b586d7987 --- /dev/null +++ b/modules/http2/h2_push.h @@ -0,0 +1,34 @@ +/* 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__) */ diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 4a1e19058a..5d301415a8 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -24,63 +24,144 @@ #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; @@ -90,8 +171,8 @@ apr_status_t h2_request_write_header(h2_request *req, 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); @@ -100,25 +181,25 @@ apr_status_t h2_request_write_header(h2_request *req, 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); @@ -126,65 +207,59 @@ apr_status_t h2_request_write_header(h2_request *req, } 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; } diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h index aa5e0bc3c0..5386848eec 100644 --- a/modules/http2/h2_request.h +++ b/modules/http2/h2_request.h @@ -19,9 +19,6 @@ /* 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; @@ -30,38 +27,37 @@ struct h2_task; 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__) */ diff --git a/modules/http2/h2_response.c b/modules/http2/h2_response.c index bd0a5fba97..ab81b187ff 100644 --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@ -27,10 +27,11 @@ #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) { @@ -43,7 +44,7 @@ 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) { @@ -56,7 +57,7 @@ h2_response *h2_response_create(int stream_id, 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) { @@ -112,17 +113,17 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r, } 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; @@ -141,10 +142,10 @@ h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from) { 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; } @@ -156,6 +157,7 @@ typedef struct { 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) @@ -171,8 +173,8 @@ 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) @@ -205,28 +207,54 @@ static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_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); @@ -240,9 +268,25 @@ static h2_ngheader *make_ngheader(apr_pool_t *pool, const char *status, 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; +} + + diff --git a/modules/http2/h2_response.h b/modules/http2/h2_response.h index 64d68cc9c2..e6d47f4669 100644 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@ -16,6 +16,8 @@ #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. */ @@ -24,18 +26,21 @@ typedef struct h2_ngheader { 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); @@ -46,4 +51,20 @@ void h2_response_destroy(h2_response *response); 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__) */ diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 2e93a66cc5..0d11097859 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -29,6 +29,7 @@ #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" @@ -55,12 +56,12 @@ static int h2_session_status_from_apr_status(apr_status_t rv) 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) { @@ -71,11 +72,11 @@ static int stream_open(h2_session *session, int stream_id) 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; } @@ -83,7 +84,7 @@ static int stream_open(h2_session *session, int 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) @@ -356,16 +357,12 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id, 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, @@ -383,6 +380,7 @@ 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, @@ -392,9 +390,9 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, 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; } @@ -410,64 +408,45 @@ 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; + 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, @@ -475,15 +454,13 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, 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]; @@ -496,23 +473,10 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, 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); @@ -522,7 +486,6 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, if (nghttp2_is_fatal(rv)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } - return 0; } return 0; @@ -911,8 +874,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv) } /* 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", @@ -920,15 +883,7 @@ apr_status_t h2_session_start(h2_session *session, int *rv) 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; } @@ -1011,8 +966,10 @@ static void update_window(void *ctx, int stream_id, apr_off_t bytes_read) 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 @@ -1173,23 +1130,30 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) 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; @@ -1205,11 +1169,62 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) 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); @@ -1300,6 +1315,13 @@ static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen) } } +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; diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index b6a9beae5d..c110b8c214 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -41,6 +41,7 @@ struct apr_thread_mutext_t; struct apr_thread_cond_t; struct h2_config; struct h2_mplx; +struct h2_push; struct h2_response; struct h2_session; struct h2_stream; @@ -72,6 +73,7 @@ struct h2_session { 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 */ @@ -159,6 +161,21 @@ apr_status_t h2_session_handle_response(h2_session *session, /* 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 @@ -167,4 +184,15 @@ struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id); 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__) */ diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index 266a9e24fb..7ef28255fe 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -25,7 +25,9 @@ #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" @@ -37,29 +39,120 @@ #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; } @@ -96,6 +189,8 @@ apr_pool_t *h2_stream_detach_pool(h2_stream *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); @@ -105,6 +200,9 @@ apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response, 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)) { @@ -120,32 +218,14 @@ apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response, 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); @@ -153,27 +233,58 @@ apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r) 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, @@ -183,56 +294,110 @@ apr_status_t h2_stream_schedule(h2_stream *stream, int eos, 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, @@ -356,13 +521,7 @@ int h2_stream_is_suspended(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) @@ -378,3 +537,22 @@ 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; +} diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index a36733fa18..9484f64933 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -19,19 +19,14 @@ /** * 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" @@ -55,18 +50,22 @@ typedef struct h2_stream h2_stream; 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 */ @@ -75,47 +74,211 @@ struct h2_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__) */ diff --git a/modules/http2/h2_stream_set.c b/modules/http2/h2_stream_set.c index 17c75f0822..6b8832b472 100644 --- a/modules/http2/h2_stream_set.c +++ b/modules/http2/h2_stream_set.c @@ -32,10 +32,7 @@ struct h2_stream_set { 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) diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c index f0f024a0dc..6995ba6693 100644 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@ -38,6 +38,7 @@ #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" @@ -152,46 +153,30 @@ static int h2_task_process_conn(conn_rec* c) } -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; @@ -216,8 +201,7 @@ apr_status_t h2_task_do(h2_task *task, h2_worker *worker) 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); @@ -270,7 +254,7 @@ static request_rec *h2_task_create_request(h2_task *task) 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); @@ -309,19 +293,19 @@ static request_rec *h2_task_create_request(h2_task *task) /* 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. diff --git a/modules/http2/h2_task.h b/modules/http2/h2_task.h index 1877a3920b..d36a813081 100644 --- a/modules/http2/h2_task.h +++ b/modules/http2/h2_task.h @@ -39,6 +39,7 @@ struct apr_thread_cond_t; struct h2_conn; struct h2_mplx; struct h2_task; +struct h2_request; struct h2_resp_head; struct h2_worker; @@ -49,11 +50,7 @@ struct h2_task { 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; @@ -68,20 +65,12 @@ struct h2_task { 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); diff --git a/modules/http2/h2_task_input.c b/modules/http2/h2_task_input.c index 1eac749f42..86334ea622 100644 --- a/modules/http2/h2_task_input.c +++ b/modules/http2/h2_task_input.c @@ -23,6 +23,7 @@ #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" @@ -53,11 +54,11 @@ h2_task_input *h2_task_input_create(h2_task *task, apr_pool_t *pool, 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)); diff --git a/modules/http2/h2_task_output.c b/modules/http2/h2_task_output.c index 053e2d69e4..06a5d7aafb 100644 --- a/modules/http2/h2_task_output.c +++ b/modules/http2/h2_task_output.c @@ -24,6 +24,7 @@ #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" @@ -33,12 +34,10 @@ #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; @@ -73,8 +72,9 @@ static apr_status_t open_if_needed(h2_task_output *output, ap_filter_t *f, 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) { diff --git a/modules/http2/h2_task_output.h b/modules/http2/h2_task_output.h index 79cb6816c7..a326c49096 100644 --- a/modules/http2/h2_task_output.h +++ b/modules/http2/h2_task_output.h @@ -40,8 +40,7 @@ struct h2_task_output { 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); diff --git a/modules/http2/h2_to_h1.c b/modules/http2/h2_to_h1.c deleted file mode 100644 index 159fde31dd..0000000000 --- a/modules/http2/h2_to_h1.c +++ /dev/null @@ -1,290 +0,0 @@ -/* 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 -#include - -#include - -#include -#include -#include -#include - -#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; -} - - diff --git a/modules/http2/h2_to_h1.h b/modules/http2/h2_to_h1.h deleted file mode 100644 index 6fc06fbf87..0000000000 --- a/modules/http2/h2_to_h1.h +++ /dev/null @@ -1,86 +0,0 @@ -/* 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__) */ diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index daada0f92b..98a431b3bf 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -20,7 +20,7 @@ * @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 @@ -28,7 +28,7 @@ * 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 */ diff --git a/modules/http2/mod_http2.dsp b/modules/http2/mod_http2.dsp index fbff711df7..12a3abfe98 100644 --- a/modules/http2/mod_http2.dsp +++ b/modules/http2/mod_http2.dsp @@ -149,6 +149,10 @@ SOURCE=./h2_mplx.c # 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 @@ -189,10 +193,6 @@ SOURCE=./h2_task_queue.c # 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