]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Backport to 2.4:
authorGraham Leggett <minfrin@apache.org>
Sun, 17 Jan 2021 16:41:47 +0000 (16:41 +0000)
committerGraham Leggett <minfrin@apache.org>
Sun, 17 Jan 2021 16:41:47 +0000 (16:41 +0000)
  *) mod_proxy: common spooling code to allow mod_proxy_fgci proxy-sendcl. BZ 57087
     trunk patch: https://svn.apache.org/r1884067
                  https://svn.apache.org/r1884068
                  https://svn.apache.org/r1884069
                  https://svn.apache.org/r1884070
     2.4.x patch: http://people.apache.org/~ylavic/patches/2.4.x-mod_proxy_fcgi-sendcl-5on5.patch
                  https://github.com/apache/httpd/pull/162
     +1: ylavic, covener, minfrin
     ylavic: Long standing BZ (always send Content-Length, according to the CGI
             specs) which is addressed by reusing mod_proxy_http's spooling
             code (moved to proxy_util) in mod_proxy_fcgi. The user still needs
             to SetEnv proxy-sendcl for that (2.4 compat).

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1885607 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
STATUS
include/ap_mmn.h
modules/proxy/mod_proxy.h
modules/proxy/mod_proxy_fcgi.c
modules/proxy/mod_proxy_http.c
modules/proxy/proxy_util.c

diff --git a/CHANGES b/CHANGES
index cf75068c73ea503bc844f2837a9c2bcf038b9f2c..cd2474fec81c361ef1d2e989b512b10639e3de7e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,10 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.4.47
 
+  *) mod_proxy_fcgi: Honor "SetEnv proxy-sendcl" to forward a chunked
+     Transfer-Encoding from the client, spooling the request body when needed
+     to provide a Content-Length to the backend.  PR 57087.  [Yann Ylavic]
+
   *) mod_proxy: Put mod_proxy_{connect,wstunnel} tunneling code in common in
      proxy_util.  [Yann Ylavic]
 
diff --git a/STATUS b/STATUS
index 38ed50eddae6175f28daa498d7d5a9f2218d906a..de1b7c21d595cc36eb4b728a1afb4a38624eed5e 100644 (file)
--- a/STATUS
+++ b/STATUS
@@ -138,19 +138,6 @@ RELEASE SHOWSTOPPERS:
 PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
   [ start all new proposals below, under PATCHES PROPOSED. ]
 
-  *) mod_proxy: common spooling code to allow mod_proxy_fgci proxy-sendcl. BZ 57087
-     trunk patch: https://svn.apache.org/r1884067
-                  https://svn.apache.org/r1884068
-                  https://svn.apache.org/r1884069
-                  https://svn.apache.org/r1884070
-     2.4.x patch: http://people.apache.org/~ylavic/patches/2.4.x-mod_proxy_fcgi-sendcl-5on5.patch
-                  https://github.com/apache/httpd/pull/162
-     +1: ylavic, covener, minfrin
-     ylavic: Long standing BZ (always send Content-Length, according to the CGI
-             specs) which is addressed by reusing mod_proxy_http's spooling
-             code (moved to proxy_util) in mod_proxy_fcgi. The user still needs
-             to SetEnv proxy-sendcl for that (2.4 compat).
-
 
 
 PATCHES PROPOSED TO BACKPORT FROM TRUNK:
index 7cef0379eacf7e4d07b523ff977f9f98feed550b..9a4f376a92bea84bb4c5d27f3e7ac8ef9cccb63c 100644 (file)
  * 20120211.99 (2.4.47-dev) Add proxy_tunnel_rec, ap_proxy_tunnel_create()
  *                          and ap_proxy_tunnel_run() to proxy_util.
  * 20120211.99 (2.4.47-dev) Add ap_proxy_worker_can_upgrade()
+ * 20120211.100 (2.4.47-dev) Add ap_proxy_prefetch_input(),
+ *                           ap_proxy_spool_input() and
+ *                           ap_proxy_read_input().
  */
 
 #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */
 #ifndef MODULE_MAGIC_NUMBER_MAJOR
 #define MODULE_MAGIC_NUMBER_MAJOR 20120211
 #endif
-#define MODULE_MAGIC_NUMBER_MINOR 99                  /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 100                 /* 0...n */
 
 /**
  * Determine if the server's current MODULE_MAGIC_NUMBER is at least a
index c9a10ea54ec7bdc4b322a0274a176e1ecd79d625..1ba54cd49e109d7648086d2bee0ad206a775b048 100644 (file)
@@ -1202,6 +1202,55 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
                                            char **old_cl_val,
                                            char **old_te_val);
 
+/**
+ * Prefetch the client request body (in memory), up to a limit.
+ * Read what's in the client pipe. If nonblocking is set and read is EAGAIN,
+ * pass a FLUSH bucket to the backend and read again in blocking mode.
+ * @param r             client request
+ * @param backend       backend connection
+ * @param input_brigade input brigade to use/fill
+ * @param block         blocking or non-blocking mode
+ * @param bytes_read    number of bytes read
+ * @param max_read      maximum number of bytes to read
+ * @return              OK or HTTP_* error code
+ * @note max_read is rounded up to APR_BUCKET_BUFF_SIZE
+ */
+PROXY_DECLARE(int) ap_proxy_prefetch_input(request_rec *r,
+                                           proxy_conn_rec *backend,
+                                           apr_bucket_brigade *input_brigade,
+                                           apr_read_type_e block,
+                                           apr_off_t *bytes_read,
+                                           apr_off_t max_read);
+
+/**
+ * Spool the client request body to memory, or disk above given limit.
+ * @param r             client request
+ * @param backend       backend connection
+ * @param input_brigade input brigade to use/fill
+ * @param bytes_spooled number of bytes spooled
+ * @param max_mem_spool maximum number of in-memory bytes
+ * @return              OK or HTTP_* error code
+ */
+PROXY_DECLARE(int) ap_proxy_spool_input(request_rec *r,
+                                        proxy_conn_rec *backend,
+                                        apr_bucket_brigade *input_brigade,
+                                        apr_off_t *bytes_spooled,
+                                        apr_off_t max_mem_spool);
+
+/**
+ * Read what's in the client pipe. If the read would block (EAGAIN),
+ * pass a FLUSH bucket to the backend and read again in blocking mode.
+ * @param r             client request
+ * @param backend       backend connection
+ * @param input_brigade brigade to use/fill
+ * @param max_read      maximum number of bytes to read
+ * @return              OK or HTTP_* error code
+ */
+PROXY_DECLARE(int) ap_proxy_read_input(request_rec *r,
+                                       proxy_conn_rec *backend,
+                                       apr_bucket_brigade *input_brigade,
+                                       apr_off_t max_read);
+
 /**
  * @param bucket_alloc  bucket allocator
  * @param r             request
index 51d0143929c10cced2ce353ea52827f4e728297a..d2d06dd3c147ae8c54beb40a4b94151cb0bda3fa 100644 (file)
@@ -532,7 +532,8 @@ static int handle_headers(request_rec *r, int *state,
 static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
                              request_rec *r, apr_pool_t *setaside_pool,
                              apr_uint16_t request_id, const char **err,
-                             int *bad_request, int *has_responded)
+                             int *bad_request, int *has_responded,
+                             apr_bucket_brigade *input_brigade)
 {
     apr_bucket_brigade *ib, *ob;
     int seen_end_of_headers = 0, done = 0, ignore_body = 0;
@@ -594,9 +595,26 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
             int last_stdin = 0;
             char *iobuf_cursor;
 
-            rv = ap_get_brigade(r->input_filters, ib,
-                                AP_MODE_READBYTES, APR_BLOCK_READ,
-                                iobuf_size);
+            if (APR_BRIGADE_EMPTY(input_brigade)) {
+                rv = ap_get_brigade(r->input_filters, ib,
+                                    AP_MODE_READBYTES, APR_BLOCK_READ,
+                                    iobuf_size);
+            }
+            else {
+                apr_bucket *e;
+                APR_BRIGADE_CONCAT(ib, input_brigade);
+                rv = apr_brigade_partition(ib, iobuf_size, &e);
+                if (rv == APR_SUCCESS) {
+                    while (e != APR_BRIGADE_SENTINEL(ib)
+                           && APR_BUCKET_IS_METADATA(e)) {
+                        e = APR_BUCKET_NEXT(e);
+                    }
+                    apr_brigade_split_ex(ib, e, input_brigade);
+                }
+                else if (rv == APR_INCOMPLETE) {
+                    rv = APR_SUCCESS;
+                }
+            }
             if (rv != APR_SUCCESS) {
                 *err = "reading input brigade";
                 *bad_request = 1;
@@ -934,7 +952,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
                            conn_rec *origin,
                            proxy_dir_conf *conf,
                            apr_uri_t *uri,
-                           char *url, char *server_portstr)
+                           char *url, char *server_portstr,
+                           apr_bucket_brigade *input_brigade)
 {
     /* Request IDs are arbitrary numbers that we assign to a
      * single request. This would allow multiplex/pipelining of
@@ -970,7 +989,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
 
     /* Step 3: Read records from the back end server and handle them. */
     rv = dispatch(conn, conf, r, temp_pool, request_id,
-                  &err, &bad_request, &has_responded);
+                  &err, &bad_request, &has_responded,
+                  input_brigade);
     if (rv != APR_SUCCESS) {
         /* If the client aborted the connection during retrieval or (partially)
          * sending the response, don't return a HTTP_SERVICE_UNAVAILABLE, since
@@ -1006,6 +1026,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
 
 #define FCGI_SCHEME "FCGI"
 
+#define MAX_MEM_SPOOL 16384
+
 /*
  * This handles fcgi:(dest) URLs
  */
@@ -1018,6 +1040,8 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
     char server_portstr[32];
     conn_rec *origin = NULL;
     proxy_conn_rec *backend = NULL;
+    apr_bucket_brigade *input_brigade;
+    apr_off_t input_bytes = 0;
     apr_uri_t *uri;
 
     proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
@@ -1060,6 +1084,101 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
         goto cleanup;
     }
 
+    /* We possibly reuse input data prefetched in previous call(s), e.g. for a
+     * balancer fallback scenario.
+     */
+    apr_pool_userdata_get((void **)&input_brigade, "proxy-fcgi-input", p);
+    if (input_brigade == NULL) {
+        const char *old_te = apr_table_get(r->headers_in, "Transfer-Encoding");
+        const char *old_cl = NULL;
+        if (old_te) {
+            apr_table_unset(r->headers_in, "Content-Length");
+        }
+        else {
+            old_cl = apr_table_get(r->headers_in, "Content-Length");
+        }
+
+        input_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+        apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, p);
+
+        /* Prefetch (nonlocking) the request body so to increase the chance
+         * to get the whole (or enough) body and determine Content-Length vs
+         * chunked or spooled. By doing this before connecting or reusing the
+         * backend, we want to minimize the delay between this connection is
+         * considered alive and the first bytes sent (should the client's link
+         * be slow or some input filter retain the data). This is a best effort
+         * to prevent the backend from closing (from under us) what it thinks is
+         * an idle connection, hence to reduce to the minimum the unavoidable
+         * local is_socket_connected() vs remote keepalive race condition.
+         */
+        status = ap_proxy_prefetch_input(r, backend, input_brigade,
+                                         APR_NONBLOCK_READ, &input_bytes,
+                                         MAX_MEM_SPOOL);
+        if (status != OK) {
+            goto cleanup;
+        }
+
+        /*
+         * The request body is streamed by default, using either C-L or
+         * chunked T-E, like this:
+         *
+         *   The whole body (including no body) was received on prefetch, i.e.
+         *   the input brigade ends with EOS => C-L = input_bytes.
+         *
+         *   C-L is known and reliable, i.e. only protocol filters in the input
+         *   chain thus none should change the body => use C-L from client.
+         *
+         *   The administrator has not "proxy-sendcl" which prevents T-E => use
+         *   T-E and chunks.
+         *
+         *   Otherwise we need to determine and set a content-length, so spool
+         *   the entire request body to memory/temporary file (MAX_MEM_SPOOL),
+         *   such that we finally know its length => C-L = input_bytes.
+         */
+        if (!APR_BRIGADE_EMPTY(input_brigade)
+                && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+            /* The whole thing fit, so our decision is trivial, use the input
+             * bytes for the Content-Length. If we expected no body, and read
+             * no body, do not set the Content-Length.
+             */
+            if (old_cl || old_te || input_bytes) {
+                apr_table_setn(r->headers_in, "Content-Length",
+                               apr_off_t_toa(p, input_bytes));
+                if (old_te) {
+                    apr_table_unset(r->headers_in, "Transfer-Encoding");
+                }
+            }
+        }
+        else if (old_cl && r->input_filters == r->proto_input_filters) {
+            /* Streaming is possible by preserving the existing C-L */
+        }
+        else if (!apr_table_get(r->subprocess_env, "proxy-sendcl")) {
+            /* Streaming is possible using T-E: chunked */
+        }
+        else {
+            /* No streaming, C-L is the only option so spool to memory/file */
+            apr_bucket_brigade *tmp_bb;
+            apr_off_t remaining_bytes = 0;
+
+            AP_DEBUG_ASSERT(MAX_MEM_SPOOL >= input_bytes);
+            tmp_bb = apr_brigade_create(p, r->connection->bucket_alloc);
+            status = ap_proxy_spool_input(r, backend, tmp_bb, &remaining_bytes,
+                                          MAX_MEM_SPOOL - input_bytes);
+            if (status != OK) {
+                goto cleanup;
+            }
+
+            APR_BRIGADE_CONCAT(input_brigade, tmp_bb);
+            input_bytes += remaining_bytes;
+
+            apr_table_setn(r->headers_in, "Content-Length",
+                           apr_off_t_toa(p, input_bytes));
+            if (old_te) {
+                apr_table_unset(r->headers_in, "Transfer-Encoding");
+            }
+        }
+    }
+
     /* This scheme handler does not reuse connections by default, to
      * avoid tying up a fastcgi that isn't expecting to work on
      * parallel requests.  But if the user went out of their way to
@@ -1084,7 +1203,7 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
 
     /* Step Three: Process the Request */
     status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
-                             server_portstr);
+                             server_portstr, input_brigade);
 
 cleanup:
     ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
index a020438f2d923864e8af2b3467f7cb484032ed3d..d18ec63600378f5cf7e6be08bc6e0c7e70e8bcef 100644 (file)
@@ -294,50 +294,6 @@ typedef struct {
                  force10                :1;
 } proxy_http_req_t;
 
-/* Read what's in the client pipe. If nonblocking is set and read is EAGAIN,
- * pass a FLUSH bucket to the backend and read again in blocking mode.
- */
-static int stream_reqbody_read(proxy_http_req_t *req, apr_bucket_brigade *bb,
-                               int nonblocking)
-{
-    request_rec *r = req->r;
-    proxy_conn_rec *p_conn = req->backend;
-    apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
-    apr_read_type_e block = nonblocking ? APR_NONBLOCK_READ : APR_BLOCK_READ;
-    apr_status_t status;
-    int rv;
-
-    for (;;) {
-        status = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
-                                block, HUGE_STRING_LEN);
-        if (block == APR_BLOCK_READ
-                || (!APR_STATUS_IS_EAGAIN(status)
-                    && (status != APR_SUCCESS || !APR_BRIGADE_EMPTY(bb)))) {
-            break;
-        }
-
-        /* Flush and retry (blocking) */
-        apr_brigade_cleanup(bb);
-        rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, req->origin, bb, 1);
-        if (rv != OK) {
-            return rv;
-        }
-        block = APR_BLOCK_READ;
-    }
-
-    if (status != APR_SUCCESS) {
-        conn_rec *c = r->connection;
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02608)
-                      "read request body failed to %pI (%s)"
-                      " from %s (%s)", p_conn->addr,
-                      p_conn->hostname ? p_conn->hostname: "",
-                      c->client_ip, c->remote_host ? c->remote_host: "");
-        return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
-    }
-
-    return OK;
-}
-
 static int stream_reqbody(proxy_http_req_t *req)
 {
     request_rec *r = req->r;
@@ -356,7 +312,8 @@ static int stream_reqbody(proxy_http_req_t *req)
     do {
         if (APR_BRIGADE_EMPTY(input_brigade)
                 && APR_BRIGADE_EMPTY(header_brigade)) {
-            rv = stream_reqbody_read(req, input_brigade, 1);
+            rv = ap_proxy_read_input(r, p_conn, input_brigade,
+                                     HUGE_STRING_LEN);
             if (rv != OK) {
                 return rv;
             }
@@ -443,7 +400,7 @@ static int stream_reqbody(proxy_http_req_t *req)
          */
         APR_BRIGADE_PREPEND(input_brigade, header_brigade);
 
-        /* Flush here on EOS because we won't stream_reqbody_read() again */
+        /* Flush here on EOS because we won't ap_proxy_read_input() again. */
         rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin,
                                    input_brigade, seen_eos);
         if (rv != OK) {
@@ -454,138 +411,6 @@ static int stream_reqbody(proxy_http_req_t *req)
     return OK;
 }
 
-static int spool_reqbody_cl(proxy_http_req_t *req, apr_off_t *bytes_spooled)
-{
-    apr_pool_t *p = req->p;
-    request_rec *r = req->r;
-    int seen_eos = 0, rv = OK;
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
-    apr_bucket_brigade *input_brigade = req->input_brigade;
-    apr_bucket_brigade *body_brigade;
-    apr_bucket *e;
-    apr_off_t bytes, fsize = 0;
-    apr_file_t *tmpfile = NULL;
-    apr_off_t limit;
-
-    body_brigade = apr_brigade_create(p, bucket_alloc);
-    *bytes_spooled = 0;
-
-    limit = ap_get_limit_req_body(r);
-
-    do {
-        if (APR_BRIGADE_EMPTY(input_brigade)) {
-            rv = stream_reqbody_read(req, input_brigade, 0);
-            if (rv != OK) {
-                return rv;
-            }
-        }
-
-        /* If this brigade contains EOS, either stop or remove it. */
-        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
-            seen_eos = 1;
-        }
-
-        apr_brigade_length(input_brigade, 1, &bytes);
-
-        if (*bytes_spooled + bytes > MAX_MEM_SPOOL) {
-            /*
-             * LimitRequestBody does not affect Proxy requests (Should it?).
-             * Let it take effect if we decide to store the body in a
-             * temporary file on disk.
-             */
-            if (limit && (*bytes_spooled + bytes > limit)) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01088)
-                              "Request body is larger than the configured "
-                              "limit of %" APR_OFF_T_FMT, limit);
-                return HTTP_REQUEST_ENTITY_TOO_LARGE;
-            }
-            /* can't spool any more in memory; write latest brigade to disk */
-            if (tmpfile == NULL) {
-                const char *temp_dir;
-                char *template;
-
-                status = apr_temp_dir_get(&temp_dir, p);
-                if (status != APR_SUCCESS) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01089)
-                                  "search for temporary directory failed");
-                    return HTTP_INTERNAL_SERVER_ERROR;
-                }
-                apr_filepath_merge(&template, temp_dir,
-                                   "modproxy.tmp.XXXXXX",
-                                   APR_FILEPATH_NATIVE, p);
-                status = apr_file_mktemp(&tmpfile, template, 0, p);
-                if (status != APR_SUCCESS) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01090)
-                                  "creation of temporary file in directory "
-                                  "%s failed", temp_dir);
-                    return HTTP_INTERNAL_SERVER_ERROR;
-                }
-            }
-            for (e = APR_BRIGADE_FIRST(input_brigade);
-                 e != APR_BRIGADE_SENTINEL(input_brigade);
-                 e = APR_BUCKET_NEXT(e)) {
-                const char *data;
-                apr_size_t bytes_read, bytes_written;
-
-                apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
-                status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
-                if (status != APR_SUCCESS) {
-                    const char *tmpfile_name;
-
-                    if (apr_file_name_get(&tmpfile_name, tmpfile) != APR_SUCCESS) {
-                        tmpfile_name = "(unknown)";
-                    }
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01091)
-                                  "write to temporary file %s failed",
-                                  tmpfile_name);
-                    return HTTP_INTERNAL_SERVER_ERROR;
-                }
-                AP_DEBUG_ASSERT(bytes_read == bytes_written);
-                fsize += bytes_written;
-            }
-            apr_brigade_cleanup(input_brigade);
-        }
-        else {
-
-            /*
-             * Save input_brigade in body_brigade. (At least) in the SSL case
-             * input_brigade contains transient buckets whose data would get
-             * overwritten during the next call of ap_get_brigade in the loop.
-             * ap_save_brigade ensures these buckets to be set aside.
-             * Calling ap_save_brigade with NULL as filter is OK, because
-             * body_brigade already has been created and does not need to get
-             * created by ap_save_brigade.
-             */
-            status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p);
-            if (status != APR_SUCCESS) {
-                return HTTP_INTERNAL_SERVER_ERROR;
-            }
-
-        }
-
-        *bytes_spooled += bytes;
-    } while (!seen_eos);
-
-    APR_BRIGADE_CONCAT(input_brigade, body_brigade);
-    if (tmpfile) {
-        apr_brigade_insert_file(input_brigade, tmpfile, 0, fsize, p);
-    }
-    if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
-        e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(input_brigade, e);
-    }
-    if (tmpfile) {
-        /* We dropped metadata buckets when spooling to tmpfile,
-         * terminate with EOS for stream_reqbody() to flush the
-         * whole in one go.
-         */
-        e = apr_bucket_eos_create(bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(input_brigade, e);
-    }
-    return OK;
-}
-
 static void terminate_headers(proxy_http_req_t *req)
 {
     apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
@@ -633,13 +458,10 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
     apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
     apr_bucket_brigade *header_brigade = req->header_brigade;
     apr_bucket_brigade *input_brigade = req->input_brigade;
-    apr_bucket_brigade *temp_brigade;
     apr_bucket *e;
-    apr_status_t status;
     apr_off_t bytes_read = 0;
     apr_off_t bytes;
     int rv;
-    apr_read_type_e block;
 
     if (req->force10 && r->expecting_100) {
         return HTTP_EXPECTATION_FAILED;
@@ -697,69 +519,12 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
         p_conn->close = 1;
     }
 
-    /* Prefetch MAX_MEM_SPOOL bytes
-     *
-     * This helps us avoid any election of C-L v.s. T-E
-     * request bodies, since we are willing to keep in
-     * memory this much data, in any case.  This gives
-     * us an instant C-L election if the body is of some
-     * reasonable size.
-     */
-    temp_brigade = apr_brigade_create(p, bucket_alloc);
-    block = req->prefetch_nonblocking ? APR_NONBLOCK_READ : APR_BLOCK_READ;
-
-    /* Account for saved input, if any. */
-    apr_brigade_length(input_brigade, 0, &bytes_read);
-
-    /* Ensure we don't hit a wall where we have a buffer too small
-     * for ap_get_brigade's filters to fetch us another bucket,
-     * surrender once we hit 80 bytes less than MAX_MEM_SPOOL
-     * (an arbitrary value).
-     */
-    while (bytes_read < MAX_MEM_SPOOL - 80
-           && (APR_BRIGADE_EMPTY(input_brigade)
-               || !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)))) {
-        status = ap_get_brigade(r->input_filters, temp_brigade,
-                                AP_MODE_READBYTES, block,
-                                MAX_MEM_SPOOL - bytes_read);
-        /* ap_get_brigade may return success with an empty brigade
-         * for a non-blocking read which would block
-         */
-        if (block == APR_NONBLOCK_READ
-            && ((status == APR_SUCCESS && APR_BRIGADE_EMPTY(temp_brigade))
-                || APR_STATUS_IS_EAGAIN(status))) {
-            break;
-        }
-        if (status != APR_SUCCESS) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01095)
-                          "prefetch request body failed to %pI (%s)"
-                          " from %s (%s)",
-                          p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
-                          c->client_ip, c->remote_host ? c->remote_host: "");
-            return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
-        }
-
-        apr_brigade_length(temp_brigade, 1, &bytes);
-        bytes_read += bytes;
-
-        /*
-         * Save temp_brigade in input_brigade. (At least) in the SSL case
-         * temp_brigade contains transient buckets whose data would get
-         * overwritten during the next call of ap_get_brigade in the loop.
-         * ap_save_brigade ensures these buckets to be set aside.
-         * Calling ap_save_brigade with NULL as filter is OK, because
-         * input_brigade already has been created and does not need to get
-         * created by ap_save_brigade.
-         */
-        status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p);
-        if (status != APR_SUCCESS) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01096)
-                          "processing prefetched request body failed"
-                          " to %pI (%s) from %s (%s)",
-                          p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
-                          c->client_ip, c->remote_host ? c->remote_host: "");
-            return HTTP_INTERNAL_SERVER_ERROR;
-        }
+    rv = ap_proxy_prefetch_input(r, req->backend, input_brigade,
+                                 req->prefetch_nonblocking ? APR_NONBLOCK_READ
+                                                           : APR_BLOCK_READ,
+                                 &bytes_read, MAX_MEM_SPOOL);
+    if (rv != OK) {
+        return rv;
     }
 
     /* Use chunked request body encoding or send a content-length body?
@@ -826,7 +591,7 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
     else if (req->old_cl_val) {
         if (r->input_filters == r->proto_input_filters) {
             if (!ap_parse_strict_length(&req->cl_val, req->old_cl_val)) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01085)
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01085)
                               "could not parse request Content-Length (%s)",
                               req->old_cl_val);
                 return HTTP_BAD_REQUEST;
@@ -866,7 +631,8 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
         /* If we have to spool the body, do it now, before connecting or
          * reusing the backend connection.
          */
-        rv = spool_reqbody_cl(req, &bytes);
+        rv = ap_proxy_spool_input(r, p_conn, input_brigade,
+                                  &bytes, MAX_MEM_SPOOL);
         if (rv != OK) {
             return rv;
         }
index 7c3372c555f199a7f25da881835e07f3c2abd946..573668e20bf1e30c99c13911178b77e904e9b5ff 100644 (file)
@@ -4011,6 +4011,268 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
     return OK;
 }
 
+PROXY_DECLARE(int) ap_proxy_prefetch_input(request_rec *r,
+                                           proxy_conn_rec *backend,
+                                           apr_bucket_brigade *input_brigade,
+                                           apr_read_type_e block,
+                                           apr_off_t *bytes_read,
+                                           apr_off_t max_read)
+{
+    apr_pool_t *p = r->pool;
+    conn_rec *c = r->connection;
+    apr_bucket_brigade *temp_brigade;
+    apr_status_t status;
+    apr_off_t bytes;
+
+    *bytes_read = 0;
+    if (max_read < APR_BUCKET_BUFF_SIZE) {
+        max_read = APR_BUCKET_BUFF_SIZE;
+    }
+
+    /* Prefetch max_read bytes
+     *
+     * This helps us avoid any election of C-L v.s. T-E
+     * request bodies, since we are willing to keep in
+     * memory this much data, in any case.  This gives
+     * us an instant C-L election if the body is of some
+     * reasonable size.
+     */
+    temp_brigade = apr_brigade_create(p, input_brigade->bucket_alloc);
+
+    /* Account for saved input, if any. */
+    apr_brigade_length(input_brigade, 0, bytes_read);
+
+    /* Ensure we don't hit a wall where we have a buffer too small for
+     * ap_get_brigade's filters to fetch us another bucket, surrender
+     * once we hit 80 bytes (an arbitrary value) less than max_read.
+     */
+    while (*bytes_read < max_read - 80
+           && (APR_BRIGADE_EMPTY(input_brigade)
+               || !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)))) {
+        status = ap_get_brigade(r->input_filters, temp_brigade,
+                                AP_MODE_READBYTES, block,
+                                max_read - *bytes_read);
+        /* ap_get_brigade may return success with an empty brigade
+         * for a non-blocking read which would block
+         */
+        if (block == APR_NONBLOCK_READ
+                && ((status == APR_SUCCESS && APR_BRIGADE_EMPTY(temp_brigade))
+                    || APR_STATUS_IS_EAGAIN(status))) {
+            break;
+        }
+        if (status != APR_SUCCESS) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01095)
+                          "prefetch request body failed to %pI (%s)"
+                          " from %s (%s)", backend->addr,
+                          backend->hostname ? backend->hostname : "",
+                          c->client_ip, c->remote_host ? c->remote_host : "");
+            return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+        }
+
+        apr_brigade_length(temp_brigade, 1, &bytes);
+        *bytes_read += bytes;
+
+        /*
+         * Save temp_brigade in input_brigade. (At least) in the SSL case
+         * temp_brigade contains transient buckets whose data would get
+         * overwritten during the next call of ap_get_brigade in the loop.
+         * ap_save_brigade ensures these buckets to be set aside.
+         * Calling ap_save_brigade with NULL as filter is OK, because
+         * input_brigade already has been created and does not need to get
+         * created by ap_save_brigade.
+         */
+        status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p);
+        if (status != APR_SUCCESS) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01096)
+                          "processing prefetched request body failed"
+                          " to %pI (%s) from %s (%s)", backend->addr,
+                          backend->hostname ? backend->hostname : "",
+                          c->client_ip, c->remote_host ? c->remote_host : "");
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_read_input(request_rec *r,
+                                       proxy_conn_rec *backend,
+                                       apr_bucket_brigade *bb,
+                                       apr_off_t max_read)
+{
+    apr_bucket_alloc_t *bucket_alloc = bb->bucket_alloc;
+    apr_read_type_e block = (backend->connection) ? APR_NONBLOCK_READ
+                                                  : APR_BLOCK_READ;
+    apr_status_t status;
+    int rv;
+
+    for (;;) {
+        apr_brigade_cleanup(bb);
+        status = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+                                block, max_read);
+        if (block == APR_BLOCK_READ
+                || (!(status == APR_SUCCESS && APR_BRIGADE_EMPTY(bb))
+                    && !APR_STATUS_IS_EAGAIN(status))) {
+            break;
+        }
+
+        /* Flush and retry (blocking) */
+        apr_brigade_cleanup(bb);
+        rv = ap_proxy_pass_brigade(bucket_alloc, r, backend,
+                                   backend->connection, bb, 1);
+        if (rv != OK) {
+            return rv;
+        }
+        block = APR_BLOCK_READ;
+    }
+
+    if (status != APR_SUCCESS) {
+        conn_rec *c = r->connection;
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02608)
+                      "read request body failed to %pI (%s)"
+                      " from %s (%s)", backend->addr,
+                      backend->hostname ? backend->hostname : "",
+                      c->client_ip, c->remote_host ? c->remote_host : "");
+        return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+    }
+
+    return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_spool_input(request_rec *r,
+                                        proxy_conn_rec *backend,
+                                        apr_bucket_brigade *input_brigade,
+                                        apr_off_t *bytes_spooled,
+                                        apr_off_t max_mem_spool)
+{
+    apr_pool_t *p = r->pool;
+    int seen_eos = 0, rv = OK;
+    apr_status_t status = APR_SUCCESS;
+    apr_bucket_alloc_t *bucket_alloc = input_brigade->bucket_alloc;
+    apr_bucket_brigade *body_brigade;
+    apr_bucket *e;
+    apr_off_t bytes, fsize = 0;
+    apr_file_t *tmpfile = NULL;
+    apr_off_t limit;
+
+    *bytes_spooled = 0;
+    body_brigade = apr_brigade_create(p, bucket_alloc);
+
+    limit = ap_get_limit_req_body(r);
+
+    do {
+        if (APR_BRIGADE_EMPTY(input_brigade)) {
+            rv = ap_proxy_read_input(r, backend, input_brigade,
+                                     HUGE_STRING_LEN);
+            if (rv != OK) {
+                return rv;
+            }
+        }
+
+        /* If this brigade contains EOS, either stop or remove it. */
+        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+            seen_eos = 1;
+        }
+
+        apr_brigade_length(input_brigade, 1, &bytes);
+
+        if (*bytes_spooled + bytes > max_mem_spool) {
+            /*
+             * LimitRequestBody does not affect Proxy requests (Should it?).
+             * Let it take effect if we decide to store the body in a
+             * temporary file on disk.
+             */
+            if (limit && (*bytes_spooled + bytes > limit)) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01088)
+                              "Request body is larger than the configured "
+                              "limit of %" APR_OFF_T_FMT, limit);
+                return HTTP_REQUEST_ENTITY_TOO_LARGE;
+            }
+            /* can't spool any more in memory; write latest brigade to disk */
+            if (tmpfile == NULL) {
+                const char *temp_dir;
+                char *template;
+
+                status = apr_temp_dir_get(&temp_dir, p);
+                if (status != APR_SUCCESS) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01089)
+                                  "search for temporary directory failed");
+                    return HTTP_INTERNAL_SERVER_ERROR;
+                }
+                apr_filepath_merge(&template, temp_dir,
+                                   "modproxy.tmp.XXXXXX",
+                                   APR_FILEPATH_NATIVE, p);
+                status = apr_file_mktemp(&tmpfile, template, 0, p);
+                if (status != APR_SUCCESS) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01090)
+                                  "creation of temporary file in directory "
+                                  "%s failed", temp_dir);
+                    return HTTP_INTERNAL_SERVER_ERROR;
+                }
+            }
+            for (e = APR_BRIGADE_FIRST(input_brigade);
+                 e != APR_BRIGADE_SENTINEL(input_brigade);
+                 e = APR_BUCKET_NEXT(e)) {
+                const char *data;
+                apr_size_t bytes_read, bytes_written;
+
+                apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
+                status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
+                if (status != APR_SUCCESS) {
+                    const char *tmpfile_name;
+
+                    if (apr_file_name_get(&tmpfile_name, tmpfile) != APR_SUCCESS) {
+                        tmpfile_name = "(unknown)";
+                    }
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01091)
+                                  "write to temporary file %s failed",
+                                  tmpfile_name);
+                    return HTTP_INTERNAL_SERVER_ERROR;
+                }
+                AP_DEBUG_ASSERT(bytes_read == bytes_written);
+                fsize += bytes_written;
+            }
+            apr_brigade_cleanup(input_brigade);
+        }
+        else {
+
+            /*
+             * Save input_brigade in body_brigade. (At least) in the SSL case
+             * input_brigade contains transient buckets whose data would get
+             * overwritten during the next call of ap_get_brigade in the loop.
+             * ap_save_brigade ensures these buckets to be set aside.
+             * Calling ap_save_brigade with NULL as filter is OK, because
+             * body_brigade already has been created and does not need to get
+             * created by ap_save_brigade.
+             */
+            status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p);
+            if (status != APR_SUCCESS) {
+                return HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+        }
+
+        *bytes_spooled += bytes;
+    } while (!seen_eos);
+
+    APR_BRIGADE_CONCAT(input_brigade, body_brigade);
+    if (tmpfile) {
+        apr_brigade_insert_file(input_brigade, tmpfile, 0, fsize, p);
+    }
+    if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
+        e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+    }
+    if (tmpfile) {
+        /* We dropped metadata buckets when spooling to tmpfile,
+         * terminate with EOS to allow for flushing in a one go.
+         */
+        e = apr_bucket_eos_create(bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+    }
+    return OK;
+}
+
 PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
                                          request_rec *r, proxy_conn_rec *p_conn,
                                          conn_rec *origin, apr_bucket_brigade *bb,