Commit c423abb8 authored by Stefan Eissing's avatar Stefan Eissing
Browse files

Merge of 1764243,1765318 from trunk:

mod_http2/mod_proxy_http2: 100-continue implementation, PING checks on aged backend connections


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1765327 13f79535-47bb-0310-9956-ffa450edef68
parent 37bed268
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -2,6 +2,13 @@

Changes with Apache 2.4.24

  *) mod_http2/mod_proxy_http2: 100-continue handling now properly implemented
     up to the backend. Reused HTTP/2 proxy connections with more than a second
     not used will block request bodies until a PING answer is received.
     Requests headers are not delayed by this, since they are repeatable in
     case of failure. This greatly increases robustness, especially with
     busy server and/or low keepalive connections. [Stefan Eissing]
     
  *) mod_proxy_http2: fixed duplicate symbols with mod_http2.
     [Stefan Eissing]
  
+2 −0
Original line number Diff line number Diff line
@@ -129,6 +129,8 @@ struct h2_request {
    
    unsigned int chunked : 1;   /* iff requst body needs to be forwarded as chunked */
    unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
    unsigned int expect_100 : 1; /* iff we need a 100-continue response */
    unsigned int expect_failed : 1; /* iff we are unable to fullfill expects */
};

typedef struct h2_headers h2_headers;
+284 −64
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <assert.h>
#include <stdio.h>

#include <apr_date.h>
#include <apr_lib.h>
#include <apr_strings.h>

@@ -296,6 +297,209 @@ static h2_headers *create_response(h2_task *task, request_rec *r)
    return h2_headers_rcreate(r, r->status, headers, r->pool);
}

typedef enum {
    H2_RP_STATUS_LINE,
    H2_RP_HEADER_LINE,
    H2_RP_DONE
} h2_rp_state_t;

typedef struct h2_response_parser {
    h2_rp_state_t state;
    h2_task *task;
    int http_status;
    apr_array_header_t *hlines;
    apr_bucket_brigade *tmp;
} h2_response_parser;

static apr_status_t parse_header(h2_response_parser *parser, char *line) {
    const char *hline;
    if (line[0] == ' ' || line[0] == '\t') {
        char **plast;
        /* continuation line from the header before this */
        while (line[0] == ' ' || line[0] == '\t') {
            ++line;
        }
        
        plast = apr_array_pop(parser->hlines);
        if (plast == NULL) {
            /* not well formed */
            return APR_EINVAL;
        }
        hline = apr_psprintf(parser->task->pool, "%s %s", *plast, line);
    }
    else {
        /* new header line */
        hline = apr_pstrdup(parser->task->pool, line);
    }
    APR_ARRAY_PUSH(parser->hlines, const char*) = hline; 
    return APR_SUCCESS;
}

static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb, 
                             char *line, apr_size_t len)
{
    h2_task *task = parser->task;
    apr_status_t status;
    
    if (!parser->tmp) {
        parser->tmp = apr_brigade_create(task->pool, task->c->bucket_alloc);
    }
    status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ, 
                                    HUGE_STRING_LEN);
    if (status == APR_SUCCESS) {
        --len;
        status = apr_brigade_flatten(parser->tmp, line, &len);
        if (status == APR_SUCCESS) {
            /* we assume a non-0 containing line and remove trailing crlf. */
            line[len] = '\0';
            if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
                len -= 2;
                line[len] = '\0';
                apr_brigade_cleanup(parser->tmp);
                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
                              "h2_task(%s): read response line: %s", 
                              task->id, line);
            }
            else {
                /* this does not look like a complete line yet */
                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
                              "h2_task(%s): read response, incomplete line: %s", 
                              task->id, line);
                return APR_EAGAIN;
            }
        }
    }
    apr_brigade_cleanup(parser->tmp);
    return status;
}

static apr_table_t *make_table(h2_response_parser *parser)
{
    h2_task *task = parser->task;
    apr_array_header_t *hlines = parser->hlines;
    if (hlines) {
        apr_table_t *headers = apr_table_make(task->pool, hlines->nelts);        
        int i;
        
        for (i = 0; i < hlines->nelts; ++i) {
            char *hline = ((char **)hlines->elts)[i];
            char *sep = ap_strchr(hline, ':');
            if (!sep) {
                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, task->c,
                              APLOGNO(02955) "h2_task(%s): invalid header[%d] '%s'",
                              task->id, i, (char*)hline);
                /* not valid format, abort */
                return NULL;
            }
            (*sep++) = '\0';
            while (*sep == ' ' || *sep == '\t') {
                ++sep;
            }
            
            if (!h2_util_ignore_header(hline)) {
                apr_table_merge(headers, hline, sep);
            }
        }
        return headers;
    }
    else {
        return apr_table_make(task->pool, 0);        
    }
}

static apr_status_t pass_response(h2_task *task, ap_filter_t *f, 
                                  h2_response_parser *parser) 
{
    apr_bucket *b;
    apr_status_t status;
    
    h2_headers *response = h2_headers_create(parser->http_status, 
                                             make_table(parser),
                                             NULL, task->pool);
    apr_brigade_cleanup(parser->tmp);
    b = h2_bucket_headers_create(task->c->bucket_alloc, response);
    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
    b = apr_bucket_flush_create(task->c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);                      
    status = ap_pass_brigade(f->next, parser->tmp);
    apr_brigade_cleanup(parser->tmp);
    
    parser->state = H2_RP_DONE;
    task->output.parse_response = 0;
    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, 
                  APLOGNO(03197) "h2_task(%s): passed response %d", 
                  task->id, response->status);
    return status;
}

static apr_status_t parse_status(h2_task *task, char *line)
{
    h2_response_parser *parser = task->output.rparser;
    int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 : 
                  (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
    if (sindex > 0) {
        int k = sindex + 3;
        char keepchar = line[k];
        line[k] = '\0';
        parser->http_status = atoi(&line[sindex]);
        line[k] = keepchar;
        parser->state = H2_RP_HEADER_LINE;
        
        return APR_SUCCESS;
    }
    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, task->c, APLOGNO(03467)
                  "h2_task(%s): unable to parse status line: %s", 
                  task->id, line);
    return APR_EINVAL;
}

apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f, 
                                       apr_bucket_brigade *bb)
{
    h2_response_parser *parser = task->output.rparser;
    char line[HUGE_STRING_LEN];
    apr_status_t status = APR_SUCCESS;

    if (!parser) {
        parser = apr_pcalloc(task->pool, sizeof(*parser));
        parser->task = task;
        parser->state = H2_RP_STATUS_LINE;
        parser->hlines = apr_array_make(task->pool, 10, sizeof(char *));
        task->output.rparser = parser;
    }
    
    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
        switch (parser->state) {
            case H2_RP_STATUS_LINE:
            case H2_RP_HEADER_LINE:
                status = get_line(parser, bb, line, sizeof(line));
                if (status == APR_EAGAIN) {
                    /* need more data */
                    return APR_SUCCESS;
                }
                else if (status != APR_SUCCESS) {
                    return status;
                }
                if (parser->state == H2_RP_STATUS_LINE) {
                    /* instead of parsing, just take it directly */
                    status = parse_status(task, line);
                }
                else if (line[0] == '\0') {
                    /* end of headers, pass response onward */
                    return pass_response(task, f, parser);
                }
                else {
                    status = parse_header(parser, line);
                }
                break;
                
            default:
                return status;
        }
    }
    return status;
}

apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
{
    h2_task *task = f->ctx;
@@ -354,7 +558,6 @@ apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
            
            bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
            APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
            /*APR_BRIGADE_INSERT_HEAD(bb, bresp);*/
            task->output.sent_response = 1;
            r->sent_bodyct = 1;
        }
@@ -383,7 +586,7 @@ apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
}

static void make_chunk(h2_task *task, apr_bucket_brigade *bb, 
                       apr_bucket *first, apr_uint64_t chunk_len, 
                       apr_bucket *first, apr_off_t chunk_len, 
                       apr_bucket *tail)
{
    /* Surround the buckets [first, tail[ with new buckets carrying the
@@ -394,7 +597,7 @@ static void make_chunk(h2_task *task, apr_bucket_brigade *bb,
    int len;
    
    len = apr_snprintf(buffer, H2_ALEN(buffer), 
                       "%"APR_UINT64_T_HEX_FMT"\r\n", chunk_len);
                       "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
    c = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
    APR_BUCKET_INSERT_BEFORE(first, c);
    c = apr_bucket_heap_create("\r\n", 2, NULL, bb->bucket_alloc);
@@ -404,6 +607,10 @@ static void make_chunk(h2_task *task, apr_bucket_brigade *bb,
    else {
        APR_BRIGADE_INSERT_TAIL(bb, c);
    }
    task->input.chunked_total += chunk_len;
    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
                  "h2_task(%s): added chunk %"APR_OFF_T_FMT", total %"
                  APR_OFF_T_FMT, task->id, chunk_len, task->input.chunked_total);
}

static int ser_header(void *ctx, const char *name, const char *value) 
@@ -413,53 +620,25 @@ static int ser_header(void *ctx, const char *name, const char *value)
    return 1;
}

apr_status_t h2_filter_request_in(ap_filter_t* f,
                                  apr_bucket_brigade* bb,
                                  ap_input_mode_t mode,
                                  apr_read_type_e block,
                                  apr_off_t readbytes)
{
    h2_task *task = f->ctx;
static apr_status_t read_and_chunk(ap_filter_t *f, h2_task *task,
                                   apr_read_type_e block) {
    request_rec *r = f->r;
    apr_status_t status = APR_SUCCESS;
    apr_bucket *b, *next, *first_data = NULL;
    apr_off_t bblen = 0;
    apr_bucket_brigade *bb = task->input.bbchunk;
    
    if (!task->input.chunked) {
        status = ap_get_brigade(f->next, bb, mode, block, readbytes);
        /* pipe data through, just take care of trailers */
        for (b = APR_BRIGADE_FIRST(bb); 
             b != APR_BRIGADE_SENTINEL(bb); b = next) {
            next = APR_BUCKET_NEXT(b);
            if (H2_BUCKET_IS_HEADERS(b)) {
                h2_headers *headers = h2_bucket_headers_get(b);
                ap_assert(headers);
                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                              "h2_task(%s): receiving trailers", task->id);
                r->trailers_in = apr_table_clone(r->pool, headers->headers);
                APR_BUCKET_REMOVE(b);
                apr_bucket_destroy(b);
                ap_remove_input_filter(f);
                break;
            }
        }
        return status;
    if (!bb) {
        bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
        task->input.bbchunk = bb;
    }
    
    /* Things are more complicated. The standard HTTP input filter, which
     * does a lot what we do not want to duplicate, also cares about chunked
     * transfer encoding and trailers.
     * We need to simulate chunked encoding for it to be happy.
     */
    if (APR_BRIGADE_EMPTY(bb)) {
        apr_bucket *b, *next, *first_data = NULL;
        apr_bucket_brigade *tmp;
        apr_off_t bblen = 0;

    if (!task->input.bbchunk) {
        task->input.bbchunk = apr_brigade_create(r->pool, f->c->bucket_alloc);
    }
    if (APR_BRIGADE_EMPTY(task->input.bbchunk)) {
        /* get more data from the lower layer filters. Always do this
         * in larger pieces, since we handle the read modes ourself.
         */
        status = ap_get_brigade(f->next, task->input.bbchunk, 
         * in larger pieces, since we handle the read modes ourself. */
        status = ap_get_brigade(f->next, bb, 
                                AP_MODE_READBYTES, block, 32*1024);
        if (status == APR_EOF) {
            if (!task->input.eos) {
@@ -475,50 +654,45 @@ apr_status_t h2_filter_request_in(ap_filter_t* f,
            return status;
        }

        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                      "h2_task(%s): trailers_in inspecting brigade", task->id);
        for (b = APR_BRIGADE_FIRST(task->input.bbchunk); 
             b != APR_BRIGADE_SENTINEL(task->input.bbchunk) && !task->input.eos; 
        for (b = APR_BRIGADE_FIRST(bb); 
             b != APR_BRIGADE_SENTINEL(bb) && !task->input.eos; 
             b = next) {
            next = APR_BUCKET_NEXT(b);
            if (APR_BUCKET_IS_METADATA(b)) {
                if (first_data) {
                    make_chunk(task, task->input.bbchunk, first_data, bblen, b);
                    make_chunk(task, bb, first_data, bblen, b);
                    first_data = NULL;
                    bblen = 0;
                }
                
                if (H2_BUCKET_IS_HEADERS(b)) {
                    apr_bucket_brigade *tmp;
                    h2_headers *headers = h2_bucket_headers_get(b);
                    
                    ap_assert(headers);
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                                  "h2_task(%s): receiving trailers", task->id);
                    tmp = apr_brigade_split_ex(task->input.bbchunk, b, NULL);
                    tmp = apr_brigade_split_ex(bb, b, NULL);
                    if (!apr_is_empty_table(headers->headers)) {
                        status = apr_brigade_puts(task->input.bbchunk, NULL, NULL, "0\r\n");
                        apr_table_do(ser_header, task->input.bbchunk, headers->headers, NULL);
                        status = apr_brigade_puts(task->input.bbchunk, NULL, NULL, "\r\n");
                        status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
                        apr_table_do(ser_header, bb, headers->headers, NULL);
                        status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
                    }
                    else {
                        status = apr_brigade_puts(task->input.bbchunk, NULL, NULL, "0\r\n\r\n");
                        status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
                    }
                    APR_BRIGADE_CONCAT(task->input.bbchunk, tmp);
                    apr_brigade_destroy(tmp);
                    r->trailers_in = apr_table_clone(r->pool, headers->headers);
                    APR_BUCKET_REMOVE(b);
                    apr_bucket_destroy(b);
                    APR_BRIGADE_CONCAT(bb, tmp);
                    apr_brigade_destroy(tmp);
                    task->input.eos = 1;
                }
                else if (APR_BUCKET_IS_EOS(b)) {
                    apr_bucket_brigade *tmp = apr_brigade_split_ex(task->input.bbchunk, b, NULL);
                    status = apr_brigade_puts(task->input.bbchunk, NULL, NULL, "0\r\n\r\n");
                    APR_BRIGADE_CONCAT(task->input.bbchunk, tmp);
                    tmp = apr_brigade_split_ex(bb, b, NULL);
                    status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
                    APR_BRIGADE_CONCAT(bb, tmp);
                    apr_brigade_destroy(tmp);
                    task->input.eos = 1;
                }
                break;
            }
            else if (b->length == 0) {
                APR_BUCKET_REMOVE(b);
@@ -527,14 +701,60 @@ apr_status_t h2_filter_request_in(ap_filter_t* f,
            else {
                if (!first_data) {
                    first_data = b;
                    bblen = 0;
                }
                bblen += b->length;
            }    
        }
        
        if (first_data) {
            make_chunk(task, task->input.bbchunk, first_data, bblen, NULL);
            make_chunk(task, bb, first_data, bblen, NULL);
        }            
    }
    return status;
}

apr_status_t h2_filter_request_in(ap_filter_t* f,
                                  apr_bucket_brigade* bb,
                                  ap_input_mode_t mode,
                                  apr_read_type_e block,
                                  apr_off_t readbytes)
{
    h2_task *task = f->ctx;
    request_rec *r = f->r;
    apr_status_t status = APR_SUCCESS;
    apr_bucket *b, *next;

    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
                  "h2_task(%s): request filter, exp=%d", task->id, r->expecting_100);
    if (!task->input.chunked) {
        status = ap_get_brigade(f->next, bb, mode, block, readbytes);
        /* pipe data through, just take care of trailers */
        for (b = APR_BRIGADE_FIRST(bb); 
             b != APR_BRIGADE_SENTINEL(bb); b = next) {
            next = APR_BUCKET_NEXT(b);
            if (H2_BUCKET_IS_HEADERS(b)) {
                h2_headers *headers = h2_bucket_headers_get(b);
                ap_assert(headers);
                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                              "h2_task(%s): receiving trailers", task->id);
                r->trailers_in = apr_table_clone(r->pool, headers->headers);
                APR_BUCKET_REMOVE(b);
                apr_bucket_destroy(b);
                ap_remove_input_filter(f);
                break;
            }
        }
        return status;
    }

    /* Things are more complicated. The standard HTTP input filter, which
     * does a lot what we do not want to duplicate, also cares about chunked
     * transfer encoding and trailers.
     * We need to simulate chunked encoding for it to be happy.
     */
    if ((status = read_and_chunk(f, task, block)) != APR_SUCCESS) {
        return status;
    }
    
    if (mode == AP_MODE_EXHAUSTIVE) {
+3 −0
Original line number Diff line number Diff line
@@ -33,6 +33,9 @@
struct h2_headers;
struct h2_task;

apr_status_t h2_from_h1_parse_response(struct h2_task *task, ap_filter_t *f, 
                                       apr_bucket_brigade *bb);

apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb);

apr_status_t h2_filter_request_in(ap_filter_t* f,
+1 −1
Original line number Diff line number Diff line
@@ -254,7 +254,7 @@ apr_status_t h2_ngn_shed_pull_request(h2_ngn_shed *shed,
    
    AP_DEBUG_ASSERT(ngn);
    *pr = NULL;
    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, shed->c, APLOGNO(03396)
    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, shed->c, APLOGNO(03396)
                  "h2_ngn_shed(%ld): pull task for engine %s, shutdown=%d", 
                  shed->c->id, ngn->id, want_shutdown);
    if (shed->aborted) {
Loading