The patch r1331416 introduced a serious bug: In the case described in bug 52879 (a PHP script run as fastcgi through php-fpm and mod_proxy_fcgi which returns a Last-Modified header with no Status header), httpd (after fix r1331416) does indeed now return a 304 status (if a matching If-not-modified header is in the request) BUT the content of the php script is also sent. This breaks the http protocol as described in rfc2616 section 10.3.5: "The 304 response MUST NOT contain a message-body, and thus is always terminated by the first empty line after the header fields." This invalid behavior cause a serious problem when processed by a reverse proxy placed in front of the backends. When the proxy receives this invalid 304 response (containing a body), it immediately sends a 304 to the client and ignores the rest of the packet; but the next packets sent by the backend stay in the tcp stack (the proxy does not expect further content). The reverse proxy prepends these packets in the response to the next request routed to the same backend. This, of course, seriouly breaks our applications...
From the first quick try, I'm not able to reproduce the issue. My PHP script looks like this: <?php header('Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT');?> test At first I've tried request with If-Modified-Since before the date in PHP script. This returns 200 OK with body as expected: # curl --header 'If-Modified-Since: Tue, 14 Nov 1995 04:58:08 GMT' http://localhost/index.php -v > GET /index.php HTTP/1.1 > User-Agent: curl/7.32.0 > Host: localhost > Accept: */* > If-Modified-Since: Tue, 14 Nov 1995 04:58:08 GMT > < HTTP/1.1 200 OK < Date: Tue, 11 Nov 2014 09:41:25 GMT < Server: Apache/2.4.10 (Fedora) Phusion_Passenger/4.0.53 < X-Powered-By: PHP/5.5.18 < Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT < Transfer-Encoding: chunked < Content-Type: text/html; charset=UTF-8 < test Then I've tried with different If-Modified-Since to trigger to 304. This returns 304, but I don't see the php script content there: # curl --header 'If-Modified-Since: Wed, 15 Nov 1995 04:58:08 GMT' http://localhost/index.php -v > GET /index.php HTTP/1.1 > User-Agent: curl/7.32.0 > Host: localhost > Accept: */* > If-Modified-Since: Wed, 15 Nov 1995 04:58:08 GMT > < HTTP/1.1 304 Not Modified < Date: Tue, 11 Nov 2014 09:41:17 GMT < Server: Apache/2.4.10 (Fedora) Phusion_Passenger/4.0.53 < Are you doing anything differently? How do you reproduce it? Note that from the code, I think this should work properly, because the output brigade is cleaned-up before it's passed to output filters.
curl seems to be following the rfc2616 correctly, and immediately close connection after receiving the 304 header, so the bug won't show with curl. I can see the problem when using telnet to connect to the server and sending the request manually. Also, after some testing, it seems the bug is triggered only when the php script generate some large enough content. Here is how to reproduce: -> index.php: <?php header('Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT'); for( $i=0; $i<60; $i++) { echo( "testing testing testing testing testing testing testing testing testing testing testing\n" ); } flush(); ?> -> send request manually via telnet: telnet localhost 80 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET /index.php HTTP/1.1 Host: localhost Accept: */* If-Modified-Since: Tue, 15 Nov 1995 05:58:08 GMT HTTP/1.1 304 Not Modified Date: Tue, 11 Nov 2014 15:54:07 GMT Server: Apache/2.4.10 (Unix) testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing Connection closed by foreign host. -> Note that telnet close the connection after about 5 seconds of waiting And this is what I get when the date triggers to send content: telnet localhost 80 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET /index.php HTTP/1.1 Host: localhost Accept: */* If-Modified-Since: Tue, 14 Nov 1995 05:58:08 GMT HTTP/1.1 200 OK Date: Tue, 11 Nov 2014 16:04:03 GMT Server: Apache/2.4.10 (Unix) X-Powered-By: PHP/5.5.18 Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT Transfer-Encoding: chunked Content-Type: text/html 1028 testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing 478 testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing testing 0 Connection closed by foreign host.
After some more testing, it appears that when a 304 message is returned, httpd skips the rest of the first chunk (as seen when a 200 message is returned) but then sends all the following chunks. Maybe this will help find the problem...
Created attachment 32204 [details] proposed patch Stop handling body sent by fcgi backend after httpd sent 304 to the client.
Thanks, proposed patch 32204 solved the problem!
Committed to trunk in r1640495.
Proposed in 2.4.x.
Backported in 2.4.x in r1650677 Will be part of 2.4.11
This isn't completely solved by patch 32204. This small PHP PoC script triggers the same issue (tested with version 2.4.25): <?php http_response_code(304); echo("test"); flush();
This seems to be solved by http://svn.apache.org/viewvc?view=revision&revision=1837056. I'm not able to reproduce with Apache >=2.4.35