On Mon, Nov 4, 2019 at 10:06 PM Ruediger Pluem <[email protected]> wrote:
>
> Sorry for getting back late. I missed that the HTTP_IN filter only sends a 
> possible
> 100 continue during the first run. Good catch. Nevertheless this leaves the 
> question open
> how to solve this. Either by your patch which sends the intermediate response 
> itself
> or by fixing HTTP_IN to check for the need to sent it not only on the first 
> run.
> I am undecided. Maybe someone else has a strong opinion either way otherwise 
> just move on with your patch.

Not someone else but still \o_

Actually I like the idea of HTTP_IN to handle 100 continue for others
cases than the first time around, it greatly simplifies things
(including for mod_proxy_http).

The way I see it is to move 100 continue handling out of the first
time only block, as you propose, and to change the decision for when
it's done like:

         /* Since we're about to read data, send 100-Continue if needed.
-         * Only valid on chunked and C-L bodies where the C-L is > 0. */
-        if ((ctx->state == BODY_CHUNK
-                || (ctx->state == BODY_LENGTH && ctx->remaining > 0))
+         * Only valid on chunked and C-L bodies where the C-L is > 0.
+         *
+         * If the read is to be nonblocking though, the caller may not want to
+         * handle this just now (e.g. mod_proxy_http), and is prepared to read
+         * nothing should the client really waits for 100 continue, so we can
+         * avoid sending it now and wait for the next blocking read.
+         *
+         * Also, we track in ctx->data_read whether something is read below,
+         * which allows to not send 100 continue either in this case, according
+         * to https://tools.ietf.org/html/rfc7231#section-5.1.1 :
+         *   A server MAY omit sending a 100 (Continue) response if it has
+         *   already received some or all of the message body for the
+         *   corresponding request, ...
+         *
+         * In any case, even if r->expecting remains set at the end of the
+         * request processing, ap_set_keepalive() will finally do the right
+         * thing (i.e. "Connection: close" the connection).
+         */
+        if (block == APR_BLOCK_READ
+                && (ctx->state == BODY_CHUNK
+                    || (ctx->state == BODY_LENGTH && ctx->remaining > 0))
                 && f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1)
-                && !(f->r->eos_sent || f->r->bytes_sent)) {
+                && !(ctx->data_read || f->r->eos_sent || f->r->bytes_sent)) {

Full patch attached, changes for both HTTP_IN and mod_proxy_http (this
is a POC only, untested yet).

I like the way it removes all the r->100_continue => req->100_continue
=> r->100_continue danse in mod_proxy_http (since HTTP_IN now does
what it wants in nonblocking mode), while the changes in HTTP_IN do
not look to insane (note the use of ap_send_interim_response() instead
of open coding it)...

WDYT?

Regards,
Yann.
Index: modules/http/http_filters.c
===================================================================
--- modules/http/http_filters.c	(revision 1869400)
+++ modules/http/http_filters.c	(working copy)
@@ -79,7 +79,8 @@ typedef struct http_filter_ctx
         BODY_CHUNK_END_LF, /* got CR after data, expect LF */
         BODY_CHUNK_TRAILER /* trailers */
     } state;
-    unsigned int eos_sent :1;
+    unsigned int eos_sent :1,
+                 data_read:1;
 } http_ctx_t;
 
 /**
@@ -401,47 +402,47 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bu
          * proxied *response*, proxy responses MUST be exempt.
          */
         if (ctx->state == BODY_NONE && f->r->proxyreq != PROXYREQ_RESPONSE) {
-            e = apr_bucket_eos_create(f->c->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(b, e);
-            ctx->eos_sent = 1;
-            return APR_SUCCESS;
+            ctx->eos_sent = 1; /* send EOS below */
         }
+    }
 
+    {
         /* Since we're about to read data, send 100-Continue if needed.
-         * Only valid on chunked and C-L bodies where the C-L is > 0. */
-        if ((ctx->state == BODY_CHUNK
-                || (ctx->state == BODY_LENGTH && ctx->remaining > 0))
+         * Only valid on chunked and C-L bodies where the C-L is > 0.
+         *
+         * If the read is to be nonblocking though, the caller may not want to
+         * handle this just now (e.g. mod_proxy_http), and is prepared to read
+         * nothing should the client really waits for 100 continue, so we can
+         * avoid sending it now and wait for the next blocking read.
+         *
+         * Also, we track in ctx->data_read whether something is read below,
+         * which allows to not send 100 continue either in this case, according
+         * to https://tools.ietf.org/html/rfc7231#section-5.1.1 :
+         *   A server MAY omit sending a 100 (Continue) response if it has
+         *   already received some or all of the message body for the
+         *   corresponding request, ...
+         *
+         * In any case, even if r->expecting remains set at the end of the
+         * request processing, ap_set_keepalive() will finally do the right
+         * thing (i.e. "Connection: close" the connection).
+         */
+        if (block == APR_BLOCK_READ
+                && (ctx->state == BODY_CHUNK
+                    || (ctx->state == BODY_LENGTH && ctx->remaining > 0))
                 && f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1)
-                && !(f->r->eos_sent || f->r->bytes_sent)) {
+                && !(ctx->data_read || f->r->eos_sent || f->r->bytes_sent)) {
             if (!ap_is_HTTP_SUCCESS(f->r->status)) {
                 ctx->state = BODY_NONE;
-                ctx->eos_sent = 1;
+                ctx->eos_sent = 1; /* send EOS below */
             }
             else {
-                char *tmp;
-                int len;
-                apr_bucket_brigade *bb;
+                int saved_status = f->r->status;
 
-                bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+                f->r->status = HTTP_CONTINUE;
+                ap_send_interim_response(f->r, 0);
+                AP_DEBUG_ASSERT(!f->r->expecting_100);
 
-                /* if we send an interim response, we're no longer
-                 * in a state of expecting one.
-                 */
-                f->r->expecting_100 = 0;
-                tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL " ",
-                        ap_get_status_line(HTTP_CONTINUE), CRLF CRLF, NULL);
-                len = strlen(tmp);
-                ap_xlate_proto_to_ascii(tmp, len);
-                e = apr_bucket_pool_create(tmp, len, f->r->pool,
-                        f->c->bucket_alloc);
-                APR_BRIGADE_INSERT_HEAD(bb, e);
-                e = apr_bucket_flush_create(f->c->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(bb, e);
-
-                rv = ap_pass_brigade(f->c->output_filters, bb);
-                if (rv != APR_SUCCESS) {
-                    return AP_FILTER_ERROR;
-                }
+                f->r->status = saved_status;
             }
         }
     }
@@ -491,8 +492,10 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bu
 
                 if (!APR_BUCKET_IS_METADATA(e)) {
                     rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ);
-
                     if (rv == APR_SUCCESS) {
+                        if (len > 0 && !ctx->data_read) {
+                            ctx->data_read = 1;
+                        }
                         rv = parse_chunk_size(ctx, buffer, len,
                                 f->r->server->limit_req_fieldsize, strict);
                     }
@@ -548,6 +551,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bu
 
                 /* How many bytes did we just read? */
                 apr_brigade_length(b, 0, &totalread);
+                if (totalread > 0 && !ctx->data_read) {
+                    ctx->data_read = 1;
+                }
 
                 /* If this happens, we have a bucket of unknown length.  Die because
                  * it means our assumptions have changed. */
Index: modules/proxy/mod_proxy_http.c
===================================================================
--- modules/proxy/mod_proxy_http.c	(revision 1869400)
+++ modules/proxy/mod_proxy_http.c	(working copy)
@@ -254,7 +254,6 @@ typedef struct {
 
     rb_methods rb_method;
 
-    int expecting_100;
     unsigned int do_100_continue:1,
                  prefetch_nonblocking:1;
 } proxy_http_req_t;
@@ -434,21 +433,6 @@ static int spool_reqbody_cl(proxy_http_req_t *req,
     apr_file_t *tmpfile = NULL;
     apr_off_t limit;
 
-    /* Send "100 Continue" now if the client expects one, before
-     * blocking on the body, otherwise we'd wait for each other.
-     */
-    if (req->expecting_100) {
-        int saved_status = r->status;
-
-        r->expecting_100 = 1;
-        r->status = HTTP_CONTINUE;
-        ap_send_interim_response(r, 0);
-        AP_DEBUG_ASSERT(!r->expecting_100);
-
-        r->status = saved_status;
-        req->expecting_100 = 0;
-    }
-
     body_brigade = apr_brigade_create(p, bucket_alloc);
     *bytes_spooled = 0;
 
@@ -579,7 +563,7 @@ static int ap_proxy_http_prefetch(proxy_http_req_t
     apr_read_type_e block;
 
     if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
-        if (req->expecting_100) {
+        if (r->expecting_100) {
             return HTTP_EXPECTATION_FAILED;
         }
         force10 = 1;
@@ -684,18 +668,6 @@ static int ap_proxy_http_prefetch(proxy_http_req_t
         apr_brigade_length(temp_brigade, 1, &bytes);
         bytes_read += bytes;
 
-        /* From https://tools.ietf.org/html/rfc7231#section-5.1.1
-         *   A server MAY omit sending a 100 (Continue) response if it has
-         *   already received some or all of the message body for the
-         *   corresponding request, or if [snip].
-         */
-        if (req->expecting_100 && bytes > 0) {
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(10206)
-                          "prefetching request body while 100-continue is"
-                          "expected, omit sending interim response");
-            req->expecting_100 = 0;
-        }
-
         /*
          * Save temp_brigade in input_brigade. (At least) in the SSL case
          * temp_brigade contains transient buckets whose data would get
@@ -1513,11 +1485,7 @@ int ap_proxy_http_process_response(proxy_http_req_
             if (!policy
                     || (!strcasecmp(policy, "RFC")
                         && (proxy_status != HTTP_CONTINUE
-                            || (req->expecting_100 = 1)))) {
-                if (proxy_status == HTTP_CONTINUE) {
-                    r->expecting_100 = req->expecting_100;
-                    req->expecting_100 = 0;
-                }
+                            || (r->expecting_100 = 1)))) {
                 ap_send_interim_response(r, 1);
             }
             /* FIXME: refine this to be able to specify per-response-status
@@ -1610,8 +1578,6 @@ int ap_proxy_http_process_response(proxy_http_req_
                  * to do the right thing according to the final response and
                  * any later update of r->expecting_100.
                  */
-                r->expecting_100 = req->expecting_100;
-                req->expecting_100 = 0;
             }
 
             /* Once only! */
@@ -2036,17 +2002,7 @@ static int proxy_http_handler(request_rec *r, prox
     /* Should we handle end-to-end or ping 100-continue? */
     if ((r->expecting_100 && (dconf->forward_100_continue || input_brigade))
             || PROXY_DO_100_CONTINUE(worker, r)) {
-        /* We need to reset r->expecting_100 or prefetching will cause
-         * ap_http_filter() to send "100 Continue" response by itself. So
-         * we'll use req->expecting_100 in mod_proxy_http to determine whether
-         * the client should be forwarded "100 continue", and r->expecting_100
-         * will be restored at the end of the function with the actual value of
-         * req->expecting_100 (i.e. cleared only if mod_proxy_http sent the
-         * "100 Continue" according to its policy).
-         */
         req->do_100_continue = req->prefetch_nonblocking = 1;
-        req->expecting_100 = r->expecting_100;
-        r->expecting_100 = 0;
     }
     /* Should we block while prefetching the body or try nonblocking and flush
      * data to the backend ASAP?
@@ -2182,10 +2138,6 @@ cleanup:
             req->backend->close = 1;
         ap_proxy_http_cleanup(proxy_function, r, req->backend);
     }
-    if (req->expecting_100) {
-        /* Restore r->expecting_100 if we didn't touch it */
-        r->expecting_100 = req->expecting_100;
-    }
     return status;
 }
 

Reply via email to