Bug 41646

Summary: wrong headers are sent for HEAD requests when ProxyErrorOverride enabled
Product: Apache httpd-2 Reporter: Stuart Children <stuart>
Component: mod_proxyAssignee: Apache HTTPD Bugs Mailing List <bugs>
Status: RESOLVED LATER    
Severity: normal CC: jtharp, scaglione, taffy-tyler6464
Priority: P2 Keywords: MassUpdate
Version: 2.2.4   
Target Milestone: ---   
Hardware: PC   
OS: Linux   

Description Stuart Children 2007-02-16 10:25:18 UTC
Discovered this whilst working on bugs #39245 and #41644.

Set up Apache as a reverse proxy:

    ProxyPass /rproxy http://localhost:12345
    ProxyPassReverse /rproxy http://localhost:12345

Now request something which does not exist on the backend server:

$ curl --get --verbose http://localhost:2200/rproxy/notfound
* About to connect() to localhost port 2200
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 2200
> GET /rproxy/notfound HTTP/1.1
> User-Agent: curl/7.15.5 (i686-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b
zlib/1.2.3 libidn/0.6.5
> Host: localhost:2200
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< Date: Fri, 16 Feb 2007 17:53:50 GMT
< Server: Apache/2.2.4 (Unix)
< Last-Modified: Fri, 16 Feb 2007 17:53:24 GMT
< ETag: "452dd8-4b-a6ec0d00"
< Accept-Ranges: bytes
< Content-Length: 75
< Content-Type: text/plain
The object you requested was NOT FOUND (404)
on this, the backend, server.
* Connection #0 to host localhost left intact
* Closing connection #0

OK, great. And now make a HEAD request for the same URL:

$ curl --head --verbose http://localhost:2200/rproxy/notfound
* About to connect() to localhost port 2200
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 2200
> HEAD /rproxy/notfound HTTP/1.1
> User-Agent: curl/7.15.5 (i686-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b
zlib/1.2.3 libidn/0.6.5
> Host: localhost:2200
> Accept: */*
> 
< HTTP/1.1 404 Not Found
HTTP/1.1 404 Not Found
< Date: Fri, 16 Feb 2007 17:54:20 GMT
Date: Fri, 16 Feb 2007 17:54:20 GMT
< Server: Apache/2.2.4 (Unix)
Server: Apache/2.2.4 (Unix)
< Last-Modified: Fri, 16 Feb 2007 17:53:24 GMT
Last-Modified: Fri, 16 Feb 2007 17:53:24 GMT
< ETag: "452dd8-4b-a6ec0d00"
ETag: "452dd8-4b-a6ec0d00"
< Accept-Ranges: bytes
Accept-Ranges: bytes
< Content-Length: 75
Content-Length: 75
< Content-Type: text/plain
Content-Type: text/plain

* Connection #0 to host localhost left intact
* Closing connection #0

Note that the Last-Modified, ETag, Content-Length, etc headers are identical in
both cases. Now, add the following configuration to the frontend server:

    ProxyErrorOverride On
    ErrorDocument 404 /error.txt

and repeat the GET request:

$ curl --get --verbose http://localhost:2200/rproxy/notfound
* About to connect() to localhost port 2200
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 2200
> GET /rproxy/notfound HTTP/1.1
> User-Agent: curl/7.15.5 (i686-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b
zlib/1.2.3 libidn/0.6.5
> Host: localhost:2200
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< Date: Fri, 16 Feb 2007 17:56:16 GMT
< Server: Apache/2.2.5-dev (Unix)
< Last-Modified: Fri, 16 Feb 2007 16:49:02 GMT
< ETag: "33399a-21-c0ba9b80"
< Accept-Ranges: bytes
< Content-Length: 33
< Content-Type: text/plain
I am an error from the frontend.
* Connection #0 to host localhost left intact
* Closing connection #0

As expected, we get the contents of the ErrorDocument specified on the frontend
server, which has a different ETag, Content-Length, etc. Now repeat the HEAD:

$ curl --head --verbose http://localhost:2200/rproxy/notfound
* About to connect() to localhost port 2200
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 2200
> HEAD /rproxy/notfound HTTP/1.1
> User-Agent: curl/7.15.5 (i686-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b
zlib/1.2.3 libidn/0.6.5
> Host: localhost:2200
> Accept: */*
> 
< HTTP/1.1 404 Not Found
HTTP/1.1 404 Not Found
< Date: Fri, 16 Feb 2007 17:57:09 GMT
Date: Fri, 16 Feb 2007 17:57:09 GMT
< Server: Apache/2.2.4 (Unix)
Server: Apache/2.2.4 (Unix)
< Last-Modified: Fri, 16 Feb 2007 17:53:24 GMT
Last-Modified: Fri, 16 Feb 2007 17:53:24 GMT
< ETag: "452dd8-4b-a6ec0d00"
ETag: "452dd8-4b-a6ec0d00"
< Accept-Ranges: bytes
Accept-Ranges: bytes
< Content-Length: 75
Content-Length: 75
< Content-Type: text/plain
Content-Type: text/plain

* Connection #0 to host localhost left intact
* Closing connection #0

Whoops - we've still got the original headers (ie: those from the backend server).

This was a bit of a pain to trackdown, and it's late on a Friday so I'm going to
skip all my "working". I think the problem is this:

In ap_proxy_http_process_response() within mod_proxy_http.c, it says:

    /* send body - but only if a body is expected */
    if ((!r->header_only) &&                   /* not HEAD request */
        !interim_response &&                   /* not any 1xx response */
        (r->status != HTTP_NO_CONTENT) &&      /* not 204 */
        (r->status != HTTP_NOT_MODIFIED)) {    /* not 304 */
...
    else if (!interim_response) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
                     "proxy: header only");

        /* Pass EOS bucket down the filter chain. */
        e = apr_bucket_eos_create(c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, e);
        if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
            || c->aborted) {
            /* Ack! Phbtt! Die! User aborted! */
            backend->close = 1;  /* this causes socket close below */
        }
        apr_brigade_cleanup(bb);
    }

So basically if this expression is true:

    r->header_only ||
      (r->status == HTTP_NO_CONTENT) ||
      (r->status != HTTP_NOT_MODIFIED)

then an EOS bucket is pushed onto the chain. This means that the current headers
get forced out first, and when ap_internal_redirect() is invoked later (to load
the error document) the new headers there are ignored.

The code that adds the EOS bucket does not exist in 2.0.x. Looking at
http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/proxy/mod_proxy_http.c?view=log
we can see it initially got added in revision 159533, and the exclusion of
interim_responses in revision 159671. I'm not 100% clear on the exact situation
you do/do not need to add an EOS at this point, so I'm not going to attempt a
patch right now.
Comment 1 Nick Kew 2007-09-08 11:33:46 UTC
*** Bug 22951 has been marked as a duplicate of this bug. ***
Comment 2 William A. Rowe Jr. 2018-11-07 21:09:32 UTC
Please help us to refine our list of open and current defects; this is a mass update of old and inactive Bugzilla reports which reflect user error, already resolved defects, and still-existing defects in httpd.

As repeatedly announced, the Apache HTTP Server Project has discontinued all development and patch review of the 2.2.x series of releases. The final release 2.2.34 was published in July 2017, and no further evaluation of bug reports or security risks will be considered or published for 2.2.x releases. All reports older than 2.4.x have been updated to status RESOLVED/LATER; no further action is expected unless the report still applies to a current version of httpd.

If your report represented a question or confusion about how to use an httpd feature, an unexpected server behavior, problems building or installing httpd, or working with an external component (a third party module, browser etc.) we ask you to start by bringing your question to the User Support and Discussion mailing list, see [https://httpd.apache.org/lists.html#http-users] for details. Include a link to this Bugzilla report for completeness with your question.

If your report was clearly a defect in httpd or a feature request, we ask that you retest using a modern httpd release (2.4.33 or later) released in the past year. If it can be reproduced, please reopen this bug and change the Version field above to the httpd version you have reconfirmed with.

Your help in identifying defects or enhancements still applicable to the current httpd server software release is greatly appreciated.