views:

103

answers:

3

I have the following script to let a visitor download a file:

header( 'Content-Type: application/octet-stream' );
header( 'Content-Transfer-Encoding: binary' );
header( 'Content-Disposition: attachment; filename=' . $fileName );
header( 'Content-Length: ' . filesize( $filePath ) );
header( 'Content-Description: Download' );
header( 'Cache-Control: private' );
header( 'Pragma: no-cache' );
header( 'Expires: 0' );

readfile( $filePath );
exit();

It doesn't work very well. (I've also put the filename in quotes, same result).

It behaves very slow, and sometimes the downloads even halt. In Opera especially, it halts on 99% of the download. Sometimes it even immediately shows 99% completed, then it starts downloading and halts at around 34%.

The server is a shared host, Mac OS X server.

With using Firefox's Live HTTP headers add-on, I've noticed the server adds aditional headers to the response:

HTTP/1.1 200 OK
Date: Thu, 18 Feb 2010 09:27:25 GMT
Server: Apache
X-Powered-By: PHP/5.2.12
Content-Transfer-Encoding: binary
Content-Disposition: attachment; filename=test.psd
Content-Length: 398635
Content-Description: Download
Cache-Control: private
Pragma: no-cache
Expires: 0
Content-Encoding: gzip // <-- expecially this one,
Vary: Accept-Encoding // <-- this one,
MS-Author-Via: DAV // <-- and this one
Keep-Alive: timeout=10, max=100
Connection: Keep-Alive
Content-Type: application/octet-stream

Could these be the cause of the problem?

When I run the script on my localhost everything works fine. Also, when I directly download files from this host, the speed is also fine and fluent.

I'm really rather clueless on this one. Your help is appeciated. Thank you in advance.

UPDATE:

I think I have narrowed the problem down to the bottleneck. The webserver automatically gzip compresses the output. When I removed the Content-Length header from my PHP script everything started downloading smooth. This makes sense: The value of the Content-Length doesn't match the actual gzipped output anymore. In PHP I read the uncompressed filesize to set the Content-Length header, but afterwards, Apache compresses it, and this is probably where the browsers choked.

I'll follow this question up with a question about how to set the correct Content-Length header size when the webserver automatically gzip compresses output.

A: 

Try unsetting the gzip-Content-Encoding.

Use ob_start() at the very beginning of your script; before setting your headers, use @ob_end_clean(); and right after it, explicitely set header("Content-Encoding:"); to try to unset any gzip-Encoding that might come in. At the end of your file place @ob_end_flush();.

The output buffering functions are handy to make the header-setting more failsafe, but probably not related to your problem. I just remember to ran into problems in a setup where the enclosing PHP code used ob_gzhandler and I needed to unset it.

initall
Hi initall, thanks for your response. I think I have narrowed the problem down. I'm afraid I'm not able to overwrite the `Content-Encoding` header, as it is set by the webserver. In other words, I believe the Apache server is configured to automatically gzip compress the output. So now when I remove `Content-Length` header from my PHP script, all works fine. And this makes sense, because the content length set by PHP doesn't match the actual content length of the gzip compressed file anymore. This is where the browsers probably choked a bit.
fireeyedboy
Ok, sounds reasonable. What I also did once in order to avoid zipping twice was something like that on top of the main script:`if (extension_loaded('zlib') } else { ob_start();}`
initall
A: 

I use the code below and it works. Telling you the truth I did not undersyand yet properly all the header that I send, I still did not have the time to investigate, I found explanations in:

Sources:

http://www.opendesigns.org/forum/discussion/1437/php-download-counter/#pgbottom http://www.webdeveloper.com/forum/showthread.php?t=115815&amp;highlight=PHP+download+counter http://php.net/manual/en/function.header.php#83384

anyway it works:

   /*
   TODO: still to be read and better understood.
   */

   //no caching (I don't uderstand what is this part useful for)
   header("Pragma: public"); //?
   header("Expires: 0"); //?
   header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); //?
   header("Cache-Control: private", false); //?

   //sending download file
   header("Content-Type: application/octet-stream"); //application/zip can use application/octet-stream that is more generic it works because in now days browsers are able to detect file anyway
   header("Content-Disposition: attachment; filename=\"" . basename($file_serverfullpath) . "\""); //ok
   header("Content-Transfer-Encoding: binary"); //?
   header("Content-Length: " . filesize($file_serverfullpath)); //ok
   readfile($file_serverfullpath);
Marco Demajo
A: 

I have narrowed the problem down to the bottleneck. The webserver automatically gzip compresses the output. When I removed the Content-Length header from my PHP script everything started downloading smooth. This makes sense: The value of the Content-Length doesn't match the actual gzipped output anymore. In PHP I read the uncompressed filesize to set the Content-Length header, but afterwards, Apache compresses it, and this is probably where the browsers choked.

fireeyedboy