0

I've run into a strange issue, and I'm at a loss as to my next steps in debugging. I'm hoping the community can pitch in some ideas.

I'm using the following stack:

PHP 7.2.34-21+ubuntu16.04.1+deb.sury.org+1 (fpm-fcgi) (built: May  1 2021 11:52:36)
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.34-21+ubuntu16.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
Server version: Apache/2.4.39 (Ubuntu)
Wordpress 4.9.16

On AWS services. Obviously, there's some overhead due to the framework but I can dumb the code down to something along the lines of this :

// Framework stuff + file handling logic
$data = 'Imagine this is a 1000-byte binary message (pdf).';

// Headers, 335 bytes large
header('date: Sat, 03 Jul 2021 05:06:41 GMT');
header('server: Apache/2.4');
header('expires: Wed, 11 Jan 1984 05:00:00 GMT');
header('cache-control: no-cache, must-revalidate, max-age=0');
header('content-disposition: inline; filename=window-sticker.pdf');
header('strict-transport-security: max-age=1209600;');
header('content-length: 1000'); // Actually use strlen($data); for this
header('x-ua-compatible: IE=edge');
header('cache-control: public');
header('content-type: application/pdf');

echo $data;

exit();

Now here's the kicker. This works fine on a bunch of other sites, that as far as I can tell use the same apache sites-enabled configuration and similar .htaccess files. But it might still be a server/network/etc.. type error so I could be missing something.

I have this one site, however, where this code breaks in the following way:

  • Tools that don't enforce content-length download/display this perfectly fine (chrome for instance).
  • Tools that do keep track of content-length fail or throw a notice (safari, curl). Curl gives me :
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7ff13c008200)
> GET /redacted/path/to/controller?p=abcdef HTTP/2
> Host: www.redacted-somesite.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
  0     0    0     0    0     0      0      0 --:--:--  0:00:05 --:--:--     0< HTTP/2 200
< date: Sun, 04 Jul 2021 17:36:24 GMT
< server: Apache/2.4
< expires: Wed, 11 Jan 1984 05:00:00 GMT
< cache-control: no-cache, must-revalidate, max-age=0
< content-disposition: inline; filename=window-sticker.pdf
< strict-transport-security: max-age=1209600;
< content-length: 1000
< x-ua-compatible: IE=edge
< cache-control: public
< content-type: application/pdf
<
* HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
* stopped the pause stream!
  0  1000    0     0    0     0      0      0 --:--:--  0:00:05 --:--:--     0
* Connection #0 to host www.redacted-somesite.com left intact
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
* Closing connection 0

Things I have checked:

  • The content length IS correctly set, the body is the same size as what is set in that header.
  • The data IS being output since tools like chrome can get the full file
  • Removing the content-length header makes this work in all tools.

And here we are, I'm not sure why things are failing. My current theory is that somehow, for this site some silent error might be writing to the buffer before the headers are sent out. But when I check the binary data sent from the server in a hex tool, it's an exact match.. So I'm at a loss. Maybe there's some compression layer screwing with me?

Any ideas would be amazing.

Thanks!

Pomme.Verte
  • 1,782
  • 1
  • 15
  • 32
  • Does this answer your question? [HTTP Headers for File Downloads](https://stackoverflow.com/questions/386845/http-headers-for-file-downloads) – Top-Master Jul 03 '21 at 16:41
  • @Top-Master Not really. I actually do want to display it in the browser and I've included both `content-type` and `content-disposition` headers. – Pomme.Verte Jul 03 '21 at 16:44
  • What is the exact Apache version you're using, including the fpm module and what is the exact php-fpm version (php version) in use [for the server you experience the issue with]? This looks like a software incompatibility/misconfiguration. [conent-length](https://stackoverflow.com/questions/2773396/whats-the-content-length-field-in-http-header) should be well defined. I assume chunked transfer is not in action (you clearly write header size is taken into account and yes, it should not). Use wireshark or similar to verify on the transport level. – hakre Jul 03 '21 at 17:18
  • I always clear the output buffer with ob_clean() after sending the headers and before sending the payload, it tends to eliminate a lot of weird problems like this. It's sort of cargo-cult, but give it a try before going too deep down the rabbit hole. – Rob Ruchte Jul 03 '21 at 17:19
  • Can you try yourself with curl on the command-line? It has the nice `-v` switch so it shows request and response headers (combine with `-i`), pipe into output file then it shows the exact bytes, and the bytes written can be seen by the size of the output file, too. – hakre Jul 03 '21 at 17:45
  • @hakre I have updated my post with data on server versions but also output from curl -v. It looks like I was mistaken, this time around I was missing way more than the size of the headers. So scrap that explanation. Could some chunking come into play? – Pomme.Verte Jul 03 '21 at 18:10
  • @RobRuchte I tried ob_clean() to no avail, sadly. – Pomme.Verte Jul 03 '21 at 18:10
  • @D.Mill: Well, `content-length: 852699` is not 1000, perhaps it's worth to create an isolated test-script that just pushes out 1000 (or 500) bytes (e.g. str_repeat), but certainly, it won't change anything (you could better reduce with it thought, which can help with the overall trouble-shooting). interestingly curl shows only 1106 bytes of data and it also has error 1 at (92) - which you also wrote earlier about. Given a static PDF file works - does it? - what does the php-fpm and the php error log give? Also if php-fpm sends stderr to apache, what give apache error log? – hakre Jul 03 '21 at 18:51
  • Okay, if you have it only on one server, another thing after looking up the protocol error is not the configuration on that server, but if there is some middleware box that is misconfigured and it corrupts the data. perhaps it is unable to relay http2 traffic. The fix is to not use that middleware box. Check the setup of the AWS infra or delegate it to the person who is responsbile. As a workaround: Downgrading the protocol might help (take HTTP 1/1 or 1/0) or adding more buffers. But these are only workarounds, you want the problem fixed. – hakre Jul 03 '21 at 18:59

1 Answers1

0

Update to match OP's latest edit:

The tools you mention seem to no longer use HTTP v1.1 protocol, See similar answer about cURL's "HTTP/2 stream 0 was not closed cleanly" error.


Old answer:

Content-length should be exactly the same as file-size, but if it is, maybe you are missing some other headers, like:

Content-Transfer-Encoding: binary

If all headers are there, check max PHP execution.

See also example of download with resume support of another post.

Top-Master
  • 7,611
  • 5
  • 39
  • 71
  • I can confirm that the length is correct. I double-checked + this exact code is working fine on other sites. This error is specific to one setup only. I also tried adding that `content-transfer-encoding` header but it does not fix the issue. – Pomme.Verte Jul 03 '21 at 17:11
  • If all headers are there, check max PHP execution (and maybe add resume support, although, should not be required till you add "`Accept-Ranges: bytes`" header). – Top-Master Jul 03 '21 at 17:14
  • Content-Length is independent to Content-Transfer-Encoding: binary. And Accept-Ranges: bytes, too (it either needs implementation or frontend server handling). Also I wonder if this would run into PHP max execution, the OP would not have mentioned it. Perhaps a check of the logs? – hakre Jul 03 '21 at 17:24
  • This isn't a max execution issue, I can get this to work fine by simply using chrome and there's no indication of foul play in the logs. – Pomme.Verte Jul 03 '21 at 18:12
  • Edited: I hope the link I mentioned helps. – Top-Master Jul 03 '21 at 18:26