You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2015/06/30 17:26:19 UTC

svn commit: r1688474 [8/21] - in /httpd/httpd/trunk/modules/http2: ./ m4/ mod-h2.xcodeproj/ mod-h2.xcodeproj/project.xcworkspace/ mod-h2.xcodeproj/project.xcworkspace/xcshareddata/ mod-h2.xcodeproj/xcuserdata/ mod-h2.xcodeproj/xcuserdata/sei.xcuserdata...

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_util.c?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_util.c (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_util.c Tue Jun 30 15:26:16 2015
@@ -0,0 +1,703 @@
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2_util.h"
+
+size_t h2_util_hex_dump(char *buffer, size_t maxlen,
+                        const char *data, size_t datalen)
+{
+    size_t offset = 0;
+    size_t maxoffset = (maxlen-4);
+    size_t i;
+    for (i = 0; i < datalen && offset < maxoffset; ++i) {
+        const char *sep = (i && i % 16 == 0)? "\n" : " ";
+        int n = apr_snprintf(buffer+offset, maxoffset-offset,
+                             "%2x%s", ((unsigned int)data[i]&0xff), sep);
+        offset += n;
+    }
+    strcpy(buffer+offset, (i<datalen)? "..." : "");
+    return strlen(buffer);
+}
+
+size_t h2_util_header_print(char *buffer, size_t maxlen,
+                            const char *name, size_t namelen,
+                            const char *value, size_t valuelen)
+{
+    size_t offset = 0;
+    size_t i;
+    for (i = 0; i < namelen && offset < maxlen; ++i, ++offset) {
+        buffer[offset] = name[i];
+    }
+    for (i = 0; i < 2 && offset < maxlen; ++i, ++offset) {
+        buffer[offset] = ": "[i];
+    }
+    for (i = 0; i < valuelen && offset < maxlen; ++i, ++offset) {
+        buffer[offset] = value[i];
+    }
+    buffer[offset] = '\0';
+    return offset;
+}
+
+
+char *h2_strlwr(char *s)
+{
+    for (char *p = s; *p; ++p) {
+        if (*p >= 'A' && *p <= 'Z') {
+            *p += 'a' - 'A';
+        }
+    }
+    return s;
+}
+
+void h2_util_camel_case_header(char *s, size_t len)
+{
+    size_t start = 1;
+    for (size_t i = 0; i < len; ++i) {
+        if (start) {
+            if (s[i] >= 'a' && s[i] <= 'z') {
+                s[i] -= 'a' - 'A';
+            }
+            
+            start = 0;
+        }
+        else if (s[i] == '-') {
+            start = 1;
+        }
+    }
+}
+
+static const int BASE64URL_TABLE[] = {
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
+    58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,
+    7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+    25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+    37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1
+};
+
+apr_size_t h2_util_base64url_decode(unsigned char **decoded, const char *encoded, 
+                                    apr_pool_t *pool)
+{
+    const unsigned char *e = (const unsigned char *)encoded;
+    const unsigned char *p = e;
+    int n;
+    
+    while (*p && BASE64URL_TABLE[ *p ] == -1) {
+        ++p;
+    }
+    apr_size_t len = p - e;
+    apr_size_t mlen = (len/4)*4;
+    *decoded = apr_pcalloc(pool, len+1);
+    
+    apr_size_t i = 0;
+    unsigned char *d = *decoded;
+    for (; i < mlen; i += 4) {
+        n = ((BASE64URL_TABLE[ e[i+0] ] << 18) +
+             (BASE64URL_TABLE[ e[i+1] ] << 12) +
+             (BASE64URL_TABLE[ e[i+2] ] << 6) +
+             BASE64URL_TABLE[ e[i+3] ]);
+        *d++ = n >> 16;
+        *d++ = n >> 8 & 0xffu;
+        *d++ = n & 0xffu;
+    }
+    apr_size_t remain = len - mlen;
+    switch (remain) {
+        case 2:
+            n = ((BASE64URL_TABLE[ e[mlen+0] ] << 18) +
+                 (BASE64URL_TABLE[ e[mlen+1] ] << 12));
+            *d++ = n >> 16;
+            break;
+        case 3:
+            n = ((BASE64URL_TABLE[ e[mlen+0] ] << 18) +
+                 (BASE64URL_TABLE[ e[mlen+1] ] << 12) +
+                 (BASE64URL_TABLE[ e[mlen+2] ] << 6));
+            *d++ = n >> 16;
+            *d++ = n >> 8 & 0xffu;
+            break;
+        default: /* do nothing */
+            break;
+    }
+    return len;
+}
+
+int h2_util_contains_token(apr_pool_t *pool, const char *s, const char *token)
+{
+    if (s) {
+        if (!apr_strnatcasecmp(s, token)) {   /* the simple life */
+            return 1;
+        }
+        
+        for (char *c = ap_get_token(pool, &s, 0); c && *c;
+             c = *s? ap_get_token(pool, &s, 0) : NULL) {
+            if (!apr_strnatcasecmp(c, token)) { /* seeing the token? */
+                return 1;
+            }
+            while (*s++ == ';') {            /* skip parameters */
+                ap_get_token(pool, &s, 0);
+            }
+            if (*s++ != ',') {               /* need comma separation */
+                return 0;
+            }
+        }
+    }
+    return 0;
+}
+
+const char *h2_util_first_token_match(apr_pool_t *pool, const char *s, 
+                                      const char *tokens[], apr_size_t len)
+{
+    if (s && *s) {
+        for (char *c = ap_get_token(pool, &s, 0); c && *c;
+             c = *s? ap_get_token(pool, &s, 0) : NULL) {
+            for (apr_size_t i = 0; i < len; ++i) {
+                if (!apr_strnatcasecmp(c, tokens[i])) {
+                    return tokens[i];
+                }
+            }
+            while (*s++ == ';') {            /* skip parameters */
+                ap_get_token(pool, &s, 0);
+            }
+            if (*s++ != ',') {               /* need comma separation */
+                return 0;
+            }
+        }
+    }
+    return NULL;
+}
+
+/* DEEP_COPY==0 crashes under load. I think the setaside is fine, 
+ * however buckets moved to another thread will still be
+ * free'd against the old bucket_alloc. *And* if the old
+ * pool gets destroyed too early, the bucket disappears while
+ * still needed.
+ */
+static const int DEEP_COPY = 1;
+static const int FILE_MOVE = 0;
+
+static apr_status_t last_not_included(apr_bucket_brigade *bb, 
+                                      apr_size_t maxlen, int count_virtual,
+                                      apr_bucket **pend)
+{
+    apr_bucket *b;
+    apr_status_t status = APR_SUCCESS;
+    
+    if (maxlen > 0) {
+        /* Find the bucket, up to which we reach maxlen/mem bytes */
+        for (b = APR_BRIGADE_FIRST(bb); 
+             (b != APR_BRIGADE_SENTINEL(bb));
+             b = APR_BUCKET_NEXT(b)) {
+            
+            if (APR_BUCKET_IS_METADATA(b)) {
+                /* included */
+            }
+            else {
+                if (maxlen == 0) {
+                    *pend = b;
+                    return status;
+                }
+                
+                if (b->length == ((apr_size_t)-1)) {
+                    const char *ign;
+                    apr_size_t ilen;
+                    status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+                    if (status != APR_SUCCESS) {
+                        return status;
+                    }
+                }
+                
+                if (!count_virtual && FILE_MOVE && APR_BUCKET_IS_FILE(b)) {
+                    /* this has no memory footprint really unless
+                     * it is read, disregard it in length count,
+                     * unless we count the virtual buckets */
+                }
+                else if (maxlen < b->length) {
+                    apr_bucket_split(b, maxlen);
+                    maxlen = 0;
+                }
+                else {
+                    maxlen -= b->length;
+                }
+            }
+        }
+    }
+    *pend = APR_BRIGADE_SENTINEL(bb);
+    return status;
+}
+
+#define LOG_LEVEL APLOG_TRACE2
+
+apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from, 
+                          apr_size_t maxlen, int count_virtual, 
+                          apr_file_t **pfile, const char *msg)
+{
+    apr_status_t status = APR_SUCCESS;
+    
+    AP_DEBUG_ASSERT(to);
+    AP_DEBUG_ASSERT(from);
+    int same_alloc = (to->bucket_alloc == from->bucket_alloc);
+    
+    if (!APR_BRIGADE_EMPTY(from)) {
+        apr_bucket *b, *end;
+        
+        status = last_not_included(from, maxlen, 
+                                   (count_virtual || !FILE_MOVE), &end);
+        if (status != APR_SUCCESS) {
+            return status;
+        }
+        
+        while (!APR_BRIGADE_EMPTY(from) && status == APR_SUCCESS) {
+            b = APR_BRIGADE_FIRST(from);
+            if (b == end) {
+                break;
+            }
+            
+            if (same_alloc || (b->list == to->bucket_alloc)) {
+                /* both brigades use the same bucket_alloc and auto-cleanups
+                 * have the same life time. It's therefore safe to just move
+                 * directly. */
+                APR_BUCKET_REMOVE(b);
+                APR_BRIGADE_INSERT_TAIL(to, b);
+                ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                              "h2_util_move: %s, passed bucket(same bucket_alloc) "
+                              "%ld-%ld, type=%s",
+                              msg, (long)b->start, (long)b->length, 
+                              APR_BUCKET_IS_METADATA(b)? 
+                              (APR_BUCKET_IS_EOS(b)? "EOS": 
+                               (APR_BUCKET_IS_FLUSH(b)? "FLUSH" : "META")) : 
+                              (APR_BUCKET_IS_FILE(b)? "FILE" : "DATA"));
+            }
+            else if (DEEP_COPY) {
+                /* we have not managed the magic of passing buckets from
+                 * one thread to another. Any attempts result in
+                 * cleanup of pools scrambling memory.
+                 */
+                if (APR_BUCKET_IS_METADATA(b)) {
+                    if (APR_BUCKET_IS_EOS(b)) {
+                        APR_BRIGADE_INSERT_TAIL(to, apr_bucket_eos_create(to->bucket_alloc));
+                        ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                      "h2_util_move: %s, copied EOS bucket", msg);
+                    }
+                    else if (APR_BUCKET_IS_FLUSH(b)) {
+                        APR_BRIGADE_INSERT_TAIL(to, apr_bucket_flush_create(to->bucket_alloc));
+                        ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                      "h2_util_move: %s, copied FLUSH bucket", msg);
+                    }
+                    else {
+                        /* ignore */
+                    }
+                }
+                else if (pfile && FILE_MOVE && APR_BUCKET_IS_FILE(b)) {
+                    /* We do not want to read files when passing buckets, if
+                     * we can avoid it. However, what we've come up so far
+                     * is not working corrently, resulting either in crashes or
+                     * too many open file descriptors.
+                     */
+                    apr_bucket_file *f = (apr_bucket_file *)b->data;
+                    apr_file_t *fd = f->fd;
+                    int setaside = (f->readpool != to->p);
+                    ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                  "h2_util_move: %s, moving FILE bucket %ld-%ld "
+                                  "from=%lx(p=%lx) to=%lx(p=%lx), setaside=%d",
+                                  msg, (long)b->start, (long)b->length, 
+                                  (long)from, (long)from->p, 
+                                  (long)to, (long)to->p, setaside);
+                    if (setaside) {
+                        status = apr_file_setaside(&fd, fd, to->p);
+                        *pfile = fd;
+                        if (status != APR_SUCCESS) {
+                            ap_log_perror(APLOG_MARK, APLOG_ERR, status, to->p,
+                                          "h2_util: %s, setaside FILE", msg);
+                            return status;
+                        }
+                    }
+                    apr_brigade_insert_file(to, fd, b->start, b->length, 
+                                            to->p);
+                }
+                else {
+                    const char *data;
+                    apr_size_t len;
+                    status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+                    if (status == APR_SUCCESS && len > 0) {
+                        status = apr_brigade_write(to, NULL, NULL, data, len);
+                        ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                      "h2_util_move: %s, copied bucket %ld-%ld "
+                                      "from=%lx(p=%lx) to=%lx(p=%lx)",
+                                      msg, (long)b->start, (long)b->length, 
+                                      (long)from, (long)from->p, 
+                                      (long)to, (long)to->p);
+                    }
+                }
+                apr_bucket_delete(b);
+            }
+            else {
+                apr_bucket_setaside(b, to->p);
+                APR_BUCKET_REMOVE(b);
+                APR_BRIGADE_INSERT_TAIL(to, b);
+                ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                              "h2_util_move: %s, passed setaside bucket %ld-%ld "
+                              "from=%lx(p=%lx) to=%lx(p=%lx)",
+                              msg, (long)b->start, (long)b->length, 
+                              (long)from, (long)from->p, 
+                              (long)to, (long)to->p);
+            }
+        }
+    }
+    
+    return status;
+}
+
+apr_status_t h2_util_pass(apr_bucket_brigade *to, apr_bucket_brigade *from, 
+                          apr_size_t maxlen, int count_virtual, 
+                          const char *msg)
+{
+    apr_status_t status = APR_SUCCESS;
+    
+    AP_DEBUG_ASSERT(to);
+    AP_DEBUG_ASSERT(from);
+    
+    if (!APR_BRIGADE_EMPTY(from)) {
+        apr_bucket *b, *end;
+        
+        status = last_not_included(from, maxlen, count_virtual, &end);
+        if (status != APR_SUCCESS) {
+            return status;
+        }
+
+        while (!APR_BRIGADE_EMPTY(from) && status == APR_SUCCESS) {
+            b = APR_BRIGADE_FIRST(from);
+            if (b == end) {
+                break;
+            }
+            
+            APR_BUCKET_REMOVE(b);
+            if (APR_BUCKET_IS_METADATA(b)) {
+                if (!APR_BUCKET_IS_EOS(b) && !APR_BUCKET_IS_FLUSH(b)) {
+                    apr_bucket_delete(b);
+                    continue;
+                }
+            }
+            else if (b->length == 0) {
+                apr_bucket_delete(b);
+                continue;
+            }
+            
+            APR_BRIGADE_INSERT_TAIL(to, b);
+            ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                          "h2_util_pass: %s, passed bucket %ld-%ld, type=%s",
+                          msg, (long)b->start, (long)b->length, 
+                          APR_BUCKET_IS_METADATA(b)? 
+                          (APR_BUCKET_IS_EOS(b)? "EOS": 
+                           (APR_BUCKET_IS_FLUSH(b)? "FLUSH" : "META")) : 
+                          (APR_BUCKET_IS_FILE(b)? "FILE" : "DATA"));
+        }
+    }
+    
+    return status;
+}
+
+apr_status_t h2_util_copy(apr_bucket_brigade *to, apr_bucket_brigade *from, 
+                          apr_size_t maxlen, const char *msg)
+{
+    apr_status_t status = APR_SUCCESS;
+    
+    AP_DEBUG_ASSERT(to);
+    AP_DEBUG_ASSERT(from);
+    int same_alloc = (to->bucket_alloc == from->bucket_alloc);
+
+    if (!APR_BRIGADE_EMPTY(from)) {
+        apr_bucket *b, *end, *cpy;
+        
+        status = last_not_included(from, maxlen, 1, &end);
+        if (status != APR_SUCCESS) {
+            return status;
+        }
+
+        for (b = APR_BRIGADE_FIRST(from);
+             b != APR_BRIGADE_SENTINEL(from) && b != end;
+             b = APR_BUCKET_NEXT(b))
+        {
+            if (same_alloc) {
+                status = apr_bucket_copy(b, &cpy);
+                if (status != APR_SUCCESS) {
+                    break;
+                }
+                APR_BRIGADE_INSERT_TAIL(to, cpy);
+            }
+            else {
+                if (APR_BUCKET_IS_METADATA(b)) {
+                    if (APR_BUCKET_IS_EOS(b)) {
+                        APR_BRIGADE_INSERT_TAIL(to, apr_bucket_eos_create(to->bucket_alloc));
+                        ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                      "h2_util_copy: %s, copied EOS bucket", msg);
+                    }
+                    else if (APR_BUCKET_IS_FLUSH(b)) {
+                        APR_BRIGADE_INSERT_TAIL(to, apr_bucket_flush_create(to->bucket_alloc));
+                        ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                      "h2_util_copy: %s, copied FLUSH bucket", msg);
+                    }
+                    else {
+                        /* ignore */
+                    }
+                }
+                else {
+                    const char *data;
+                    apr_size_t len;
+                    status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+                    if (status == APR_SUCCESS && len > 0) {
+                        status = apr_brigade_write(to, NULL, NULL, data, len);
+                        ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p,
+                                      "h2_util_copy: %s, copied bucket %ld-%ld "
+                                      "from=%lx(p=%lx) to=%lx(p=%lx)",
+                                      msg, (long)b->start, (long)b->length, 
+                                      (long)from, (long)from->p, 
+                                      (long)to, (long)to->p);
+                    }
+                }
+            }
+        }
+    }
+    return status;
+}
+
+int h2_util_has_flush_or_eos(apr_bucket_brigade *bb) {
+    apr_bucket *b;
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = APR_BUCKET_NEXT(b))
+    {
+        if (APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len)
+{
+    apr_bucket *b, *end;
+    
+    apr_status_t status = last_not_included(bb, len, 1, &end);
+    if (status != APR_SUCCESS) {
+        return status;
+    }
+    
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb) && b != end;
+         b = APR_BUCKET_NEXT(b))
+    {
+        if (APR_BUCKET_IS_EOS(b)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int h2_util_bb_has_data(apr_bucket_brigade *bb)
+{
+    apr_bucket *b;
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = APR_BUCKET_NEXT(b))
+    {
+        if (!APR_BUCKET_IS_METADATA(b)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int h2_util_bb_has_data_or_eos(apr_bucket_brigade *bb)
+{
+    apr_bucket *b;
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = APR_BUCKET_NEXT(b))
+    {
+        if (APR_BUCKET_IS_METADATA(b)) {
+            if (APR_BUCKET_IS_EOS(b)) {
+                return 1;
+            }
+        }
+        else {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, 
+                              apr_size_t *plen, int *peos)
+{
+    apr_status_t status;
+    /* test read to determine available length */
+    apr_off_t blen = 0;
+    status = apr_brigade_length(bb, 0, &blen);
+    if (blen < (apr_off_t)*plen) {
+        *plen = blen;
+    }
+    *peos = h2_util_has_eos(bb, *plen);
+    return status;
+}
+
+apr_status_t h2_util_bb_read(apr_bucket_brigade *bb, char *buffer, 
+                             apr_size_t *plen, int *peos)
+{
+    apr_status_t status = APR_SUCCESS;
+    apr_size_t avail = *plen;
+    apr_size_t written = 0;
+
+    /* Copy data in our brigade into the buffer until it is filled or
+     * we encounter an EOS.
+     */
+    *peos = 0;
+    while ((status == APR_SUCCESS) && !APR_BRIGADE_EMPTY(bb)) {
+        
+        apr_bucket *b = APR_BRIGADE_FIRST(bb);
+        if (APR_BUCKET_IS_METADATA(b)) {
+            if (APR_BUCKET_IS_EOS(b)) {
+                *peos = 1;
+            }
+            else {
+                /* ignore */
+            }
+        }
+        else if (avail <= 0) {
+            break;
+        } 
+        else {
+            const char *data;
+            apr_size_t data_len;
+            
+            if (b->length != ((apr_size_t)-1) && b->length > avail) {
+                apr_bucket_split(b, avail);
+            }
+            status = apr_bucket_read(b, &data, &data_len, 
+                                     APR_NONBLOCK_READ);
+            if (status == APR_SUCCESS && data_len > 0) {
+                if (data_len > avail) {
+                    apr_bucket_split(b, avail);
+                    data_len = avail;
+                }
+                memcpy(buffer, data, data_len);
+                avail -= data_len;
+                buffer += data_len;
+                written += data_len;
+            }
+        }
+        apr_bucket_delete(b);
+    }
+    
+    *plen = written;
+    if (status == APR_SUCCESS && !*peos && !*plen) {
+        return APR_EAGAIN;
+    }
+    return status;
+}
+
+apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb, 
+                              h2_util_pass_cb *cb, void *ctx, 
+                              apr_size_t *plen, int *peos)
+{
+    apr_status_t status = APR_SUCCESS;
+    apr_size_t avail = *plen;
+    apr_size_t written = 0;
+    apr_bucket *next, *b = APR_BRIGADE_FIRST(bb);
+    int consume = (cb != NULL);
+    
+    /* Pass data in our brigade through the callback until the length
+     * is satisfied or we encounter an EOS.
+     */
+    *peos = 0;
+    for (b = APR_BRIGADE_FIRST(bb);
+         (status == APR_SUCCESS) && (b != APR_BRIGADE_SENTINEL(bb));
+         b = next) {
+        
+        if (APR_BUCKET_IS_METADATA(b)) {
+            if (APR_BUCKET_IS_EOS(b)) {
+                *peos = 1;
+            }
+            else {
+                /* ignore */
+            }
+        }
+        else if (avail <= 0) {
+            break;
+        } 
+        else {
+            const char *data = NULL;
+            apr_size_t data_len;
+            
+            if (b->length != ((apr_size_t)-1)) {
+                status = apr_bucket_read(b, &data, &data_len, 
+                                         APR_NONBLOCK_READ);
+            }
+            else {
+                data_len = b->length;
+            }
+            
+            if (data_len > avail) {
+                apr_bucket_split(b, avail);
+                data_len = avail;
+            }
+            
+            if (consume) {
+                if (!data) {
+                    status = apr_bucket_read(b, &data, &data_len, 
+                                             APR_NONBLOCK_READ);
+                }
+                if (status == APR_SUCCESS) {
+                    status = cb(ctx, data, data_len);
+                }
+            }
+            else {
+                data_len = b->length;
+            }
+            avail -= data_len;
+            written += data_len;
+        }
+        
+        next = APR_BUCKET_NEXT(b);
+        if (consume) {
+            apr_bucket_delete(b);
+        }
+    }
+    
+    *plen = written;
+    if (status == APR_SUCCESS && !*peos && !*plen) {
+        return APR_EAGAIN;
+    }
+    return status;
+}
+

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_util.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_util.h?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_util.h (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_util.h Tue Jun 30 15:26:16 2015
@@ -0,0 +1,140 @@
+/* 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_util__
+#define __mod_h2__h2_util__
+
+struct nghttp2_frame;
+
+size_t h2_util_hex_dump(char *buffer, size_t maxlen,
+                        const char *data, size_t datalen);
+
+size_t h2_util_header_print(char *buffer, size_t maxlen,
+                            const char *name, size_t namelen,
+                            const char *value, size_t valuelen);
+
+char *h2_strlwr(char *s);
+
+void h2_util_camel_case_header(char *s, size_t len);
+
+/**
+ * Return != 0 iff the string s contains the token, as specified in
+ * HTTP header syntax, rfc7230.
+ */
+int h2_util_contains_token(apr_pool_t *pool, const char *s, const char *token);
+
+const char *h2_util_first_token_match(apr_pool_t *pool, const char *s, 
+                                      const char *tokens[], apr_size_t len);
+
+/**
+ * I always wanted to write my own base64url decoder...not. See 
+ * https://tools.ietf.org/html/rfc4648#section-5 for description.
+ */
+apr_size_t h2_util_base64url_decode(unsigned char **decoded, 
+                                    const char *encoded, 
+                                    apr_pool_t *pool);
+
+#define H2_HD_MATCH_LIT(l, name, nlen)  \
+    ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+#define H2_HD_MATCH_LIT_CS(l, name)  \
+    ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+#define H2_CREATE_NV_LIT_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME;      \
+                                             nv->namelen = sizeof(NAME) - 1;  \
+                                             nv->value = (uint8_t *)VALUE;    \
+                                             nv->valuelen = strlen(VALUE)
+
+#define H2_CREATE_NV_CS_LIT(nv, NAME, VALUE) nv->name = (uint8_t *)NAME;      \
+                                             nv->namelen = strlen(NAME);      \
+                                             nv->value = (uint8_t *)VALUE;    \
+                                             nv->valuelen = sizeof(VALUE) - 1
+
+#define H2_CREATE_NV_CS_CS(nv, NAME, VALUE) nv->name = (uint8_t *)NAME;       \
+                                            nv->namelen = strlen(NAME);       \
+                                            nv->value = (uint8_t *)VALUE;     \
+                                            nv->valuelen = strlen(VALUE)
+
+/**
+ * Moves data from one brigade into another. If maxlen > 0, it only
+ * moves up to maxlen bytes into the target brigade, making bucket splits
+ * if needed.
+ * @param to the brigade to move the data to
+ * @param from the brigade to get the data from
+ * @param maxlen of bytes to move, 0 for all
+ * @param count_virtual if virtual buckets like FILE do count against maxlen
+ * @param msg message for use in logging
+ */
+apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from, 
+                          apr_size_t maxlen, int count_virtual, 
+                          apr_file_t **pfile, const char *msg);
+
+/**
+ * Copies buckets from one brigade into another. If maxlen > 0, it only
+ * copies up to maxlen bytes into the target brigade, making bucket splits
+ * if needed.
+ * @param to the brigade to copy the data to
+ * @param from the brigade to get the data from
+ * @param maxlen of bytes to copy, 0 for all
+ * @param msg message for use in logging
+ */
+apr_status_t h2_util_copy(apr_bucket_brigade *to, apr_bucket_brigade *from, 
+                          apr_size_t maxlen, const char *msg);
+
+/**
+ * Pass the buckets from one brigade into another, without any setaside. 
+ * Only recommended if both brigades use the same bucket alloc or if
+ * you really know what you are doing.
+ * @param to the brigade to pass the buckets to
+ * @param from the brigade to get the buckets from
+ * @param maxlen of bucket bytes to copy, 0 for all
+ * @param count_virtual if virtual buckets like FILE do count against maxlen
+ * @param msg message for use in logging
+ */
+apr_status_t h2_util_pass(apr_bucket_brigade *to, apr_bucket_brigade *from, 
+                          apr_size_t maxlen, int count_virtual, 
+                          const char *msg);
+
+/**
+ * Return != 0 iff there is a FLUSH or EOS bucket in the brigade.
+ * @param bb the brigade to check on
+ * @return != 0 iff brigade holds FLUSH or EOS bucket (or both)
+ */
+int h2_util_has_flush_or_eos(apr_bucket_brigade *bb);
+int h2_util_has_eos(apr_bucket_brigade *bb, apr_size_t len);
+int h2_util_bb_has_data(apr_bucket_brigade *bb);
+int h2_util_bb_has_data_or_eos(apr_bucket_brigade *bb);
+
+/**
+ * Check how many bytes of the desired amount are available and if the
+ * end of stream is reached by that amount.
+ * @param bb the brigade to check
+ * @param plen the desired length and, on return, the available length
+ * @param on return, if eos has been reached
+ */
+apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, 
+                              apr_size_t *plen, int *peos);
+
+apr_status_t h2_util_bb_read(apr_bucket_brigade *bb, char *buffer, 
+                             apr_size_t *plen, int *peos);
+
+typedef apr_status_t h2_util_pass_cb(void *ctx, 
+                                       const char *data, apr_size_t len);
+
+apr_status_t h2_util_bb_readx(apr_bucket_brigade *bb, 
+                              h2_util_pass_cb *cb, void *ctx, 
+                              apr_size_t *plen, int *peos);
+
+#endif /* defined(__mod_h2__h2_util__) */

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_version.h.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_version.h.in?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_version.h.in (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_version.h.in Tue Jun 30 15:26:16 2015
@@ -0,0 +1,42 @@
+/* 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_version_h
+#define mod_h2_h2_version_h
+
+/**
+ * @macro
+ * Version number of the h2 module as c string
+ */
+#define MOD_H2_VERSION "@PACKAGE_VERSION@"
+
+/**
+ * @macro
+ * Numerical representation of the version number of the h2 module
+ * 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_H2_VERSION_NUM @PACKAGE_VERSION_NUM@
+
+
+/**
+ * @macro
+ * != 0 iff the nghttp2 library supports data callbacks.
+ * Disabled for now since it lowers performance.
+ */
+#define NGHTTP2_HAS_DATA_CBXXX @NGHTTP2_HAS_DATA_CB@
+#define NGHTTP2_HAS_DATA_CB 0
+
+#endif /* mod_h2_h2_version_h */

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.c?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.c (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.c Tue Jun 30 15:26:16 2015
@@ -0,0 +1,168 @@
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_thread_cond.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_mplx.h"
+#include "h2_task.h"
+#include "h2_worker.h"
+
+static void *execute(apr_thread_t *thread, void *wctx)
+{
+    h2_worker *worker = (h2_worker *)wctx;
+    apr_status_t status = APR_SUCCESS;
+    h2_mplx *m;
+    (void)thread;
+    
+    /* Furthermore, other code might want to see the socket for
+     * this connection. Allocate one without further function...
+     */
+    status = apr_socket_create(&worker->socket,
+                               APR_INET, SOCK_STREAM,
+                               APR_PROTO_TCP, worker->pool);
+    if (status != APR_SUCCESS) {
+        ap_log_perror(APLOG_MARK, APLOG_ERR, status, worker->pool,
+                      "h2_worker(%d): alloc socket", worker->id);
+        worker->worker_done(worker, worker->ctx);
+        return NULL;
+    }
+    
+    worker->task = NULL;
+    m = NULL;
+    while (!worker->aborted) {
+        status = worker->get_next(worker, &m, &worker->task, worker->ctx);
+        
+        if (worker->task) {            
+            h2_task_do(worker->task, worker);
+            worker->task = NULL;
+            apr_thread_cond_signal(h2_worker_get_cond(worker));
+        }
+    }
+
+    status = worker->get_next(worker, &m, NULL, worker->ctx);
+    m = NULL;
+    
+    if (worker->socket) {
+        apr_socket_close(worker->socket);
+        worker->socket = NULL;
+    }
+    
+    worker->worker_done(worker, worker->ctx);
+    return NULL;
+}
+
+h2_worker *h2_worker_create(int id,
+                            apr_pool_t *parent_pool,
+                            apr_threadattr_t *attr,
+                            h2_worker_mplx_next_fn *get_next,
+                            h2_worker_done_fn *worker_done,
+                            void *ctx)
+{
+    apr_allocator_t *allocator = NULL;
+    apr_pool_t *pool = NULL;
+    
+    apr_status_t status = apr_allocator_create(&allocator);
+    if (status != APR_SUCCESS) {
+        return NULL;
+    }
+    
+    status = apr_pool_create_ex(&pool, parent_pool, NULL, allocator);
+    if (status != APR_SUCCESS) {
+        return NULL;
+    }
+    apr_allocator_owner_set(allocator, pool);
+
+    h2_worker *w = apr_pcalloc(pool, sizeof(h2_worker));
+    if (w) {
+        APR_RING_ELEM_INIT(w, link);
+        
+        w->id = id;
+        w->pool = pool;
+        w->bucket_alloc = apr_bucket_alloc_create(pool);
+
+        w->get_next = get_next;
+        w->worker_done = worker_done;
+        w->ctx = ctx;
+        
+        status = apr_thread_cond_create(&w->io, w->pool);
+        if (status != APR_SUCCESS) {
+            return NULL;
+        }
+        
+        apr_thread_create(&w->thread, attr, execute, w, pool);
+    }
+    return w;
+}
+
+apr_status_t h2_worker_destroy(h2_worker *worker)
+{
+    if (worker->io) {
+        apr_thread_cond_destroy(worker->io);
+        worker->io = NULL;
+    }
+    if (worker->pool) {
+        apr_pool_destroy(worker->pool);
+        /* worker is gone */
+    }
+    return APR_SUCCESS;
+}
+
+int h2_worker_get_id(h2_worker *worker)
+{
+    return worker->id;
+}
+
+void h2_worker_abort(h2_worker *worker)
+{
+    worker->aborted = 1;
+}
+
+int h2_worker_is_aborted(h2_worker *worker)
+{
+    return worker->aborted;
+}
+
+apr_thread_t *h2_worker_get_thread(h2_worker *worker)
+{
+    return worker->thread;
+}
+
+apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker)
+{
+    return worker->io;
+}
+
+apr_socket_t *h2_worker_get_socket(h2_worker *worker)
+{
+    return worker->socket;
+}
+
+apr_pool_t *h2_worker_get_pool(h2_worker *worker)
+{
+    return worker->pool;
+}
+
+apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker)
+{
+    return worker->bucket_alloc;
+}
+

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.h?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.h (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_worker.h Tue Jun 30 15:26:16 2015
@@ -0,0 +1,155 @@
+/* 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_worker__
+#define __mod_h2__h2_worker__
+
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_task;
+
+/* h2_worker is a basically a apr_thread_t that reads fromt he h2_workers
+ * task queue and runs h2_tasks it is given.
+ */
+typedef struct h2_worker h2_worker;
+
+/* Invoked when the worker wants a new task to process. Will block
+ * until a h2_mplx becomes available or the worker itself
+ * gets aborted (idle timeout, for example). */
+typedef apr_status_t h2_worker_mplx_next_fn(h2_worker *worker,
+                                            struct h2_mplx **pm,
+                                            struct h2_task **ptask,
+                                            void *ctx);
+
+/* Invoked just before the worker thread exits. */
+typedef void h2_worker_done_fn(h2_worker *worker, void *ctx);
+
+
+struct h2_worker {
+    /** Links to the rest of the workers */
+    APR_RING_ENTRY(h2_worker) link;
+    
+    int id;
+    apr_thread_t *thread;
+    apr_pool_t *pool;
+    apr_bucket_alloc_t *bucket_alloc;
+    struct apr_thread_cond_t *io;
+    apr_socket_t *socket;
+    
+    h2_worker_mplx_next_fn *get_next;
+    h2_worker_done_fn *worker_done;
+    void *ctx;
+    
+    int aborted;
+    struct h2_task *task;
+};
+
+/**
+ * The magic pointer value that indicates the head of a h2_worker list
+ * @param  b The worker list
+ * @return The magic pointer value
+ */
+#define H2_WORKER_LIST_SENTINEL(b)	APR_RING_SENTINEL((b), h2_worker, link)
+
+/**
+ * Determine if the worker list is empty
+ * @param b The list to check
+ * @return true or false
+ */
+#define H2_WORKER_LIST_EMPTY(b)	APR_RING_EMPTY((b), h2_worker, link)
+
+/**
+ * Return the first worker in a list
+ * @param b The list to query
+ * @return The first worker in the list
+ */
+#define H2_WORKER_LIST_FIRST(b)	APR_RING_FIRST(b)
+
+/**
+ * Return the last worker in a list
+ * @param b The list to query
+ * @return The last worker int he list
+ */
+#define H2_WORKER_LIST_LAST(b)	APR_RING_LAST(b)
+
+/**
+ * Insert a single worker at the front of a list
+ * @param b The list to add to
+ * @param e The worker to insert
+ */
+#define H2_WORKER_LIST_INSERT_HEAD(b, e) do {				\
+	h2_worker *ap__b = (e);                                        \
+	APR_RING_INSERT_HEAD((b), ap__b, h2_worker, link);	\
+    } while (0)
+
+/**
+ * Insert a single worker at the end of a list
+ * @param b The list to add to
+ * @param e The worker to insert
+ */
+#define H2_WORKER_LIST_INSERT_TAIL(b, e) do {				\
+	h2_worker *ap__b = (e);					\
+	APR_RING_INSERT_TAIL((b), ap__b, h2_worker, link);	\
+    } while (0)
+
+/**
+ * Get the next worker in the list
+ * @param e The current worker
+ * @return The next worker
+ */
+#define H2_WORKER_NEXT(e)	APR_RING_NEXT((e), link)
+/**
+ * Get the previous worker in the list
+ * @param e The current worker
+ * @return The previous worker
+ */
+#define H2_WORKER_PREV(e)	APR_RING_PREV((e), link)
+
+/**
+ * Remove a worker from its list
+ * @param e The worker to remove
+ */
+#define H2_WORKER_REMOVE(e)	APR_RING_REMOVE((e), link)
+
+
+/* Create a new worker with given id, pool and attributes, callbacks
+ * callback parameter.
+ */
+h2_worker *h2_worker_create(int id,
+                            apr_pool_t *pool,
+                            apr_threadattr_t *attr,
+                            h2_worker_mplx_next_fn *get_next,
+                            h2_worker_done_fn *worker_done,
+                            void *ctx);
+
+apr_status_t h2_worker_destroy(h2_worker *worker);
+
+void h2_worker_abort(h2_worker *worker);
+
+int h2_worker_get_id(h2_worker *worker);
+
+int h2_worker_is_aborted(h2_worker *worker);
+
+apr_pool_t *h2_worker_get_pool(h2_worker *worker);
+
+apr_bucket_alloc_t *h2_worker_get_bucket_alloc(h2_worker *worker);
+
+apr_socket_t *h2_worker_get_socket(h2_worker *worker);
+
+apr_thread_t *h2_worker_get_thread(h2_worker *worker);
+
+struct apr_thread_cond_t *h2_worker_get_cond(h2_worker *worker);
+
+#endif /* defined(__mod_h2__h2_worker__) */

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.c?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.c (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.c Tue Jun 30 15:26:16 2015
@@ -0,0 +1,344 @@
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_atomic.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_mplx.h"
+#include "h2_task.h"
+#include "h2_task_queue.h"
+#include "h2_worker.h"
+#include "h2_workers.h"
+
+static int in_list(h2_workers *workers, h2_mplx *m)
+{
+    h2_mplx *e;
+    for (e = H2_MPLX_LIST_FIRST(&workers->mplxs); 
+         e != H2_MPLX_LIST_SENTINEL(&workers->mplxs);
+         e = H2_MPLX_NEXT(e)) {
+        if (e == m) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+
+static apr_status_t get_mplx_next(h2_worker *worker, h2_mplx **pm, 
+                                  h2_task **ptask, void *ctx)
+{
+    apr_status_t status;
+    h2_mplx *m = NULL;
+    h2_task *task = NULL;
+    apr_time_t max_wait, start_wait;
+    int has_more = 0;
+    h2_workers *workers = (h2_workers *)ctx;
+    
+    if (*pm && ptask != NULL) {
+        /* We have a h2_mplx instance and the worker wants the next task. 
+         * Try to get one from the given mplx. */
+        *ptask = h2_mplx_pop_task(*pm, &has_more);
+        if (*ptask) {
+            return APR_SUCCESS;
+        }
+    }
+    
+    if (*pm) {
+        /* Got a mplx handed in, but did not get or want a task from it. 
+         * Release it, as the workers reference will be wiped.
+         */
+        h2_mplx_release(*pm);
+        *pm = NULL;
+    }
+    
+    if (!ptask) {
+        /* of the worker does not want a next task, we're done.
+         */
+        return APR_SUCCESS;
+    }
+    
+    max_wait = apr_time_from_sec(apr_atomic_read32(&workers->max_idle_secs));
+    start_wait = apr_time_now();
+    
+    status = apr_thread_mutex_lock(workers->lock);
+    if (status == APR_SUCCESS) {
+        ++workers->idle_worker_count;
+        
+        ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+                     "h2_worker(%d): looking for work", h2_worker_get_id(worker));
+        
+        while (!task && !h2_worker_is_aborted(worker) && !workers->aborted) {
+            
+            /* Get the next h2_mplx to process that has a task to hand out.
+             * If it does, place it at the end of the queu and return the
+             * task to the worker.
+             * If it (currently) has no tasks, remove it so that it needs
+             * to register again for scheduling.
+             * If we run out of h2_mplx in the queue, we need to wait for
+             * new mplx to arrive. Depending on how many workers do exist,
+             * we do a timed wait or block indefinitely.
+             */
+            m = NULL;
+            while (!task && !H2_MPLX_LIST_EMPTY(&workers->mplxs)) {
+                m = H2_MPLX_LIST_FIRST(&workers->mplxs);
+                H2_MPLX_REMOVE(m);
+                
+                task = h2_mplx_pop_task(m, &has_more);
+                if (task) {
+                    if (has_more) {
+                        H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
+                    }
+                    else {
+                        has_more = !H2_MPLX_LIST_EMPTY(&workers->mplxs);
+                    }
+                    break;
+                }
+            }
+            
+            if (!task) {
+                /* Need to wait for either a new mplx to arrive.
+                 */
+                if (workers->worker_count > workers->min_size) {
+                    apr_time_t now = apr_time_now();
+                    if (now >= (start_wait + max_wait)) {
+                        /* waited long enough without getting a task. */
+                        status = APR_TIMEUP;
+                    }
+                    else {
+                        ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+                                     "h2_worker(%d): waiting signal, "
+                                     "worker_count=%d", worker->id, 
+                                     (int)workers->worker_count);
+                        status = apr_thread_cond_timedwait(workers->mplx_added,
+                                                           workers->lock, max_wait);
+                    }
+                    
+                    if (status == APR_TIMEUP) {
+                        /* waited long enough */
+                        if (workers->worker_count > workers->min_size) {
+                            ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, 
+                                         workers->s,
+                                         "h2_workers: aborting idle worker");
+                            h2_worker_abort(worker);
+                            break;
+                        }
+                    }
+                }
+                else {
+                    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+                                 "h2_worker(%d): waiting signal (eternal), "
+                                 "worker_count=%d", worker->id, 
+                                 (int)workers->worker_count);
+                    apr_thread_cond_wait(workers->mplx_added, workers->lock);
+                }
+            }
+        }
+        
+        /* Here, we either have gotten task and mplx for the worker or
+         * needed to give up with more than enough workers.
+         */
+        if (task) {
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+                         "h2_worker(%d): start task(%s)",
+                         h2_worker_get_id(worker), task->id);
+            /* Since we hand out a reference to the worker, we increase
+             * its ref count.
+             */
+            h2_mplx_reference(m);
+            *pm = m;
+            *ptask = task;
+            
+            if (has_more && workers->idle_worker_count > 1) {
+                apr_thread_cond_signal(workers->mplx_added);
+            }
+            status = APR_SUCCESS;
+        }
+        else {
+            status = APR_EOF;
+        }
+        
+        --workers->idle_worker_count;
+        apr_thread_mutex_unlock(workers->lock);
+    }
+    
+    return status;
+}
+
+static void worker_done(h2_worker *worker, void *ctx)
+{
+    h2_workers *workers = (h2_workers *)ctx;
+    apr_status_t status = apr_thread_mutex_lock(workers->lock);
+    if (status == APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+                     "h2_worker(%d): done", h2_worker_get_id(worker));
+        H2_WORKER_REMOVE(worker);
+        --workers->worker_count;
+        h2_worker_destroy(worker);
+        
+        apr_thread_mutex_unlock(workers->lock);
+    }
+}
+
+
+static apr_status_t add_worker(h2_workers *workers)
+{
+    h2_worker *w = h2_worker_create(workers->next_worker_id++,
+                                    workers->pool, workers->thread_attr,
+                                    get_mplx_next, worker_done, workers);
+    if (!w) {
+        return APR_ENOMEM;
+    }
+    ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
+                 "h2_workers: adding worker(%d)", h2_worker_get_id(w));
+    ++workers->worker_count;
+    H2_WORKER_LIST_INSERT_TAIL(&workers->workers, w);
+    return APR_SUCCESS;
+}
+
+static apr_status_t h2_workers_start(h2_workers *workers) {
+    apr_status_t status = apr_thread_mutex_lock(workers->lock);
+    if (status == APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+                      "h2_workers: starting");
+
+        while (workers->worker_count < workers->min_size
+               && status == APR_SUCCESS) {
+            status = add_worker(workers);
+        }
+        apr_thread_mutex_unlock(workers->lock);
+    }
+    return status;
+}
+
+h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
+                              int min_size, int max_size)
+{
+    AP_DEBUG_ASSERT(s);
+    AP_DEBUG_ASSERT(pool);
+    apr_status_t status = APR_SUCCESS;
+
+    h2_workers *workers = apr_pcalloc(pool, sizeof(h2_workers));
+    if (workers) {
+        workers->s = s;
+        workers->pool = pool;
+        workers->min_size = min_size;
+        workers->max_size = max_size;
+        apr_atomic_set32(&workers->max_idle_secs, 10);
+        
+        apr_threadattr_create(&workers->thread_attr, workers->pool);
+        
+        APR_RING_INIT(&workers->workers, h2_worker, link);
+        APR_RING_INIT(&workers->mplxs, h2_mplx, link);
+        
+        status = apr_thread_mutex_create(&workers->lock,
+                                         APR_THREAD_MUTEX_DEFAULT,
+                                         workers->pool);
+        if (status == APR_SUCCESS) {
+            status = apr_thread_cond_create(&workers->mplx_added, workers->pool);
+        }
+        
+        if (status == APR_SUCCESS) {
+            status = h2_workers_start(workers);
+        }
+        
+        if (status != APR_SUCCESS) {
+            h2_workers_destroy(workers);
+            workers = NULL;
+        }
+    }
+    return workers;
+}
+
+void h2_workers_destroy(h2_workers *workers)
+{
+    if (workers->mplx_added) {
+        apr_thread_cond_destroy(workers->mplx_added);
+        workers->mplx_added = NULL;
+    }
+    if (workers->lock) {
+        apr_thread_mutex_destroy(workers->lock);
+        workers->lock = NULL;
+    }
+    while (!H2_MPLX_LIST_EMPTY(&workers->mplxs)) {
+        h2_mplx *m = H2_MPLX_LIST_FIRST(&workers->mplxs);
+        H2_MPLX_REMOVE(m);
+    }
+    while (!H2_WORKER_LIST_EMPTY(&workers->workers)) {
+        h2_worker *w = H2_WORKER_LIST_FIRST(&workers->workers);
+        H2_WORKER_REMOVE(w);
+    }
+}
+
+apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
+{
+    apr_status_t status = apr_thread_mutex_lock(workers->lock);
+    if (status == APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, status, workers->s,
+                     "h2_workers: register mplx(%ld)", m->id);
+        if (in_list(workers, m)) {
+            status = APR_EAGAIN;
+        }
+        else {
+            H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
+            status = APR_SUCCESS;
+        }
+        
+        if (workers->idle_worker_count > 0) { 
+            apr_thread_cond_signal(workers->mplx_added);
+        }
+        else if (workers->worker_count < workers->max_size) {
+            ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+                         "h2_workers: got %d worker, adding 1", 
+                         workers->worker_count);
+            add_worker(workers);
+        }
+        
+        apr_thread_mutex_unlock(workers->lock);
+    }
+    return status;
+}
+
+apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
+{
+    apr_status_t status = apr_thread_mutex_lock(workers->lock);
+    if (status == APR_SUCCESS) {
+        status = APR_EAGAIN;
+        if (in_list(workers, m)) {
+            H2_MPLX_REMOVE(m);
+            status = APR_SUCCESS;
+        }
+        apr_thread_mutex_unlock(workers->lock);
+    }
+    return status;
+}
+
+void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs)
+{
+    if (idle_secs <= 0) {
+        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
+                     "h2_workers: max_worker_idle_sec value of %d"
+                     " is not valid, ignored.", idle_secs);
+        return;
+    }
+    apr_atomic_set32(&workers->max_idle_secs, idle_secs);
+}
+

Added: httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.h?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.h (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/h2_workers.h Tue Jun 30 15:26:16 2015
@@ -0,0 +1,87 @@
+/* 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_workers__
+#define __mod_h2__h2_workers__
+
+/* Thread pool specific to executing h2_tasks. Has a minimum and maximum 
+ * number of workers it creates. Starts with minimum workers and adds
+ * some on load, reduces the number again when idle.
+ *
+ */
+struct apr_thread_mutex_t;
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_task;
+struct h2_task_queue;
+
+typedef struct h2_workers h2_workers;
+
+struct h2_workers {
+    server_rec *s;
+    apr_pool_t *pool;
+    int aborted;
+    
+    int next_worker_id;
+    int min_size;
+    int max_size;
+    
+    apr_threadattr_t *thread_attr;
+    
+    APR_RING_HEAD(h2_worker_list, h2_worker) workers;
+    APR_RING_HEAD(h2_mplx_list, h2_mplx) mplxs;
+    
+    int worker_count;
+    volatile apr_uint32_t max_idle_secs;
+    volatile apr_uint32_t idle_worker_count;
+    
+    struct apr_thread_mutex_t *lock;
+    struct apr_thread_cond_t *mplx_added;
+};
+
+
+/* Create a worker pool with the given minimum and maximum number of
+ * threads.
+ */
+h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
+                              int min_size, int max_size);
+
+/* Destroy the worker pool and all its threads. 
+ */
+void h2_workers_destroy(h2_workers *workers);
+
+/**
+ * Registers a h2_mplx for task scheduling. If this h2_mplx runs
+ * out of tasks, it will be automatically be unregistered. Should
+ * new tasks arrive, it needs to be registered again.
+ */
+apr_status_t h2_workers_register(h2_workers *workers, 
+                                 struct h2_mplx *m);
+
+/**
+ * Remove a h2_mplx from the worker registry.
+ */
+apr_status_t h2_workers_unregister(h2_workers *workers, 
+                                   struct h2_mplx *m);
+
+/**
+ * Set the amount of seconds a h2_worker should wait for new tasks
+ * before shutting down (if there are more than the minimum number of
+ * workers).
+ */
+void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs);
+
+#endif /* defined(__mod_h2__h2_workers__) */

Added: httpd/httpd/trunk/modules/http2/mod_h2/m4/h2.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/m4/h2.m4?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/m4/h2.m4 (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/m4/h2.m4 Tue Jun 30 15:26:16 2015
@@ -0,0 +1 @@
+# just so it is not empty

Added: httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.c?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.c (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.c Tue Jun 30 15:26:16 2015
@@ -0,0 +1,162 @@
+/* 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 <apr_optional.h>
+#include <apr_optional_hooks.h>
+#include <apr_want.h>
+
+#include <httpd.h>
+#include <http_log.h>
+
+#include "mod_h2.h"
+
+#include <nghttp2/nghttp2.h>
+#include "h2_stream.h"
+#include "h2_alt_svc.h"
+#include "h2_conn.h"
+#include "h2_task.h"
+#include "h2_session.h"
+#include "h2_config.h"
+#include "h2_ctx.h"
+#include "h2_h2.h"
+#include "h2_alpn.h"
+#include "h2_upgrade.h"
+#include "h2_version.h"
+
+
+static void h2_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(h2) = {
+    STANDARD20_MODULE_STUFF,
+    NULL,
+    NULL,
+    h2_config_create_svr, /* func to create per server config */
+    h2_config_merge,      /* func to merge per server config */
+    h2_cmds,              /* command handlers */
+    h2_hooks
+};
+
+/* The module initialization. Called once as apache hook, before any multi
+ * processing (threaded or not) happens. It is typically at least called twice, 
+ * see
+ * http://wiki.apache.org/httpd/ModuleLife
+ * Since the first run is just a "practise" run, we want to initialize for real
+ * only on the second try. This defeats the purpose of the first dry run a bit, 
+ * since apache wants to verify that a new configuration actually will work. 
+ * So if we have trouble with the configuration, this will only be detected 
+ * when the server has already switched.
+ * On the other hand, when we initialize lib nghttp2, all possible crazy things 
+ * might happen and this might even eat threads. So, better init on the real 
+ * invocation, for now at least.
+ */
+static int h2_post_config(apr_pool_t *p, apr_pool_t *plog,
+                          apr_pool_t *ptemp, server_rec *s)
+{
+    void *data = NULL;
+    const char *mod_h2_init_key = "mod_h2_init_counter";
+    nghttp2_info *ngh2;
+    apr_status_t status;
+    (void)plog;(void)ptemp;
+    
+    apr_pool_userdata_get(&data, mod_h2_init_key, s->process->pool);
+    if ( data == NULL ) {
+        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s,
+                     "initializing post config dry run");
+        apr_pool_userdata_set((const void *)1, mod_h2_init_key,
+                              apr_pool_cleanup_null, s->process->pool);
+        return APR_SUCCESS;
+    }
+    
+    ngh2 = nghttp2_version(0);
+    ap_log_error( APLOG_MARK, APLOG_INFO, 0, s,
+                 "mod_h2 (v%s, nghttp2 %s), initializing...",
+                 MOD_H2_VERSION, ngh2? ngh2->version_str : "unknown");
+    
+    switch (h2_conn_mpm_type()) {
+        case H2_MPM_EVENT:
+        case H2_MPM_WORKER:
+            /* all fine, we know these ones */
+            break;
+        case H2_MPM_PREFORK:
+            ap_log_error( APLOG_MARK, APLOG_WARNING, 0, s,
+                         "This httpd uses mpm_prefork for multiprocessing. "
+                         "Please take notice that mod_h2 always with run "
+                         "requests in a multi-threaded environment. If you "
+                         "use prefork for single-thread connection handling, "
+                         " mod_h2 might pose problems.");
+            break;
+        case H2_MPM_UNKNOWN:
+            /* ??? */
+            ap_log_error( APLOG_MARK, APLOG_ERR, 0, s,
+                         "post_config: mpm type unknown");
+            break;
+    }
+    
+    status = h2_h2_init(p, s);
+    if (status == APR_SUCCESS) {
+        status = h2_alpn_init(p, s);
+    }
+    
+    return status;
+}
+
+/* Runs once per created child process. Perform any process 
+ * related initionalization here.
+ */
+static void h2_child_init(apr_pool_t *pool, server_rec *s)
+{
+    /* Set up our connection processing */
+    apr_status_t status = h2_conn_child_init(pool, s);
+    if (status != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
+                      "initializing connection handling");
+    }
+}
+
+const char *h2_get_protocol(conn_rec *c)
+{
+    return h2_ctx_pnego_get(h2_ctx_get(c));
+}
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void h2_hooks(apr_pool_t *pool)
+{
+    ap_log_perror(APLOG_MARK, APLOG_INFO, 0, pool, "installing hooks");
+    
+    static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
+    
+    /* Run once after configuration is set, but before mpm children initialize.
+     */
+    ap_hook_post_config(h2_post_config, mod_ssl, NULL, APR_HOOK_MIDDLE);
+    
+    /* Run once after a child process has been created.
+     */
+    ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+
+    h2_h2_register_hooks();
+    h2_alpn_register_hooks();
+    h2_upgrade_register_hooks();
+    h2_task_register_hooks();
+
+    h2_alt_svc_register_hooks();
+    
+    /* We offer a function to other modules that lets them retrieve
+     * the h2 protocol used on a connection (if any).
+     */
+    APR_REGISTER_OPTIONAL_FN(h2_get_protocol);
+}
+
+

Added: httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.h?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.h (added)
+++ httpd/httpd/trunk/modules/http2/mod_h2/mod_h2.h Tue Jun 30 15:26:16 2015
@@ -0,0 +1,28 @@
+/* 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_mod_h2_h
+#define mod_h2_mod_h2_h
+
+const char *h2_get_protocol(conn_rec *c);
+
+
+/** 
+ * An optional function which returns the h2 protocol used on the given
+ * connection and NULL if no h2* protocol is active on it.
+ */
+APR_DECLARE_OPTIONAL_FN(const char *, h2_get_protocol, (conn_rec*));
+
+#endif

Added: httpd/httpd/trunk/modules/http2/sandbox/.gitignore
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/.gitignore?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/.gitignore (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/.gitignore Tue Jun 30 15:26:16 2015
@@ -0,0 +1,2 @@
+install
+gen

Added: httpd/httpd/trunk/modules/http2/sandbox/Makefile.am
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/Makefile.am?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/Makefile.am (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/Makefile.am Tue Jun 30 15:26:16 2015
@@ -0,0 +1,58 @@
+# 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.
+#
+SUBDIRS         = httpd nghttp2 test
+DIST_SUBDIRS    = httpd nghttp2 test
+
+INSTALL_DIR     = install
+
+EXTRA_DIST = \
+    httpd/Makefile \
+    httpd/*.sh \
+    httpd/patches/* \
+    httpd/packages/* \
+    nghttp2/Makefile \
+    test/Makefile \
+    test/*.sh \
+    test/*.txt \
+    test/htdocs \
+    test/conf \
+    test/clients/Makefile
+
+
+.PHONY: test loadtest start stop restart
+
+start:
+	make -C test start
+
+restart:
+	make -C test restart
+
+stop:
+	make -C test stop
+
+test:
+	make -C test test
+
+loadtest:
+	make -C test loadtest
+
+mpm_worker:
+	make -C test mpm_worker
+
+mpm_event:
+	make -C test mpm_event
+
+mpm_prefork:
+	make -C test mpm_prefork

Added: httpd/httpd/trunk/modules/http2/sandbox/httpd/.gitignore
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/httpd/.gitignore?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/httpd/.gitignore (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/httpd/.gitignore Tue Jun 30 15:26:16 2015
@@ -0,0 +1 @@
+gen

Added: httpd/httpd/trunk/modules/http2/sandbox/httpd/Makefile
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/httpd/Makefile?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/httpd/Makefile (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/httpd/Makefile Tue Jun 30 15:26:16 2015
@@ -0,0 +1,319 @@
+# 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.
+#
+
+
+OS           = $(shell uname -s)
+MACHINE      = $(shell uname -m)
+
+
+APR_VERSION  = 1.5.2
+APR_DIR      = apr-$(APR_VERSION)
+APR_TAR      = $(APR_DIR).tar.gz
+APR_URL      = http://archive.apache.org/dist/apr/$(APR_TAR)
+
+APR-UTIL_VERSION = 1.5.4
+APR-UTIL_DIR = apr-util-$(APR-UTIL_VERSION)
+APR-UTIL_TAR = $(APR-UTIL_DIR).tar.gz
+APR-UTIL_URL = http://archive.apache.org/dist/apr/$(APR-UTIL_TAR)
+
+HTTPD_VERSION = 2.4.12
+HTTPD_DIR    = httpd-$(HTTPD_VERSION)
+HTTPD_TAR    = httpd-$(HTTPD_VERSION).tar.gz
+HTTPD_URL    = https://archive.apache.org/dist/httpd/$(HTTPD_TAR)
+
+HTTPD_CONF   = --enable-mpms-shared=all --with-included-apr
+HTTPD_DEPS   = $(GEN)/$(HTTPD_DIR)/.apr-extracted
+
+PCRE_VERSION = 8.36
+PCRE_DIR     = pcre-$(PCRE_VERSION)
+PCRE_TAR     = $(PCRE_DIR).tar.gz
+PCRE_URL     = http://sourceforge.net/projects/pcre/files/pcre/$(PCRE_VERSION)/$(PCRE_TAR)
+
+OPENSSL_BASE = http://www.openssl.org
+OPENSSL_DIR = openssl-latest
+OPENSSL_TAR = $(OPENSSL_DIR).tar.gz
+OPENSSL_URL = $(OPENSSL_BASE)/source/latest.tar.gz
+OPENSSL_CONF_CMD = ./config
+OPENSSL_CONF =  shared -fPIC
+
+FCGI_VERSION = 2.3.9
+FCGI_DIR     = mod_fcgid-$(FCGI_VERSION)
+FCGI_TAR     = $(FCGI_DIR).tar.gz
+FCGI_URL     = https://archive.apache.org/dist/httpd/mod_fcgid/$(FCGI_TAR)
+
+GEN          = gen
+INST_DIR     = ../install
+BLD_PREFIX   = $(shell dirname $$PWD)/install
+
+CURL_OPTS    = --progress-bar
+
+# For OS X, we drag our own PCRE lib in as the one installed is not
+# recognized by the httpd configure (and pbly rightly so).
+# On other OS, we expect a proper pcre installation
+#
+ifeq ($(OS),Darwin)
+	# we need our own apr + apr-util
+	HTTPD_CONF  += --with-crypto
+	# we need our own pcre
+	HTTPD_DEPS  += $(INST_DIR)/.pcre-installed
+	HTTPD_CONF  += --with-pcre=$(BLD_PREFIX)
+endif
+
+OPENSSL_VERSION = $(shell openssl version -v | sed -e 's/OpenSSL *//g' -e 's/[a-z]* .*//g')
+
+ifeq ($(OPENSSL_VERSION), $(filter $(OPENSSL_VERSION),0.9.7 0.9.8 1.0.0 1.0.1))
+	# Very old openssl without alpn support installed, need a newer one
+	HTTPD_DEPS  += $(INST_DIR)/.openssl-installed
+    HTTPD_CONF  += --enable-ssl --with-ssl=$(BLD_PREFIX)
+	ifeq ($(OS),Darwin)
+        OPENSSL_CONF_CMD = ./Configure
+        OPENSSL_CONF = darwin64-x86_64-cc
+        HTTPD_CONF  += --enable-ssl-staticlib-deps
+	endif
+endif
+
+HTTP_PORT = 12345
+HTTPS_PORT = 12346
+
+all: install
+
+distdir:
+
+dirs:
+	@mkdir -p $(GEN)/build
+
+clean:
+	@rm -rf $(GEN)/$(PCRE_DIR) $(GEN)/$(OPENSSL_DIR) $(GEN)/$(HTTPD_DIR) \
+        $(GEN)/build $(INST_DIR)/.httpd-installed
+
+distclean:
+	@rm -rf $(GEN)
+
+install: \
+    $(INST_DIR)/.httpd-installed \
+    $(INST_DIR)/.modules-installed
+
+setup: $(INST_DIR)/.httpd-setup
+
+copy-mod_ssl:
+	@cp mod_ssl-alpn/*.[ch] $(GEN)/$(HTTPD_DIR)/modules/ssl
+	@rm -f $(GEN)/$(HTTPD_DIR)/.httpd-built
+
+################################################################################
+# Install the local httpd
+#
+$(INST_DIR)/.httpd-installed:  $(GEN)/$(HTTPD_DIR)/.httpd-built
+	@mkdir -p $(INST_DIR)
+	@echo -n installing httpd locally...
+	@cd $(GEN)/$(HTTPD_DIR)/ && make install
+	@echo done.
+	@touch $(INST_DIR)/.httpd-installed
+
+
+################################################################################
+# Build the local, patched httpd
+#
+$(GEN)/$(HTTPD_DIR)/.httpd-built: \
+		$(GEN)/$(HTTPD_DIR)/.httpd-configured
+	@echo -n building httpd...
+	@cd $(GEN)/$(HTTPD_DIR)/ && make
+	@echo done.
+	@touch $(GEN)/$(HTTPD_DIR)/.httpd-built
+
+################################################################################
+# Configure the local httpd sources
+#
+$(GEN)/$(HTTPD_DIR)/configure: 
+	cd $(GEN)/$(HTTPD_DIR)/ && ./buildconf
+
+$(GEN)/$(HTTPD_DIR)/.httpd-configured: \
+		$(HTTPD_DEPS) \
+		$(GEN)/$(HTTPD_DIR)/configure \
+		$(GEN)/$(HTTPD_DIR)/.httpd-patched
+	@echo -n configuring httpd...
+	cd $(GEN)/$(HTTPD_DIR)/ && \
+	./configure --prefix=$(BLD_PREFIX) $(HTTPD_CONF)
+	@echo done.
+	@touch $(GEN)/$(HTTPD_DIR)/.httpd-configured
+
+################################################################################
+# Patch the local httpd sources
+#
+$(GEN)/$(HTTPD_DIR)/.httpd-patched: \
+		$(GEN)/$(HTTPD_DIR)/.httpd-extracted
+	@echo applying patches...
+	@cd gen/$(HTTPD_DIR) && patch -p0 < ../../patches/httpd-2.4.12-alpn-v5.patch
+	@cd gen/$(HTTPD_DIR) && patch -p0 < ../../patches/sni_misdirect.patch
+	@echo httpd patched.
+	@touch $(GEN)/$(HTTPD_DIR)/.httpd-patched
+#@cd gen/$(HTTPD_DIR) && patch -p0 < ../../patches/alpn-2.4.x.unified.diff.patch
+
+################################################################################
+# Extract apache source tree
+#
+$(GEN)/$(HTTPD_DIR)/.httpd-extracted: \
+		$(GEN)/$(HTTPD_TAR)
+	@rm -rf $(GEN)/$(HTTPD_DIR)
+	@echo -n extracting httpd packages...
+	@cd $(GEN) && tar xfz $(HTTPD_TAR)
+	@echo done.
+	@touch $(GEN)/$(HTTPD_DIR)/.httpd-extracted
+
+################################################################################
+# Extract apr + apr-util into apache source tree (if needed)
+#
+$(GEN)/$(HTTPD_DIR)/.apr-extracted: \
+		$(GEN)/$(HTTPD_DIR)/.httpd-extracted \
+		$(GEN)/$(APR_TAR) $(GEN)/$(APR-UTIL_TAR)
+	@rm -rf $(GEN)/$(HTTPD_DIR)/srclib/apr $(GEN)/$(HTTPD_DIR)/srclib/apr-util
+	@echo -n extracting apr packages...
+	@cd gen/$(HTTPD_DIR)/srclib; tar xfz ../../$(APR_TAR) && mv $(APR_DIR) apr
+	@cd gen/$(HTTPD_DIR)/srclib; tar xfz ../../$(APR-UTIL_TAR) && mv $(APR-UTIL_DIR) apr-util
+	@echo done.
+	@touch $(GEN)/$(HTTPD_DIR)/.apr-extracted
+
+################################################################################
+# Retrieve apache sources (and apr + apr-util if needed)
+#
+$(GEN)/$(HTTPD_TAR):
+	@mkdir -p $(GEN)
+	curl $(CURL_OPTS) $(HTTPD_URL) > $(GEN)/$(HTTPD_TAR)
+
+$(GEN)/$(APR_TAR):
+	@mkdir -p $(GEN)
+	curl $(CURL_OPTS) $(APR_URL) > $(GEN)/$(APR_TAR)
+
+$(GEN)/$(APR-UTIL_TAR):
+	@mkdir -p $(GEN)
+	curl $(CURL_OPTS) $(APR-UTIL_URL) > $(GEN)/$(APR-UTIL_TAR)
+
+################################################################################
+# Build + install additional modules
+#
+$(INST_DIR)/.modules-installed: \
+        $(INST_DIR)/.httpd-installed \
+        $(INST_DIR)/.mod_fcgid-installed
+	@touch $(INST_DIR)/.modules-installed
+
+################################################################################
+# Build + install mod_fcgid
+#
+$(INST_DIR)/.mod_fcgid-installed: \
+        $(INST_DIR)/.httpd-installed \
+        $(GEN)/$(FCGI_DIR)/.mod_fcgid-built
+	@echo -n installing mod_fcgid locally...
+	@cd $(GEN)/$(FCGI_DIR) && make install
+	@echo done.
+	@touch $(INST_DIR)/.mod_fcgid-installed
+
+$(GEN)/$(FCGI_DIR)/.mod_fcgid-built: \
+        $(GEN)/$(FCGI_DIR)/.mod_fcgid-configured
+	@echo -n building mod_fcgid...
+	@cd $(GEN)/$(FCGI_DIR) && make
+	@echo done.
+	@touch $(GEN)/$(FCGI_DIR)/.mod_fcgid-built
+
+
+$(GEN)/$(FCGI_DIR)/.mod_fcgid-configured: \
+        $(GEN)/$(FCGI_DIR)/.mod_fcgid-extracted
+	@echo -n configuring mod_fcgid...
+	@cd $(GEN)/$(FCGI_DIR) &&  APXS=../../$(INST_DIR)/bin/apxs ./configure.apxs
+	@echo done.
+	@touch $(GEN)/$(FCGI_DIR)/.mod_fcgid-configured
+
+$(GEN)/$(FCGI_DIR)/.mod_fcgid-extracted: \
+        $(GEN)/$(FCGI_TAR)
+	@rm -rf $(GEN)/$(FCGI_DIR)
+	@echo -n downloading and extracting mod_fcgid...
+	@cd gen; tar xfz $(FCGI_TAR)
+	@echo done.
+	@touch $(GEN)/$(FCGI_DIR)/.mod_fcgid-extracted
+
+$(GEN)/$(FCGI_TAR):
+	@mkdir -p $(GEN)
+	curl $(CURL_OPTS) $(FCGI_URL) > $(GEN)/$(FCGI_TAR)
+
+
+################################################################################
+# Build + install a local opensll library (if needed)
+#
+$(INST_DIR)/.openssl-installed: \
+		$(GEN)/$(OPENSSL_DIR)/.built
+	@mkdir -p $(INST_DIR)
+	@echo -n installing openssl locally, may take some time...
+	@cd $(GEN)/$(OPENSSL_DIR) && make install_sw >> ../build.log
+	@echo done.
+	@touch $(INST_DIR)/.openssl-installed
+
+$(GEN)/$(OPENSSL_DIR)/.built: \
+		$(GEN)/$(OPENSSL_DIR)/.configured
+	@echo -n building openssl locally...
+	@cd $(GEN)/$(OPENSSL_DIR) && make
+	@echo done.
+	@touch $(GEN)/$(OPENSSL_DIR)/.built
+
+$(GEN)/$(OPENSSL_DIR)/.configured: \
+        $(GEN)/$(OPENSSL_DIR)/.patched
+	@echo -n configuring openssl...
+	cd $(GEN)/$(OPENSSL_DIR) && $(OPENSSL_CONF_CMD) --openssldir=$(BLD_PREFIX) $(OPENSSL_CONF)
+	@echo done.
+	@touch $(GEN)/$(OPENSSL_DIR)/.configured
+
+$(GEN)/$(OPENSSL_DIR)/.patched: \
+		$(GEN)/$(OPENSSL_DIR)/.extracted
+	@echo applying patches...
+	# experimental patch to solve SNI+ALPN callback ordering in openssl
+	# not vital with latest httpd-alpn patch. We leave this out of our sandbox
+	# to have it running against an unpatched ssl for better test coverage.
+	#@cd gen/$(OPENSSL_DIR) && patch -p1 < ../../patches/openssl-1.0.2-alpn.patch
+	@echo openssl patched.
+	@touch $(GEN)/$(OPENSSL_DIR)/.patched
+
+$(GEN)/$(OPENSSL_DIR)/.extracted:
+	@echo -n downloading and extracting openssl...
+	@mkdir -p $(GEN)
+	@bash get-openssl-latest.sh $(OPENSSL_URL) $(GEN)/$(OPENSSL_DIR)
+	@echo done.
+	@touch $(GEN)/$(OPENSSL_DIR)/.extracted
+
+################################################################################
+# Build + install a local pcre library (if needed)
+#
+$(INST_DIR)/.pcre-installed: \
+		$(GEN)/$(PCRE_DIR)/.pcre-built
+	@mkdir -p $(INST_DIR)
+	@echo -n installing pcre locally...
+	@cd $(GEN)/$(PCRE_DIR) && make install >> ../build.log
+	@echo done.
+	@touch $(INST_DIR)/.pcre-installed
+
+$(GEN)/$(PCRE_DIR)/.pcre-built: \
+		$(GEN)/$(PCRE_DIR)/.pcre-configured
+	@echo -n building pcre locally...
+	@cd $(GEN)/$(PCRE_DIR) && make >> ../build.log
+	@echo done.
+	@touch $(GEN)/$(PCRE_DIR)/.pcre-built
+
+$(GEN)/$(PCRE_DIR)/.pcre-configured: $(GEN)/$(PCRE_TAR)
+	@rm -rf $(GEN)/$(PCRE_DIR)
+	@cd $(GEN) && tar xfz $(PCRE_TAR)
+	@echo -n configuring pcre...
+	@cd $(GEN)/$(PCRE_DIR) && ./configure --prefix=$(BLD_PREFIX) >> ../build.log
+	@echo done.
+	@touch $(GEN)/$(PCRE_DIR)/.pcre-configured
+
+$(GEN)/$(PCRE_TAR): packages/$(PCRE_TAR)
+	@mkdir -p $(GEN)
+	@cp packages/$(PCRE_TAR) $(GEN)

Added: httpd/httpd/trunk/modules/http2/sandbox/httpd/get-openssl-latest.sh
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/httpd/get-openssl-latest.sh?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/httpd/get-openssl-latest.sh (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/httpd/get-openssl-latest.sh Tue Jun 30 15:26:16 2015
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+#  get-openssl-latest.sh
+#  mod-h2
+#
+#  Created by Stefan Eissing on 24.03.15.
+#  Inspects the given URL for redirects and downloads tar file
+#  to real name. Symlinks given tar name to real name
+
+URL=$1
+DESTDIR=$2
+
+usage() {
+    echo "$@"
+    echo "usage: $(basename $0) url dir"
+    echo "  download and extract latest openssl url to given directory,"
+    echo "  using real version name and symlinks"
+    exit 2
+}
+
+fail() {
+    echo "$@"
+    exit 1
+}
+
+[ -z "$URL" ] && usage "url parameter missing"
+[ -z "$DESTDIR" ] && usage "dir parameter missing"
+
+GEN=$(dirname "$DESTDIR")
+[ -d "$GEN" ] || fail "destination dir $GEN does not exist"
+
+curl -s -D "$GEN"/xxx-header $URL > "$GEN"/xxx-content || fail "error downloading $URL"
+REAL_URL=$( fgrep -i location: < "$GEN"/xxx-header | sed s',.*: ,,' | tr -d '\r\n' )
+
+case "$REAL_URL" in
+    */var/www/*)
+        # currently openssl returns the wrong path - yet the correct tar name
+        REAL_TAR=$(basename $REAL_URL)
+        REAL_URL=$(dirname $URL)/$REAL_TAR
+        ;;
+    *)
+        REAL_TAR=$(basename $REAL_URL)
+        ;;
+esac
+
+echo "downloading latest openssl from $REAL_URL"
+
+REAL_DIR=$(basename $REAL_TAR .tar.gz)
+rm -f "$GEN/$REAL_TAR" "$DESTDIR" "$GEN"/xxx-header "$GEN"/xxx-content
+
+curl -'#' "$REAL_URL" > "$GEN/$REAL_TAR" || fail "error downloading $REAL_URL"
+(cd "$GEN" && tar xfz "$REAL_TAR") || fail "error extracting $GEN/$REAL_TAR"
+[ -d "$GEN/$REAL_DIR" ] || fail "expected directory $GEN/$REAL_DIR"
+(cd $GEN && ln -s "$REAL_DIR" $(basename $DESTDIR))
+

Propchange: httpd/httpd/trunk/modules/http2/sandbox/httpd/get-openssl-latest.sh
------------------------------------------------------------------------------
    svn:executable = *

Added: httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile Tue Jun 30 15:26:16 2015
@@ -0,0 +1,25 @@
+top_srcdir   = /Users/sei/projects/mod-h2/httpd/gen/httpd-2.4.10
+top_builddir = /Users/sei/projects/mod-h2/httpd/gen/httpd-2.4.10
+srcdir       = /Users/sei/projects/mod-h2/httpd/gen/httpd-2.4.10/modules/ssl
+builddir     = /Users/sei/projects/mod-h2/httpd/gen/httpd-2.4.10/modules/ssl
+VPATH        = /Users/sei/projects/mod-h2/httpd/gen/httpd-2.4.10/modules/ssl
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     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.
+
+#
+#   standard stuff
+#
+
+include $(top_srcdir)/build/special.mk

Added: httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile.in?rev=1688474&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile.in (added)
+++ httpd/httpd/trunk/modules/http2/sandbox/httpd/mod_ssl-alpn/Makefile.in Tue Jun 30 15:26:16 2015
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     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.
+
+#
+#   standard stuff
+#
+
+include $(top_srcdir)/build/special.mk