I have a script that merges 2 documents and then it shows a final document in a browser.
It works in Firefox, Opera and IE. But it does not work in Chrome.
Chrome only shows loading and it stops in 1/4 of loading.
The code:
exec("pdftk A=$pdfin B=$tmpfname cat B1 A output $tmpfoutput");
$data = file_get_contents($tmpfoutput);
header("Content-type: application/pdf");
header("Content-disposition: inline;filename=GeneratedPdf.pdf");
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($tmpfoutput));
header('Accept-Ranges: bytes');
echo $data;
I have been told that it also worked in Chrome before so I think there will be only a problem with the last version.
Thanks for any help.
By appending Accept-Ranges: bytes, your script tells the browser that it will accept range requests, i.e. multiple requests that request a part of the response. Your script obviously does not support range requests because it generates and provides the data at once.
To fix the error, remove header('Accept-Ranges: bytes');
If your PDF files are usually large, then a more user-friendly solution is to actually implement range requests in your script. Odds are that your server does already have an efficient routine that handles range requests, so a smart choice is to save PDF file to a publicly accessible directory, then 302-redirect the request to this URL after the PDF has been generated. Make sure that the URLs are unguessable, e.g. by using UUIDs. And remove the PDF files at some point, e.g. using a cronjob.
Related
I’ve got some PHP code set up to send various mp3 files under certain conditions. While it works to send the audio data, the mp3 file’s duration isn’t reported (so, for example, it’s reported as a ‘live broadcast’ when viewed in Safari). Linking directly to the file works properly, so I presume the issue is to do with my PHP code and HTTP headers.
Currently, to send the file, I’m doing this:
header("Content-Transfer-Encoding: binary");
header("Content-Type: audio/mpeg");
header(“Content-length: “ . filesize($filePath));
header(“Content-Disposition: attachment; filename=$filename”);
header(“Cache-Control: no-cache”);
readfile($filePath);
die();
Essentially I just want the PHP to deliver the file in exactly the same way as if I linked directly to the mp3 file itself. Any ideas on what I’m doing wrong would be appreciated!
Edit: Solved! Figured out the issue and described in the comment below.
I'm serving an .mp3 file (as a download dialog box, where the end-user can rename and save the file). There are a lot of conflicting reccomendations for this on the forums and even within the comments on the php manual.
I hope to solve my issue and create here a reference on how to best execute this goal: i.e. a clear "best practices summary" usable by begining php coders on the most efficient way to get started delivering downloadable files.
I would really appreciate it if you'd check over the code here and suggest your corrections--this is part of a site that helps artists self-publish music, books, etc.
To test, I am uploading this to my server as index.php; perhaps that is also part of the problem.
The current status of this script is that the browser hangs a bit and then loads the binary as text into the browser display window.
I've thought at many points that my problem was syntax in the important "Content-Length" header, or the order of my headers, so I've tried several versions of all that, but none cut off the download.
Here is the exact code that I am now trying on my own,
where ####### means 7 numbers (the actual file size in bytes),
and everything else should be clear:
<?php
header('Server: ');
header('X-Powered-By: ');
header('Cache-Control: no-cache');
header('Expires: -1');
header('Content-Description: File Transfer');
header('Content-Transfer-Encoding: binary');
header('Content-Type: audio/mpeg');
header('Content-Disposition: attachment;filename="suggested-save-name.mp3"');
header('Content-Length: #######["exact-file-name.mp3"]');
#readfile("http://full-public-url.com/exact-file-name.mp3");
ob_clean();
flush();
exit;
?>
Returns these response headers:
>X-Powered-By:PHP/5.4.xy
>Vary:negotiate
>Transfer-Encoding:chunked
>TCN:choice
>Server:Apache
>Keep-Alive:timeout=2, max=200
>Date:Fri, 10 May 2013 16:mm:ss GMT
>Content-Type:text/html
>Content-Location:filename.php
>Connection:Keep-Alive
I hope it is a simple error (or more than one) in the syntax of my script, or the way I created and saved the .php file. I have verified the settings are at default, php is up to date, and there are no .htaccess issues. I have carefully made sure there are no extra spaces at the end of this file, as well as all other files in the web directory, and as well I've tried the script with and without the closing ?>.
Thank you in advance
...
Best script after reading Answers, below:
<?php
$file-variable=('./exact-file-name.mp3')
$size=filesize($file-variable);
header('Server: ');
header('X-Powered-By: ');
header('Cache-Control: no-cache');
header('Expires: -1');
header('Content-Type: application/octet-stream'); //will need to redirect for older IE
header('Content-Disposition: attachment; filename="suggested-save-name.mp3"');
header('Content-Length: "$size"');
#readfile("$file-variable");
ob_clean();
flush();
exit;
Successful response headers, from the better script (& after removing the UTF-8 BOM):
>Vary:negotiate
>TCN:choice
>Server:Apache //How can I hide this?
>Keep-Alive:timeout=2, max=200
>Expires:-1
>Date:Sat, 11 May 2013 12:mm:ss GMT
>Content-Type:audio/mpeg
>content-transfer-encoding:binary
>Content-Location:filename.php //I would also like to obfuscate this
>Content-Length:#######
>Content-Disposition:attachment; filename="suggested-save-name.mp3"
>Content-Description:File Transfer
>Connection:Keep-Alive
>Cache-Control:no-cache
Step 1 would be to remove all nonsense headers that are not defined in the HTTP standard.
This will eliminate Content-Description and Content-Transfer-Encoding. Both are useless at best, and might interfere with normal browser operations in the worst case.
Step 2 is to optimize the file delivery. Do not download the MP3 with a HTTP request, access the FILE on the server. Do not use a URL, do use a file path. If the MP3 is right next to your script, this will work:
readfile('./exact-file-name.mp3');
At this point you should usually end up with a working download. If not, try changing the Content-Type to something more generic. audio/mpeg might trigger the audio player in some browser, application/octet-stream should work in most browsers but older Internet Explorer, which do inappropriate content sniffing on certain mime types including this one. application/x-ms-download is supposed to work then.
Make sure the header is sent. PHP does not send HTTP headers if the HTTP body was already startet, and any whitespace including the UTF-8 BOM will trigger body output.
Some comments on your "final" headers in general:
Content-Length: should only have an integer stating the length in bytes, nothing more. Especially no mention of any filename in square brackets.
Content-Transfer-Encoding and Content-Description are still useless.
Content-Location is not needed. If you don't know what it does, omit it. Obfuscation will not work here, the browser needs to know the URL he is accessing. Duplicating this URL in this header does not change anything, obfuscating it will likely break things somewhere.
The two headers you really only need for a download are: Content-Type and (if you want to pre-define a filename for the user) Content-Disposition.
test.php code:
$path = 'audio.mp3';
header("Content-type: audio/mpeg");
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: inline; filename="'.$path.'"');
header("Content-length: ".filesize($path));
readfile($path);
html code:
<iframe src="test.php"></iframe>
this will play the .php as a .mp3, but i will not be able to access the navigation slider in the browser media player. i click on the slider in several different places, but nothing will happen. when i change the source to "audio.mp3" i am able to control the slider again. im pretty sure that it has something to do with the headers. any idea what headers i can use to fix this?
It actually has everything to do with what browser and plugin you are using, "streaming" mp3 or media files for that matter should not be done this way. Use any of the many open source and easy to use Flash players, they handle buffering, controls, display/hidden, and everything for you, for a mp3 file on your server.
Server setup: Apache 2.2.14, PHP 5.3.1
I use a PHP script to serve files of all types as part of an application with complex access permissions. Things have worked out pretty well so far, but then one of our beta users took a picture with a 10-megapixel digital camera and uploaded it. It's somewhere north of 9 MB, 9785570 bytes.
For some reason, in Safari (and thus far ONLY in Safari, I've reproduced this on 5.0.5) the download will sometimes hang partway through and never finish. Safari just keeps on merrily trying to load forever. I can't consistently reproduce the problem - if I reload over and over sometimes the download will complete and sometimes it won't. There's no apparent pattern.
I'm monitoring the server access logs and in the cases where Safari hangs I see a 200 response of the appropriate filesize after I navigate away from the page, or cancel the page load, but not before.
Here's the code that serves the file, including headers. When the download succeeds and I inspect the headers browser-side I see the content type and size have been set correctly. Is it something in my headers? Something in Safari? Both?
header('Content-Type: ' . $fileContentType);
header('Content-Disposition: filename=' . basename($fpath));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($fpath));
ob_clean();
flush();
session_write_close();
readfile($fpath);
exit;
FURTHER BULLETINS AS EVENTS WARRANT:
By artificially throttling download speed to 256k/s -- that is, by chunking the file into 256k pieces and pausing between serving them, as
$chunksize = 1 * (256 * 1024); // how many bytes per chunk
if ($size > $chunksize) {
$handle = fopen($fpath, 'rb');
$buffer = '';
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
sleep(1);
}
fclose($handle);
} else {
readfile($fpath);
}
I was able to guarantee a successful display of the image file in Safari under arbitrary conditions.
A chunksize of 512k does not guarantee a successful display.
I am almost certain that the problem here is that Safari's image renderer can't handle data coming in any faster, but:
I would like to know for certain
I would also to know if there's some other kind of workaround like a special CSS webkit property or whatever to handle large images because 256k/second is kind of dire for a 10 MB file.
And just to pile on the weird, setting up a finer-grained sleep with usleep() results in problems at a sleep time of 500 ms but not 750 ms.
I did a little digging and found little specific, but I do see a lot of people asserting that Safari has issues with honoring cache control directives. One person asserts:
You don't need all those Cache Controls, just a max-age with Expires set in the past, does everything all those headers your using does [...] many of those Cache Controls headers your using cause problems for Safari [...] Lastly, some browsers don't understand filename, the only understand name, which must be included in the Content-Type header line, never in the Content-Disposition line. [...]
( see last post in thread: http://www.codingforums.com/archive/index.php/t-114251.html OLD info, but you never know... )
So possibly comment out some of your headers and look to see if there is an improvement.
(anecdotal) I also saw some some older post complaining about safari both resuming an interrupted download by appending the whole file to the end of the partial one, and endless downloading which appears to count bytes beyond the file length being sent. (anecdotal)
You might want to try to "chunk" the file while reading it in.
There a numerous posts here on PHP.net that explain ways to do that: http://php.net/manual/en/function.readfile.php
Try:
ob_start();
readfile($path);
$buffer = ob_get_clean();
echo $buffer;
I have an XML feed generated by a PHP script with mod_rewrite running. IE opens the feed OK but I cannot get it to save the file to disk or import to Excel.
The file I am trying to access in the url is similar to:
http://domain.com/download/export.xml
This gets written to a download PHP script and is not actually an XML file.
After setting the headers as below, it is still not possible to get IE to save the file. Also, as it is not capable of displaying the plain source, copy & paste into notepad will not work because of various styling changes IE makes to the XML.
<?php
header("Content-type: text/xml");
header('Content-Disposition: attachment; filename="QuoteExport_'.$quoteDate.'_'.$quoteSlot.'.xml"');
Does anyone know the solution? Thanks.
Edit:
Thanks for the help so far. I have tried a combination of these headers and still not getting the results I want. No matter what I set, IE always displays it in-line in the browser with no option to download. The save functionality also still does not work either. Any other ideas how to force IE to save the XML as a file by using headers?
Edit2: The state now is that IE gives the option to open/save but whilst trying to save, there is a popup saying that it is "Unable to open this internet site.". Yet at the same time, the open option displays the content with no issues.
To force ie and other browsers to download you have to specify specific headers like in this example :
header("Content-disposition: attachment; filename=\"".$fileName . ".csv\"");
header("Content-Type: application/force-download");
header("Content-Transfer-Encoding: binary");
header("Pragma: no-cache");
header("Expires: 0");
Other wise if you use text/xml, ie thinks he knows how to display it and does so.
You need to set a session parameter for some versions of IE. Assuming you have $my_file_name and $my_file_contents set, here's how it would look:
if(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
session_cache_limiter("public");
}
session_start();
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename=".' $my_file_name '. "');
print($my_file_contents);
(Adapted from this article)
Try setting the content-type to application/xml or application/force-download
After spending much time changing the headers, I went for a slightly different approach. No matter what I was setting them to, it just would not work for me.
Anyway the solution implemented was:
When a client lands on the URL (http~://my-domain.com/downloads/export.xml), the PHP script generates a real XML file in the same location as the request-URI and saves it on the server, schedules it for a delete job in the future and then effectively refreshes the browser. Then, the mod-rewrite rules on the second request serve the actual file and then IE can use the file correctly. A future request then causes the cycle to run again.
Slightly round about way of doing things, but it was the fastest method. Also, I had the ability to schedule file operations from another part of the application making life much easier for clean-up!
Anyway thanks for the help and I did learn a thing-or-two about header options.