From: Stefan Eissing Date: Mon, 11 Jan 2016 17:12:24 +0000 (+0000) Subject: new directive H2PushDiarySize, first simple apr_hash implementation of a push diary... X-Git-Tag: 2.5.0-alpha~2438 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=cb8084064d87ce33910650fce8f79b97e0538232;p=apache new directive H2PushDiarySize, first simple apr_hash implementation of a push diary that prevents duplicate pushes on a connection git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1724086 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index 43c64b9762..40ba99eb46 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -193,6 +193,43 @@ + + H2PushDiarySize + H2 Server Push Diary Size + H2PushDiarySize n + H2PushDiarySize 128 + + server config + virtual host + + Available in version 2.4.19 and later. + + +

+ This directive toggles the maximum number of HTTP/2 server pushes + that are remembered per HTTP/2 connection. This can be used inside the + VirtualHost + section to influence the number for all connections to that virtual host. +

+

+ The push diary records a digest (currently using SHA256) of pushed + resources (their URL) to avoid duplicate pushes on the same connection. + These value are not persisted, so clients openeing a new connection + will experience known pushes again. There is ongoing work to enable + a client to disclose a digest of the resources it already has, so + the diary maybe initialized by the client on each connection setup. +

+

+ If the maximum size is reached, newer entries replace the oldest + ones. Using SHA256, each diary entry uses 32 bytes, letting a + default diary with 128 entries consume around 4 KB of memory. +

+

+ A size of 0 will effectively disable the push diary. +

+
+
+ H2PushPriority H2 Server Push Priority diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index ecfe949dc9..ee1bf598c3 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -62,6 +62,8 @@ static h2_config defconf = { -1, /* connection timeout */ -1, /* keepalive timeout */ 0, /* stream timeout */ + 128, /* push diary size */ + }; void h2_config_init(apr_pool_t *pool) @@ -97,6 +99,7 @@ static void *h2_config_create(apr_pool_t *pool, conf->h2_timeout = DEF_VAL; conf->h2_keepalive = DEF_VAL; conf->h2_stream_timeout = DEF_VAL; + conf->push_diary_size = DEF_VAL; return conf; } @@ -145,6 +148,7 @@ void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) n->h2_timeout = H2_CONFIG_GET(add, base, h2_timeout); n->h2_keepalive = H2_CONFIG_GET(add, base, h2_keepalive); n->h2_stream_timeout = H2_CONFIG_GET(add, base, h2_stream_timeout); + n->push_diary_size = H2_CONFIG_GET(add, base, push_diary_size); return n; } @@ -193,6 +197,8 @@ apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var) return H2_CONFIG_GET(conf, &defconf, h2_keepalive); case H2_CONF_STREAM_TIMEOUT_SECS: return H2_CONFIG_GET(conf, &defconf, h2_stream_timeout); + case H2_CONF_PUSH_DIARY_SIZE: + return H2_CONFIG_GET(conf, &defconf, push_diary_size); default: return DEF_VAL; } @@ -526,6 +532,17 @@ static const char *h2_conf_set_stream_timeout(cmd_parms *parms, return NULL; } +static const char *h2_conf_set_push_diary_size(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = (h2_config *)h2_config_sget(parms->server); + (void)arg; + cfg->push_diary_size = (int)apr_atoi64(value); + if (cfg->push_diary_size < 0) { + return "value must be >= 0"; + } + return NULL; +} #define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) @@ -570,6 +587,8 @@ const command_rec h2_cmds[] = { RSRC_CONF, "timeout (seconds) for idle HTTP/2 connections, no streams open"), AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL, RSRC_CONF, "read/write timeout (seconds) for HTTP/2 streams"), + AP_INIT_TAKE1("H2PushDiarySize", h2_conf_set_push_diary_size, NULL, + RSRC_CONF, "size of push diary"), AP_END_CMD }; diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h index 57d926d5f7..a40bb83c02 100644 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@ -42,6 +42,7 @@ typedef enum { H2_CONF_TIMEOUT_SECS, H2_CONF_KEEPALIVE_SECS, H2_CONF_STREAM_TIMEOUT_SECS, + H2_CONF_PUSH_DIARY_SIZE, } h2_config_var_t; struct apr_hash_t; @@ -72,6 +73,7 @@ typedef struct h2_config { int h2_timeout; /* timeout for http/2 connections */ int h2_keepalive; /* timeout for idle connections, no streams */ int h2_stream_timeout; /* timeout for http/2 streams, slave connections */ + int push_diary_size; /* # of entries in push diary */ } h2_config; diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c index 1ba4ea6fb8..e29daecf9d 100644 --- a/modules/http2/h2_push.c +++ b/modules/http2/h2_push.c @@ -16,8 +16,10 @@ #include #include -#include #include +#include +#include +#include #include #include @@ -29,6 +31,8 @@ #include "h2_push.h" #include "h2_request.h" #include "h2_response.h" +#include "h2_session.h" +#include "h2_stream.h" static const char *policy_str(h2_push_policy policy) { @@ -451,3 +455,127 @@ void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_en } req->push_policy = policy; } + +static unsigned int val_apr_hash(const char *str) +{ + apr_ssize_t len = strlen(str); + return apr_hashfunc_default(str, &len); +} + +static void calc_apr_hash(h2_push_digest *d, h2_push *push) +{ + unsigned int val; + + val = val_apr_hash(push->req->scheme); + val ^= val_apr_hash(push->req->authority); + val ^= val_apr_hash(push->req->path); + + d->val.apr_hash = val; +} + +static int cmp_apr_hash(h2_push_digest *d1, h2_push_digest *d2) +{ + return d1->val.apr_hash - d2->val.apr_hash; +} + +h2_push_diary *h2_push_diary_create(apr_pool_t *p, apr_size_t max_entries) +{ + h2_push_diary *diary = NULL; + + if (max_entries > 0) { + diary = apr_pcalloc(p, sizeof(*diary)); + + diary->entries = apr_array_make(p, 10, sizeof(h2_push_diary_entry*)); + diary->max_entries = max_entries; + diary->dtype = H2_PUSH_DIGEST_APR_HASH; + diary->dcalc = calc_apr_hash; + diary->dcmp = cmp_apr_hash; + } + + return diary; +} + +static h2_push_diary_entry *h2_push_diary_find(h2_push_diary *diary, h2_push_digest *d) +{ + if (diary) { + h2_push_diary_entry *e; + int i; + + for (i = 0; i < diary->entries->nelts; ++i) { + e = APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry*); + if (!diary->dcmp(&e->digest, d)) { + return e; + } + } + } + return NULL; +} + +static h2_push_diary_entry *h2_push_diary_insert(h2_push_diary *diary, h2_push_digest *d) +{ + h2_push_diary_entry *e; + int i; + + if (diary->entries->nelts < diary->max_entries) { + e = apr_pcalloc(diary->entries->pool, sizeof(*e)); + APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry*) = e; + } + else { + h2_push_diary_entry *oldest = NULL; + for (i = 0; i < diary->entries->nelts; ++i) { + e = APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry*); + if (!oldest || oldest->last_accessed > e->last_accessed) { + oldest = e; + } + } + e = oldest; + } + + memcpy(&e->digest, d, sizeof(*d)); + e->last_accessed = apr_time_now(); + return e; +} + +apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t *pushes) +{ + apr_array_header_t *npushes = pushes; + h2_push_digest d; + h2_push_diary_entry *e; + int i; + + if (session->push_diary && pushes) { + npushes = NULL; + + for (i = 0; i < pushes->nelts; ++i) { + h2_push *push; + + push = APR_ARRAY_IDX(pushes, i, h2_push*); + session->push_diary->dcalc(&d, push); + e = h2_push_diary_find(session->push_diary, &d); + if (e) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, + "push_diary_update: already there PUSH %s", push->req->path); + e->last_accessed = apr_time_now(); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, + "push_diary_update: adding PUSH %s", push->req->path); + if (!npushes) { + npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*)); + } + APR_ARRAY_PUSH(npushes, h2_push*) = push; + h2_push_diary_insert(session->push_diary, &d); + } + } + } + return npushes; +} + +apr_array_header_t *h2_push_collect_update(h2_stream *stream, + const struct h2_request *req, + const struct h2_response *res) +{ + apr_array_header_t *pushes = h2_push_collect(stream->pool, req, res); + return h2_push_diary_update(stream->session, pushes); +} + diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h index f3a8601c34..5976e4c907 100644 --- a/modules/http2/h2_push.h +++ b/modules/http2/h2_push.h @@ -18,6 +18,8 @@ struct h2_request; struct h2_response; struct h2_ngheader; +struct h2_session; +struct h2_stream; typedef enum { H2_PUSH_NONE, @@ -30,11 +32,80 @@ typedef struct h2_push { const struct h2_request *req; } h2_push; +typedef enum { + H2_PUSH_DIGEST_APR_HASH, + H2_PUSH_DIGEST_SHA256 +} h2_push_digest_t; + +typedef struct h2_push_digest { + union { + unsigned int apr_hash; + unsigned char sha256[32]; + } val; +} h2_push_digest; + +typedef void h2_push_digest_calc(h2_push_digest *d, h2_push *push); +typedef int h2_push_digest_cmp(h2_push_digest *d1, h2_push_digest *d2); + +typedef struct h2_push_diary_entry { + h2_push_digest digest; + apr_time_t last_accessed; +} h2_push_diary_entry; + +typedef struct h2_push_diary { + apr_array_header_t *entries; + apr_size_t max_entries; + h2_push_digest_t dtype; + h2_push_digest_calc *dcalc; + h2_push_digest_cmp *dcmp; +} h2_push_diary; + +/** + * Determine the list of h2_push'es to send to the client on behalf of + * the given request/response pair. + * + * @param p the pool to use + * @param req the requst from the client + * @param res the response from the server + * @return array of h2_push addresses or NULL + */ apr_array_header_t *h2_push_collect(apr_pool_t *p, const struct h2_request *req, const struct h2_response *res); +/** + * Set the push policy for the given request. Takes request headers into + * account, see draft https://tools.ietf.org/html/draft-ruellan-http-accept-push-policy-00 + * for details. + * + * @param req the request to determine the policy for + * @param p the pool to use + * @param push_enabled if HTTP/2 server push is generally enabled for this request + */ void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled); +/** + * Create a new push diary for the given maximum number of entries. + * + * @oaram p the pool to use + * @param max_entries the maximum number of entries the diary should hold + * @return the created diary, might be NULL of max_entries is 0 + */ +h2_push_diary *h2_push_diary_create(apr_pool_t *p, apr_size_t max_entries); + +/** + * Filters the given pushes against the diary and returns only those pushes + * that were newly entered in the diary. + */ +apr_array_header_t *h2_push_diary_update(struct h2_session *session, apr_array_header_t *pushes); + +/** + * Collect pushes for the given request/response pair, enter them into the + * diary and return those pushes newly entered. + */ +apr_array_header_t *h2_push_collect_update(struct h2_stream *stream, + const struct h2_request *req, + const struct h2_response *res); + #endif /* defined(__mod_h2__h2_push__) */ diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 4f92120b3d..82dcee7166 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -781,6 +781,7 @@ static h2_session *h2_session_create_int(conn_rec *c, { nghttp2_session_callbacks *callbacks = NULL; nghttp2_option *options = NULL; + apr_size_t diary_size; apr_pool_t *pool = NULL; apr_status_t status = apr_pool_create(&pool, c->pool); @@ -882,13 +883,17 @@ static h2_session *h2_session_create_int(conn_rec *c, h2_session_destroy(session); return NULL; } - + + diary_size = h2_config_geti(session->config, H2_CONF_PUSH_DIARY_SIZE); + session->push_diary = h2_push_diary_create(session->pool, diary_size); + if (APLOGcdebug(c)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "session(%ld) created, timeout=%d, keepalive_timeout=%d, " - "max_streams=%d, stream_mem=%d", + "max_streams=%d, stream_mem=%d, push_diary_size=%d", session->id, session->timeout_secs, session->keepalive_secs, - (int)session->max_stream_count, (int)session->max_stream_mem); + (int)session->max_stream_count, (int)session->max_stream_mem, + (int)diary_size); } } return session; @@ -987,6 +992,10 @@ static apr_status_t h2_session_start(h2_session *session, int *rv) ++slen; } + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + "h2_session(%ld): start, INITIAL_WINDOW_SIZE=%ld, " + "MAX_CONCURRENT_STREAMS=%d", + session->id, (long)win_size, (int)session->max_stream_count); *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, slen); if (*rv != 0) { diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index 04e0792794..996dc826ff 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -45,6 +45,7 @@ struct h2_filter_cin; struct h2_mplx; struct h2_priority; struct h2_push; +struct h2_push_diary; struct h2_response; struct h2_session; struct h2_stream; @@ -127,6 +128,8 @@ typedef struct h2_session { struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */ struct h2_workers *workers; /* for executing stream tasks */ + + struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */ } h2_session; diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index 1777c990f4..6a3436b2b6 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -600,7 +600,7 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream) apr_array_header_t *pushes; int i; - pushes = h2_push_collect(stream->pool, stream->request, stream->response); + pushes = h2_push_collect_update(stream, stream->request, stream->response); if (pushes && !apr_is_empty_array(pushes)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, "h2_stream(%ld-%d): found %d push candidates",