]> granicus.if.org Git - apache/commitdiff
new directive H2PushDiarySize, first simple apr_hash implementation of a push diary...
authorStefan Eissing <icing@apache.org>
Mon, 11 Jan 2016 17:12:24 +0000 (17:12 +0000)
committerStefan Eissing <icing@apache.org>
Mon, 11 Jan 2016 17:12:24 +0000 (17:12 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1724086 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/mod/mod_http2.xml
modules/http2/h2_config.c
modules/http2/h2_config.h
modules/http2/h2_push.c
modules/http2/h2_push.h
modules/http2/h2_session.c
modules/http2/h2_session.h
modules/http2/h2_stream.c

index 43c64b97623f58e1ec5f79a8a920f20d6fe48bd5..40ba99eb46b9fa2fc2ec1de2ff3d5f2f8455646b 100644 (file)
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>H2PushDiarySize</name>
+        <description>H2 Server Push Diary Size</description>
+        <syntax>H2PushDiarySize n</syntax>
+        <default>H2PushDiarySize 128</default>
+        <contextlist>
+            <context>server config</context>
+            <context>virtual host</context>
+        </contextlist>
+        <compatibility>Available in version 2.4.19 and later.</compatibility>
+        
+        <usage>
+            <p>
+                This directive toggles the maximum number of HTTP/2 server pushes 
+                that are remembered per HTTP/2 connection. This can be used inside the
+                <directive module="core" type="section">VirtualHost</directive> 
+                section to influence the number for all connections to that virtual host. 
+            </p>
+            <p>
+                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.
+            </p>
+            <p>
+                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.
+            </p>
+            <p>
+                A size of 0 will effectively disable the push diary.
+            </p>
+        </usage>
+    </directivesynopsis>
+
     <directivesynopsis>
         <name>H2PushPriority</name>
         <description>H2 Server Push Priority</description>
index ecfe949dc9f3692db8407253d7a327668eedfa02..ee1bf598c3b003df0e6922f54c19f2bc256518f5 100644 (file)
@@ -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
 };
 
index 57d926d5f701767e6028faaa618fefe3214ebbbf..a40bb83c0268766761944f669b0f43a5746d5300 100644 (file)
@@ -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;
 
 
index 1ba4ea6fb85e31a2952992a352bbd32eabb3b53d..e29daecf9dbe9c5d86d4945ff50f603a4b903326 100644 (file)
 #include <assert.h>
 #include <stdio.h>
 
-#include <apr_strings.h>
 #include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include <apr_time.h>
 
 #include <httpd.h>
 #include <http_core.h>
@@ -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);
+}
+
index f3a8601c34526e711542747f4de452df7d7aefb0..5976e4c907b618ecd03bd0f80596cd7386ccdd81 100644 (file)
@@ -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__) */
index 4f92120b3d0d124c90bd5d0f47d6423a8cfcab12..82dcee716654f718aec4571efe556fd18bb98ee9 100644 (file)
@@ -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) {
index 04e079279475273051ad7ab86f9f93e2247f6e88..996dc826ff3b11607f3cee5672bcdb08780c119b 100644 (file)
@@ -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;
 
 
index 1777c990f4464a36f36288033d8d1c3d1d5aa082..6a3436b2b6270b1ecd9af08dae7412d0d907b1f4 100644 (file)
@@ -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",