Re: Adding Post Data to a SubRequest

Torsten Förtsch <[email protected]>
Newsgroups gmane.comp.apache.mod-perl
Message-ID <[email protected]>
On Wednesday, 14 December 2011 16:03:18 Jordan Michaels wrote:
> 1) Is there an ideal way to add POST data to a subrequest that I'm 
> missing here?

No, subrequests don't have a request body by definition. It's hard coded in 
httpd 2.2.x.

Enclosed you find a patch for httpd 2.2.21 that makes is possible to attach 
the request body of the main request to a subreq made by mod_includes <!--
#include virtual ... -->. The intent at the time of writing of the patch was 
to send a POST request via mod_proxy to a backend server passing the original 
req body where the subreq was initiated by the mod_include response handler. 
It turned out to be quite tricky.

Maybe it helps.

Torsten Förtsch

-- 
Need professional modperl support? Hire me! (http://foertsch.name)

Like fantasy? http://kabatinte.net
virtual_include_method-2.2.21.patch (text/x-patch, 12.7 KB)
--- modules/filters/mod_include.c.orig	2011-10-07 14:49:47.461434968 +0200
+++ modules/filters/mod_include.c	2011-10-07 13:16:15.633377775 +0200
@@ -1654,6 +1654,7 @@
                                    apr_bucket_brigade *bb)
 {
     request_rec *r = f->r;
+    enum {METHOD_GET, METHOD_POST, METHOD_INHERIT} method;
 
     if (!ctx->argc) {
         ap_log_rerror(APLOG_MARK,
@@ -1672,6 +1673,8 @@
         return APR_SUCCESS;
     }
 
+    method=METHOD_GET;
+
     while (1) {
         char *tag     = NULL;
         char *tag_val = NULL;
@@ -1684,6 +1687,29 @@
             break;
         }
 
+        if (tag[0] == 'm' && !strcmp(tag, "method")) {
+            if ((tag_val[0] == 'g' || tag_val[0] == 'G')
+                && !strcasecmp(tag_val, "get")) {
+                method=METHOD_GET;
+            }
+            else if ((tag_val[0] == 'p' || tag_val[0] == 'P')
+                && !strcasecmp(tag_val, "post")) {
+                method=METHOD_POST;
+            }
+            else if ((tag_val[0] == 'i' || tag_val[0] == 'I')
+                && !strcasecmp(tag_val, "inherit")) {
+                method=METHOD_INHERIT;
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
+                              "\"%s\" to parameter \"method\" of tag "
+                              "include in %s", tag_val, r->filename);
+                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+                break;
+            }
+            continue;
+        }
+
         if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
                           "\"%s\" to tag include in %s", tag, r->filename);
@@ -1710,7 +1736,15 @@
             }
         }
         else {
-            rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
+            if (method == METHOD_GET
+                || (method == METHOD_INHERIT && strcmp(r->method, "POST"))) {
+                rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
+            }
+            else {              /* POST */
+                method = METHOD_POST;
+                apr_table_setn(r->notes, "subreq-pass-request-body", "1");
+                rr = ap_sub_req_method_uri("POST", parsed_string, r, f->next);
+            }
         }
 
         if (!error_fmt && rr->status != HTTP_OK) {
@@ -1732,10 +1766,22 @@
             ap_set_module_config(rr->request_config, &include_module, r);
         }
 
+        /* XXX: would be good to check for EOS on rr->input_filters
+         * if method==POST and issue a warning if so.
+         */
+
         if (!error_fmt && ap_run_sub_req(rr)) {
             error_fmt = "unable to include \"%s\" in parsed file %s";
         }
 
+        /* method="POST" must be specified *before* *each*
+         * virtual="..."
+         */
+        if (method != METHOD_GET) {
+            method = METHOD_GET;
+            apr_table_unset(r->notes, "subreq-pass-request-body");
+        }
+
         if (error_fmt) {
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
                           r->filename);
--- server/core.c.orig	2011-09-08 17:59:38.000000000 +0200
+++ server/core.c	2011-10-07 13:16:15.633377775 +0200
@@ -3692,17 +3692,11 @@
 
     ap_allow_standard_methods(r, MERGE_ALLOW, M_GET, M_OPTIONS, M_POST, -1);
 
-    /* If filters intend to consume the request body, they must
-     * register an InputFilter to slurp the contents of the POST
-     * data from the POST input stream.  It no longer exists when
-     * the output filters are invoked by the default handler.
-     */
-    if ((errstatus = ap_discard_request_body(r)) != OK) {
-        return errstatus;
-    }
-
     if (r->method_number == M_GET || r->method_number == M_POST) {
         if (r->finfo.filetype == 0) {
+            if ((errstatus = ap_discard_request_body(r)) != OK) {
+                return errstatus;
+            }
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                           "File does not exist: %s", r->filename);
             return HTTP_NOT_FOUND;
@@ -3712,6 +3706,9 @@
          * raw I/O on a dir.
          */
         if (r->finfo.filetype == APR_DIR) {
+            if ((errstatus = ap_discard_request_body(r)) != OK) {
+                return errstatus;
+            }
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                           "Attempt to serve directory: %s", r->filename);
             return HTTP_NOT_FOUND;
@@ -3720,6 +3717,9 @@
         if ((r->used_path_info != AP_REQ_ACCEPT_PATH_INFO) &&
             r->path_info && *r->path_info)
         {
+            if ((errstatus = ap_discard_request_body(r)) != OK) {
+                return errstatus;
+            }
             /* default to reject */
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                           "File does not exist: %s",
@@ -3740,6 +3740,9 @@
 
             req_cfg = ap_get_module_config(r->request_config, &core_module);
             if (!req_cfg->deliver_script) {
+                if ((errstatus = ap_discard_request_body(r)) != OK) {
+                    return errstatus;
+                }
                 /* The flag hasn't been set for this request. Punt. */
                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                               "This resource does not accept the %s method.",
@@ -3755,6 +3758,9 @@
                                                 ? 0 : APR_SENDFILE_ENABLED)
 #endif
                                     , 0, r->pool)) != APR_SUCCESS) {
+            if ((errstatus = ap_discard_request_body(r)) != OK) {
+                return errstatus;
+            }
             ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
                           "file permissions deny server access: %s", r->filename);
             return HTTP_FORBIDDEN;
@@ -3813,6 +3819,11 @@
         APR_BRIGADE_INSERT_TAIL(bb, e);
 
         status = ap_pass_brigade(r->output_filters, bb);
+
+        if ((errstatus = ap_discard_request_body(r)) != OK) {
+            return errstatus;
+        }
+
         if (status == APR_SUCCESS
             || r->status != HTTP_OK
             || c->aborted) {
@@ -3827,6 +3838,9 @@
         }
     }
     else {              /* unusual method (not GET or POST) */
+        if ((errstatus = ap_discard_request_body(r)) != OK) {
+            return errstatus;
+        }
         if (r->method_number == M_INVALID) {
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                           "Invalid method in request %s", r->the_request);
--- modules/http/http_filters.c.orig	2011-05-06 15:14:27.000000000 +0200
+++ modules/http/http_filters.c	2011-10-07 14:21:50.247870961 +0200
@@ -321,8 +321,9 @@
         /* 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)) &&
-            f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1) &&
+             (ctx->state == BODY_LENGTH && ctx->remaining > 0)) &&
+            f->r->expecting_100==1 &&
+            f->r->proto_num >= HTTP_VERSION(1,1) &&
             !(f->r->eos_sent || f->r->bytes_sent)) {
             if (!ap_is_HTTP_SUCCESS(f->r->status)) {
                 ctx->state = BODY_NONE;
@@ -331,6 +332,9 @@
                 char *tmp;
                 int len;
 
+                /* don't send it twice */
+                f->r->expecting_100++;
+
                 /* if we send an interim response, we're no longer
                  * in a state of expecting one.
                  */
@@ -550,6 +554,7 @@
     if (ctx->state == BODY_LENGTH && ctx->remaining == 0) {
         e = apr_bucket_eos_create(f->c->bucket_alloc);
         APR_BRIGADE_INSERT_TAIL(b, e);
+        ctx->eos_sent = 1;
     }
 
     /* We have a limit in effect. */
@@ -872,6 +877,78 @@
 
 }
 
+static apr_status_t check_input_body_length(request_rec *r)
+{
+    const char *tenc;
+    const char *lenp;
+
+    /* avoid recursion */
+    if (r->status == HTTP_REQUEST_ENTITY_TOO_LARGE) {
+        return APR_SUCCESS;
+    }
+
+    tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+    lenp = apr_table_get(r->headers_in, "Content-Length");
+
+    if( lenp && !tenc ) {
+        apr_off_t len=0, limit;
+        char *endstr;
+
+        if (apr_strtoff(&len, lenp, &endstr, 10)
+            || endstr == lenp
+            || *endstr
+            || len < 0) {
+            /* invalid CL */
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                          "Invalid Content-Length");
+            return HTTP_REQUEST_ENTITY_TOO_LARGE;
+        }
+
+        limit = ap_get_limit_req_body(r);
+        if (limit && limit < len) {
+            /* body too large */
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                          "Requested content-length of %" APR_OFF_T_FMT
+                          " is larger than the configured limit"
+                          " of %" APR_OFF_T_FMT, len, limit);
+            return HTTP_REQUEST_ENTITY_TOO_LARGE;
+        }
+    }
+
+    return APR_SUCCESS;
+ }
+
+static void http_send_continue_100(request_rec *r, apr_bucket_brigade *bb)
+{
+    const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+    const char *lenp = apr_table_get(r->headers_in, "Content-Length");
+
+    if (r->expecting_100==1 &&  /* expected reply is not sent yet */
+        r->proto_num >= HTTP_VERSION(1,1) &&
+        ((tenc && !strcasecmp(tenc, "chunked")) || lenp)) {
+        
+        struct iovec vec[3];
+
+        vec[0].iov_base = (void *)(AP_SERVER_PROTOCOL " ");
+        vec[0].iov_len  = sizeof(AP_SERVER_PROTOCOL " ")-1;
+        vec[1].iov_base = (void *)(ap_get_status_line(100));
+        vec[1].iov_len  = strlen(vec[1].iov_base);
+        vec[2].iov_base = (void *)(CRLF CRLF);
+        vec[2].iov_len  = sizeof(CRLF CRLF) - 1;
+#if APR_CHARSET_EBCDIC
+        {
+            char *tmp;
+            apr_size_t len;
+            tmp = apr_pstrcatv(r->pool, vec, 3, &len);
+            ap_xlate_proto_to_ascii(tmp, len);
+            apr_brigade_write(bb, NULL, NULL, tmp, len);
+        }
+#else
+        apr_brigade_writev(bb, NULL, NULL, vec, 3);
+#endif
+    }
+}
+
 /* fill "bb" with a barebones/initial HTTP response header */
 static void basic_http_header(request_rec *r, apr_bucket_brigade *bb,
                               const char *protocol)
@@ -886,6 +963,9 @@
         return;
     }
 
+    /* this is the last chance to signal the client to send the req body */
+    http_send_continue_100(r, bb);
+
     /* Output the HTTP/1.x Status-Line and the Date and Server fields */
 
     vec[0].iov_base = (void *)protocol;
@@ -1125,6 +1205,7 @@
     header_filter_ctx *ctx = f->ctx;
     const char *ctype;
     ap_bucket_error *eb = NULL;
+    int status;
 
     AP_DEBUG_ASSERT(!r->main);
 
@@ -1156,12 +1237,17 @@
         }
     }
     if (eb) {
-        int status;
-
         status = eb->status;
         apr_brigade_cleanup(b);
         ap_die(status, r);
         return AP_FILTER_ERROR;
+    }
+
+    /* check request body in case the http input filter has not been called */
+    if ((status = check_input_body_length(r)) != APR_SUCCESS) {
+        apr_brigade_cleanup(b);
+        ap_die(status, r);
+        return AP_FILTER_ERROR;
     }
 
     if (r->assbackwards) {
--- modules/proxy/mod_proxy_http.c.orig	2011-02-14 09:04:57.000000000 +0100
+++ modules/proxy/mod_proxy_http.c	2011-10-07 13:16:15.641378735 +0200
@@ -935,8 +935,11 @@
      * input_brigade and jump past all of the request body logic...
      * Reading anything with ap_get_brigade is likely to consume the
      * main request's body or read beyond EOS - which would be unplesant.
+     * 
+     * An exception: if the main req is marked with the
+     * "subreq-pass-request-body" note the subreq may consume the body.
      */
-    if (r->main) {
+    if (r->main && !apr_table_get(r->main->notes, "subreq-pass-request-body")) {
         /* XXX: Why DON'T sub-requests use keepalives? */
         p_conn->close++;
         if (old_cl_val) {
--- server/protocol.c.orig	2011-05-07 13:39:29.000000000 +0200
+++ server/protocol.c	2011-10-07 13:16:15.641378735 +0200
@@ -1100,8 +1100,9 @@
     /* did the original request have a body?  (e.g. POST w/SSI tags)
      * if so, make sure the subrequest doesn't inherit body headers
      */
-    if (apr_table_get(r->headers_in, "Content-Length")
-        || apr_table_get(r->headers_in, "Transfer-Encoding")) {
+    if (!apr_table_get(r->notes, "subreq-pass-request-body")
+        && (apr_table_get(r->headers_in, "Content-Length")
+            || apr_table_get(r->headers_in, "Transfer-Encoding"))) {
         strip_headers_request_body(rnew);
     }
     rnew->subprocess_env  = apr_table_copy(rnew->pool, r->subprocess_env);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.