> Am 13.04.2022 um 17:16 schrieb Ruediger Pluem <rpl...@apache.org>:
> 
> 
> 
> On 4/7/22 12:41 PM, ic...@apache.org wrote:
>> Author: icing
>> Date: Thu Apr  7 10:41:46 2022
>> New Revision: 1899648
>> 
>> URL: http://svn.apache.org/viewvc?rev=1899648&view=rev
>> Log:
>>  *) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
>>     filter to send responses through the output filter chain.
>>     Specifically: the HTTP_HEADER output filter and 
>> ap_send_interim_response()
>>     create a RESPONSE bucket and no longer are concerned with HTTP/1.x
>>     serialization.
>>     A new HTTP1_RESPONSE_OUT transcode filter writes the proper HTTP/1.x
>>     bytes when dealing with a RESPONSE bucket. That filter installs itself
>>     on the pre_read_request hook when the connection has protocol 'http/1.1'.
>> 
>> 
>> Added:
>>    httpd/httpd/trunk/changes-entries/core_response_buckets.txt
>> Modified:
>>    httpd/httpd/trunk/include/mod_core.h
>>    httpd/httpd/trunk/modules/http/http_core.c
>>    httpd/httpd/trunk/modules/http/http_filters.c
>>    httpd/httpd/trunk/modules/http/http_protocol.c
>>    httpd/httpd/trunk/server/protocol.c
>> 
> 
>> Modified: httpd/httpd/trunk/modules/http/http_filters.c
>> URL: 
>> http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_filters.c?rev=1899648&r1=1899647&r2=1899648&view=diff
>> ==============================================================================
>> --- httpd/httpd/trunk/modules/http/http_filters.c (original)
>> +++ httpd/httpd/trunk/modules/http/http_filters.c Thu Apr  7 10:41:46 2022
> 
>> @@ -1364,62 +1178,124 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>>             return rv;
>>         }
>>     }
>> -
>> -    for (e = APR_BRIGADE_FIRST(b);
>> -         e != APR_BRIGADE_SENTINEL(b);
>> -         e = APR_BUCKET_NEXT(e))
>> -    {
>> -        if (AP_BUCKET_IS_ERROR(e) && !eb) {
>> -            eb = e->data;
>> -            continue;
>> -        }
>> -        if (APR_BUCKET_IS_EOS(e)) {
>> -            if (!eos) eos = e;
>> -            continue;
>> -        }
>> -        /*
>> -         * If we see an EOC bucket it is a signal that we should get out
>> -         * of the way doing nothing.
>> +    else {
>> +        /* Determine if it is time to insert the response bucket for
>> +         * the request. Request handlers just write content or an EOS
>> +         * and that needs to take the current state of request_rec to
>> +         * send on status and headers as a response bucket.
>> +         *
>> +         * But we also send interim responses (as response buckets)
>> +         * through this filter and that must not trigger generating
>> +         * an additional response bucket.
>> +         *
>> +         * Waiting on a DATA/ERROR/EOS bucket alone is not enough,
>> +         * unfortunately, as some handlers trigger response generation
>> +         * by just writing a FLUSH (see mod_lua's websocket for example).
>>          */
>> -        if (AP_BUCKET_IS_EOC(e)) {
>> -            ap_remove_output_filter(f);
>> -            return ap_pass_brigade(f->next, b);
>> +        for (e = APR_BRIGADE_FIRST(b);
>> +             e != APR_BRIGADE_SENTINEL(b) && !trigger;
>> +             e = APR_BUCKET_NEXT(e))
>> +        {
>> +            if (AP_BUCKET_IS_RESPONSE(e)) {
>> +                /* remember the last one if there are many. */
>> +                respb = e;
>> +            }
>> +            else if (APR_BUCKET_IS_FLUSH(e)) {
>> +                /* flush without response bucket triggers */
>> +                if (!respb) trigger = e;
>> +            }
>> +            else if (APR_BUCKET_IS_EOS(e)) {
>> +                trigger = e;
>> +            }
>> +            else if (AP_BUCKET_IS_ERROR(e)) {
>> +                /* Need to handle this below via ap_die() */
>> +                break;
>> +            }
>> +            else {
>> +                /* First content bucket, always triggering the response.*/
>> +                trigger = e;
>> +            }
> 
> Why don't we remove ourselves any longer if we hit an EOC bucket and pass 
> stuff on immediately?
> If we want to handle this HTTP protocol agnostic here we need to ensure that 
> the EOC bucket
> triggers the immediate removal of the ap_h1_response_out_filter once the 
> ap_h1_response_out_filter
> sees it without putting any headers on the wire.
> I am not sure what the appropriate behaviour would be in the HTTP > 1.1 case.

Valid point. The previous filter implementation removed itself immediately on
encountering an EOC, regardless of where it appeared in the brigade.

The current implementation inserts, if not done previously, a RESPONSE bucket.
Afterwards it checks on EOC, remove itself and passes on the brigade.

The behaviour differs, if an EOC is sent *without* a final RESPONSE having been
created before that. The question is what the intended behaviour of passing an
EOC is. Reading the comment in mod_proxy_http.c:1096, it seems that the 
intention really
is to NOT generate a response.

Seems I should change the new implementation to also do that. For H2, not 
seeing 
a RESPONSE would be a stream error and reset the stream. Which seems fine.

>>         }
>> -    }
>> 
>> -    if (!ctx->headers_sent && !check_headers(r)) {
>> -        /* We may come back here from ap_die() below,
>> -         * so clear anything from this response.
>> -         */
>> -        apr_table_clear(r->headers_out);
>> -        apr_table_clear(r->err_headers_out);
>> -        apr_brigade_cleanup(b);
>> +        if (respb) {
>> +            ap_bucket_response *resp = respb->data;
>> +            if (resp->status >= 200 || resp->status == 101) {
> 
> Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?
> 
>> +                /* Someone is passing the final response, remember it
>> +                 * so we no longer generate one. */
>> +                ctx->final_status = resp->status;
>> +                ctx->final_header_only = 
>> AP_STATUS_IS_HEADER_ONLY(resp->status);
>> +            }
>> +        }
>> 
> 
>> @@ -1430,148 +1306,15 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>>     }
>> 
>>     if (eos) {
>> -        /* on having seen EOS and added possible trailers, we
>> -         * can remove this filter.
>> -         */
>>         e = create_trailers_bucket(r, b->bucket_alloc);
>>         if (e) {
>>             APR_BUCKET_INSERT_BEFORE(eos, e);
>>         }
>>         ap_remove_output_filter(f);
>>     }
>> -
>> -    if (ctx->headers_sent) {
>> -        /* we did already the stuff below, just pass on */
>> -        return ap_pass_brigade(f->next, b);
>> -    }
>> -
>> -    /*
>> -     * Now that we are ready to send a response, we need to combine the two
>> -     * header field tables into a single table.  If we don't do this, our
>> -     * later attempts to set or unset a given fieldname might be bypassed.
>> -     */
>> -    if (!apr_is_empty_table(r->err_headers_out)) {
>> -        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
>> -                                           r->headers_out);
>> -    }
>> -
>> -    /*
>> -     * Remove the 'Vary' header field if the client can't handle it.
>> -     * Since this will have nasty effects on HTTP/1.1 caches, force
>> -     * the response into HTTP/1.0 mode.
>> -     *
>> -     * Note: the force-response-1.0 should come before the call to
>> -     *       basic_http_header_check()
>> -     */
>> -    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
>> -        apr_table_unset(r->headers_out, "Vary");
>> -        r->proto_num = HTTP_VERSION(1,0);
>> -        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
>> -    }
>> -    else {
>> -        fixup_vary(r);
>> -    }
>> -
>> -    /*
>> -     * Now remove any ETag response header field if earlier processing
>> -     * says so (such as a 'FileETag None' directive).
>> -     */
>> -    if (apr_table_get(r->notes, "no-etag") != NULL) {
>> -        apr_table_unset(r->headers_out, "ETag");
>> -    }
>> -
>> -    /* determine the protocol and whether we should use keepalives. */
>> -    basic_http_header_check(r, &protocol);
>> -    ap_set_keepalive(r);
>> -
>> -    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
>> -        apr_table_unset(r->headers_out, "Transfer-Encoding");
>> -        apr_table_unset(r->headers_out, "Content-Length");
>> -        r->content_type = r->content_encoding = NULL;
>> -        r->content_languages = NULL;
>> -        r->clength = r->chunked = 0;
>> -    }
>> -    else if (r->chunked) {
>> -        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
>> -        apr_table_unset(r->headers_out, "Content-Length");
>> -    }
>> -
>> -    ctype = ap_make_content_type(r, r->content_type);
>> -    if (ctype) {
>> -        apr_table_setn(r->headers_out, "Content-Type", ctype);
>> -    }
>> -
>> -    if (r->content_encoding) {
>> -        apr_table_setn(r->headers_out, "Content-Encoding",
>> -                       r->content_encoding);
>> -    }
>> -
>> -    if (!apr_is_empty_array(r->content_languages)) {
>> -        int i;
>> -        char *token;
>> -        char **languages = (char **)(r->content_languages->elts);
>> -        const char *field = apr_table_get(r->headers_out, 
>> "Content-Language");
>> -
>> -        while (field && (token = ap_get_list_item(r->pool, &field)) != 
>> NULL) {
>> -            for (i = 0; i < r->content_languages->nelts; ++i) {
>> -                if (!ap_cstr_casecmp(token, languages[i]))
>> -                    break;
>> -            }
>> -            if (i == r->content_languages->nelts) {
>> -                *((char **) apr_array_push(r->content_languages)) = token;
>> -            }
>> -        }
>> -
>> -        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
>> -        apr_table_setn(r->headers_out, "Content-Language", field);
>> -    }
>> -
>> -    /*
>> -     * Control cachability for non-cacheable responses if not already set by
>> -     * some other part of the server configuration.
>> -     */
>> -    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
>> -        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
>> -        ap_recent_rfc822_date(date, r->request_time);
>> -        apr_table_addn(r->headers_out, "Expires", date);
>> -    }
>> -
>> -    b2 = apr_brigade_create(r->pool, c->bucket_alloc);
>> -    basic_http_header(r, b2, protocol);
>> -
>> -    h.pool = r->pool;
>> -    h.bb = b2;
>> -
>> -    send_all_header_fields(&h, r);
>> -
>> -    terminate_header(b2);
>> -
>> -    if (header_only) {
>> -        e = APR_BRIGADE_LAST(b);
>> -        if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
>> -            APR_BUCKET_REMOVE(e);
>> -            APR_BRIGADE_INSERT_TAIL(b2, e);
>> -            ap_remove_output_filter(f);
>> -        }
>> -        apr_brigade_cleanup(b);
>> -    }
>> -
>> -    rv = ap_pass_brigade(f->next, b2);
>> -    apr_brigade_cleanup(b2);
>> -    ctx->headers_sent = 1;
>> -
>> -    if (rv != APR_SUCCESS || header_only) {
>> -        goto out;
>> -    }
>> -
>> -    r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
>> -
>> -    if (r->chunked) {
>> -        /* We can't add this filter until we have already sent the headers.
>> -         * If we add it before this point, then the headers will be chunked
>> -         * as well, and that is just wrong.
>> -         */
>> -        ap_add_output_filter("CHUNK", NULL, r, r->connection);
>> +    else if (ctx->final_status == 101) {
> 
> Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?

The HTTP status numbers are burned into my cortex, but I agree that
using the constant gives better readability. Will change.
> 
>> +        /* switching protocol, whatever comes next is not HTTP/1.x */
>> +        ap_remove_output_filter(f);
>>     }
>> 
>>     rv = ap_pass_brigade(f->next, b);
>> @@ -1989,3 +1732,351 @@ apr_status_t ap_http_outerror_filter(ap_
>> 
>>     return ap_pass_brigade(f->next,  b);
>> }
>> +
>> +
>> +/* fill "bb" with a barebones/initial HTTP/1.x response header */
>> +static void h1_append_response_head(request_rec *r,
>> +                                    ap_bucket_response *resp,
>> +                                    const char *protocol,
>> +                                    apr_bucket_brigade *bb)
>> +{
>> +    const char *date = NULL;
>> +    const char *server = NULL;
>> +    const char *status_line;
>> +    struct iovec vec[4];
>> +
>> +    if (r->assbackwards) {
>> +        /* there are no headers to send */
>> +        return;
>> +    }
>> +
>> +    /* Output the HTTP/1.x Status-Line and the Date and Server fields */
>> +    if (resp->reason) {
>> +        status_line =  apr_psprintf(r->pool, "%d %s", resp->status, 
>> resp->reason);
>> +    }
>> +    else {
>> +        status_line = ap_get_status_line_ex(r->pool, resp->status);
>> +    }
>> +
>> +    vec[0].iov_base = (void *)protocol;
>> +    vec[0].iov_len  = strlen(protocol);
>> +    vec[1].iov_base = (void *)" ";
>> +    vec[1].iov_len  = sizeof(" ") - 1;
>> +    vec[2].iov_base = (void *)(status_line);
>> +    vec[2].iov_len  = strlen(status_line);
>> +    vec[3].iov_base = (void *)CRLF;
>> +    vec[3].iov_len  = sizeof(CRLF) - 1;
>> +#if APR_CHARSET_EBCDIC
>> +    {
>> +        char *tmp;
>> +        apr_size_t len;
>> +        tmp = apr_pstrcatv(r->pool, vec, 4, &len);
>> +        ap_xlate_proto_to_ascii(tmp, len);
>> +        apr_brigade_write(bb, NULL, NULL, tmp, len);
>> +    }
>> +#else
>> +    apr_brigade_writev(bb, NULL, NULL, vec, 4);
>> +#endif
>> +
>> +    date = apr_table_get(resp->headers, "Date");
>> +    server = apr_table_get(resp->headers, "Server");
>> +    if (date) {
>> +        /* We always write that as first, just because we
>> +         * always did and some quirky clients might rely on that.
>> +         */
>> +        ap_h1_append_header(bb, r->pool, "Date", date);
>> +        apr_table_unset(resp->headers, "Date");
>> +    }
>> +    if (server) {
>> +        /* We always write that second, just because we
>> +         * always did and some quirky clients might rely on that.
>> +         */
>> +        ap_h1_append_header(bb, r->pool, "Server", server);
>> +        apr_table_unset(resp->headers, "Server");
>> +    }
>> +
>> +    if (APLOGrtrace3(r)) {
>> +        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
>> +                      "Response sent with status %d%s",
>> +                      r->status,
>> +                      APLOGrtrace4(r) ? ", headers:" : "");
>> +
>> +        /*
>> +         * Date and Server are less interesting, use TRACE5 for them while
>> +         * using TRACE4 for the other headers.
>> +         */
>> +        if (date)
>> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Date: %s",
>> +                          date);
>> +        if (server)
>> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Server: %s",
>> +                          server);
>> +    }
>> +}
>> +
>> +typedef struct h1_response_ctx {
>> +    int final_response_sent;    /* strict: a response status >= 200 was 
>> sent */
>> +    int discard_body;           /* the response is header only, discard 
>> body */
>> +    apr_bucket_brigade *tmpbb;
>> +} h1_response_ctx;
>> +
>> +AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t 
>> *f,
>> +                                                               
>> apr_bucket_brigade *b)
>> +{
>> +    request_rec *r = f->r;
>> +    conn_rec *c = r->connection;
>> +    apr_bucket *e, *next = NULL;
>> +    h1_response_ctx *ctx = f->ctx;
>> +    apr_status_t rv = APR_SUCCESS;
>> +    core_server_config *conf = 
>> ap_get_core_module_config(r->server->module_config);
>> +    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
>> +
>> +    AP_DEBUG_ASSERT(!r->main);
>> +
>> +    if (!ctx) {
>> +        ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
>> +    }
>> +
>> +    for (e = APR_BRIGADE_FIRST(b);
>> +         e != APR_BRIGADE_SENTINEL(b);
>> +         e = next)
>> +    {
>> +        next = APR_BUCKET_NEXT(e);
>> +
>> +        if (APR_BUCKET_IS_METADATA(e)) {
>> +
>> +            if (APR_BUCKET_IS_EOS(e)) {
>> +                if (!ctx->final_response_sent) {
>> +                    /* should not happen. do we generate a 500 here? */
>> +                }
>> +                ap_remove_output_filter(f);
>> +                goto pass;
>> +            }
>> +            else if (AP_BUCKET_IS_RESPONSE(e)) {
>> +                ap_bucket_response *resp = e->data;
>> +
>> +                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
>> +                              "ap_http1_response_out_filter seeing response 
>> bucket status=%d",
>> +                              resp->status);
>> +                if (strict && resp->status < 100) {
>> +                    /* error, not a valid http status */
>> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
>> APLOGNO(10386)
>> +                                  "ap_http1_response_out_filter seeing 
>> headers "
>> +                                  "status=%d in strict mode",
>> +                                  resp->status);
>> +                    rv = AP_FILTER_ERROR;
>> +                    goto cleanup;
>> +                }
>> +                else if (ctx->final_response_sent) {
>> +                    /* already sent the final response for the request.
>> +                     */
>> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
>> APLOGNO(10387)
>> +                                  "ap_http1_response_out_filter seeing 
>> headers "
>> +                                  "status=%d after final response already 
>> sent",
>> +                                  resp->status);
>> +                    rv = AP_FILTER_ERROR;
>> +                    goto cleanup;
>> +                }
>> +                else {
>> +                    /* a response status to transcode, might be final or 
>> interim
>> +                     */
>> +                    const char *proto = AP_SERVER_PROTOCOL;
>> +
>> +                    ctx->final_response_sent = (resp->status >= 200)
>> +                        || (!strict && resp->status < 100);
>> +                    ctx->discard_body = ctx->final_response_sent &&
>> +                        (r->header_only || 
>> AP_STATUS_IS_HEADER_ONLY(resp->status));
>> +
>> +                    if (!ctx->tmpbb) {
>> +                        ctx->tmpbb = apr_brigade_create(r->pool, 
>> c->bucket_alloc);
>> +                    }
>> +                    if (next != APR_BRIGADE_SENTINEL(b)) {
>> +                        apr_brigade_split_ex(b, next, ctx->tmpbb);
>> +                    }
>> +
>> +                    if (ctx->final_response_sent) {
>> +                        ap_h1_set_keepalive(r, resp);
>> +
>> +                        if (AP_STATUS_IS_HEADER_ONLY(resp->status)) {
>> +                            apr_table_unset(resp->headers, 
>> "Transfer-Encoding");
>> +                        }
>> +                        else if (r->chunked) {
>> +                            apr_table_mergen(resp->headers, 
>> "Transfer-Encoding", "chunked");
>> +                            apr_table_unset(resp->headers, 
>> "Content-Length");
>> +                        }
>> +                    }
>> +
>> +                    /* kludge around broken browsers when indicated by 
>> force-response-1.0
>> +                     */
>> +                    if (r->proto_num == HTTP_VERSION(1,0)
>> +                        && apr_table_get(r->subprocess_env, 
>> "force-response-1.0")) {
>> +                        r->connection->keepalive = AP_CONN_CLOSE;
>> +                        proto = "HTTP/1.0";
>> +                    }
>> +                    h1_append_response_head(r, resp, proto, b);
>> +                    ap_h1_append_headers(b, r, resp->headers);
>> +                    ap_h1_terminate_header(b);
>> +                    apr_bucket_delete(e);
>> +
>> +                    if (ctx->final_response_sent && r->chunked) {
>> +                        /* We can't add this filter until we have already 
>> sent the headers.
>> +                         * If we add it before this point, then the headers 
>> will be chunked
>> +                         * as well, and that is just wrong.
>> +                         */
>> +                        rv = ap_pass_brigade(f->next, b);
>> +                        apr_brigade_cleanup(b);
>> +                        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
>> +                                      "ap_http1_response_out_filter passed 
>> response"
>> +                                      ", add CHUNK filter");
>> +                        if (APR_SUCCESS != rv) {
>> +                            apr_brigade_cleanup(ctx->tmpbb);
>> +                            goto cleanup;
>> +                        }
>> +                        ap_add_output_filter("CHUNK", NULL, r, 
>> r->connection);
>> +                    }
>> +
>> +                    APR_BRIGADE_CONCAT(b, ctx->tmpbb);
>> +
>> +                    if (resp->status == 101) {
> 
> Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?
> 
>> +                        /* switched to another protocol, get out of the way 
>> */
>> +                        AP_DEBUG_ASSERT(!r->chunked);
>> +                        ap_remove_output_filter(f);
>> +                        goto pass;
>> +                    }
>> +                }
>> +            }
>> +        }
>> +        else if (!ctx->final_response_sent && strict) {
>> +            /* data buckets before seeing the final response are in error.
>> +             */
>> +            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10390)
>> +                          "ap_http1_response_out_filter seeing data before 
>> headers, %ld bytes ",
>> +                          (long)e->length);
>> +            rv = AP_FILTER_ERROR;
>> +            goto cleanup;
>> +        }
>> +        else if (ctx->discard_body) {
>> +            apr_bucket_delete(e);
>> +        }
>> +    }
>> +
>> +pass:
>> +    rv = ap_pass_brigade(f->next, b);
>> +cleanup:
>> +    return rv;
>> +}
>> +
> 
> Regards
> 
> RĂ¼diger
> 

Reply via email to