I'm randomly getting download errors from a link on a page. I also simplified the link to a directory for easy usage in emails for users.
On the main page the link looks like this:
a href="http://myPage.com/Mac" target="_blank" id="macDownloadButton" class="downloadbutton w-button">Download Mac version</a>
On my server, that's a directory with an index.php in it which looks like this:
<?php
// mac version
$file="http://www.myPage.com/downloads/myApp_Mac.zip";
$filename="myApp_Mac.zip";
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Content-Length: ' . filesize($file));
header('Content-Encoding: none');
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename=' . $filename);
readfile($file);
exit;
?>
Again, the reason I do this is so it's a simple link to send to users in email like, "http://myPage.com/Mac" and "http://myPage.com/Windows".
The weird thing is that it mostly works...but sometimes it doesn't.
What am I doing wrong?
It's hard to know precisely what's wrong unless you check for errors on your readfile() call.
But you're invoking your web server from your web server here when you specify a filename starting with http. You're doing
readfile('http://www.myPage.com/downloads/myApp_Mac.zip');
where you could just as easily do
readfile('../downloads/myApp_Mac.zip');
and read the zip file from the local file system to send to your user.
What's more, filesize('../downloads/myApp_Mac.zip'); will yield a numerical value quickly and send it in the Content-Length header. That will allow the browser, by knowing the total size of the file you're sending, to display a meaningful progress bar.
You should remove the Accept-Ranges header; the php program you showed us doesn't honor range requests. If you lie to the browser by telling it you do honor those requests, the browser may get confused and corrupt the downloaded copy of your file. That will baffle your user.
Your Content-Disposition header is perfect. It defines the filename to be used on your user's machine in the downloads folder.
Simple operations are more reliable, and this may help you.
The reason you got stat failed with link as an error message is this: stat(2) is a operating-system call that operates on files in local and mounted file systems.
As previously mentioned by O. Jones you should definitely always use your local file path.
Most of my previous issues have been mostly browser related where I needed to tweak/add a http header, and in one case I needed to send all the HTTP headers in lowercase but I haven't had an issue like that in years. My personal recommendation would be to use a solid download library/function - it will make a noticeable difference to your productivity as well as rule out most browser related issues you may come across.
I have used the codeIgniter download helper for the last 3 years and recommend it for 99% of use cases. At the very least I would recommend your read through it's code - you will probably find a few cases you have never even considered such as clearing the output buffer,mime detection and even a special case for Android 2.1 as well as a few headers you may or may not need.
If all else fails I have no idea what server your running this on but if you continue to have issues I would recommend monitoring which processes your machine is running while paying close attention to ram and IO usage. I've have encountered bad/misbehaving services that run periodically using 99% of my IO or ram for short intervals at a time that caused a few really odd and unexpected errors.
Related
I did an xml file and force download it by these headers:
header('Content-disposition: attachment; filename="export.xml"');
header('Content-type: application/xml; charset=utf8');
readfile('export.xml');
But before the download I see a dialog that this file can be harmful for my computer? How to get rid of this dialog? Maybe my headers is wrong?
upd Well, can do nothing, I did a test on my test-hosting, u can check it here: site with generation link, and an xml file as is: export.xml
Try changing application/xml to text/xml. Probably your browser thinks that application means executable.
Try this :
<?php
header('Content-disposition: attachment; filename="export.xml"');
header('Content-type: "text/xml"; charset="utf8"');
readfile('export.xml');
?>
Note: This does not solve your issue, however it did solve an issue I had on my computer giving that notice (windows, chrome, apache webserver, PHP 5.4.10). I leave it here for future visitors.
Some browsers do not only look for the headers but also for the "filename" in the URL.
For example if you download a PHP file that contains XML, the browser might identify it as a dangerous file (because it can be executed on your system or is not within some whitelist or what not):
http://example.com/xml-download.php
A simple solution is to make this file not end with .php any longer, for example by adding a ?:
http://example.com/xml-download.php?
And continue with that to even signal the filename that way:
http://example.com/xml-download.php?export.xml
(the last one is not necessary but can be useful especially with some older browsers)
At the current time I'm using this code to output a file to a user.
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$or.'');
readfile($file);
The code, however doesn't tell the browser how large the file is. And it can't output large files like 1 gb. I want the code to tell the browser the actual size of the file and be able to output large files
For large files, you need to use chunked transfer. In most cases the underlying web server (Apach/Nginx/WHY) will have facilities to do that. I recommend you use them.
That way your code does not take up a worker thread for ages, and the run-away timer will not cut in in the middle of your down-load (which would upset your users).
btw - You are talking about file download, not upload - that would be user to server. Your tag is wrong.
readfile() will not present any memory issues, even when sending large files, on its own. If you encounter an out of memory error ensure that output buffering is off with ob_get_level().
header("Content-Length: $value");
I am currently developing an application in PHP in which my server (a dedicated server) must to download a file, and the user should download the file in same time.
Here is an example :
Server start to download a file at a time A.
User wants to download this file at the time A + 3 seconds (for example)
I already solved the problem :"If the user downloads the file faster than the server..". But I didn't know how to make a php script in which the user is gonna to download the full file (it means that the size must be the full size of the file, not the size it's currently downloaded at the time A+3seconds). I already make that :
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$data['name'].'";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$data['size']);
readfile($remoteFile);
But it doesn't work, the user is gonna download just the size it is currently on the server (which corrupt the file) and not the full file...
If you have any solution, thank you.
You could probably pipe the file manually, by opening the connection and reading until you're past all headers. Then once you've figured out the Content-Length, send that to the user and just echo all remaining data you get (do use flush() and avoid output buffers).
Pseudocode(-ish):
open the file
# grab headers
while you didn't get all HTTP headers:
read more
look for the Content-Length header
send the Content-Length header
# grab the file
while the rest of the request isn't done
read more
send it to the user
flush the buffers
done
Expanding on #Tom answer, you can use cURL to greatly simplify the algorithm by using the CURLOPT_HEADERFUNCTION and CURLOPT_READFUNCTION callbacks - see curl_setopt().
Don't send the content-length header. It's not required assuming you're using http 1.1(your webserver almost certainly does). Drawback is their browser cant show download time/size remaining.
I have been trying to find the reason for this error for weeks now - and I have come up a blank. The system uses PHP to generate dynamic .pdf files.
I have three servers: Dev (Win7 with Apache2), Test (Ubuntu 10.4 with nginx), and Live (Ubuntu 10.10 with nginx). All are running php5 and the system I have developed - same code. Equivalent, same config.
I have many browsers I have tested things with: DevIE (win7, IE8), DevFF (Win7 Firefox 3.5), DevSaf (win, Safari), LaptopFF (WinXP, Firfox 3.5), Laptop IE(WinXP, IE8 Test (Ubuntu FF3.5), and users (mostly IE8 on Win 7 and Win XP).
When I generate a PDF from Test it works correctly in all browsers (except Users which I can't test).
When I generate a PDF from Dev it fails from DevIE, DevFF and DevSaf, but calling for it from Test works.
Apache2 always fails from the same machine.
From the laptop, using FF succeeds, and using IE8 fails (see below).
The users are reporting intermittent problems. It fails, and then the repeat the request and it succeeds.
When it fails....
The log of the generated PDF is shown, sending the right sort of size reply (500KB to 1.8MB) with a 200 OK result. This is sometimes followed about 10 seconds later with a repeat of the same URL - but this generates the log-on screen (again 200 OK reply), but only 2K in size. The implication is that it was requested without the cookie.
Adobe Reader tries to display the log-on page, with the inevitable "This file does not start with "%PDF-" error message.
Except for when I try with the laptop and IE8 - then it fails with show source showing a 4 line html file with an empty body!
The system has been working for over a year - and only started failing with a change of production server about 2 months ago. The test version was not changed at this time, but started to fail also.
I have tried all sorts of headers, but nothing I have tried makes any difference. The current set of headers is:
header('Content-Disposition: inline; filename="'.$this->pdfFilename().'"');
header('Content-type: application/pdf');
header("Pragma: public");
$when = date('r',time()+20); // expire in 20 seconds
header("Expires: $when");
I've tried replacing inline with attachment. Adding and removing all sorts of no-cache headers. All to no avail.
The PDF is requested in a new window, by JavaScript - and is followed 8 seconds later by a refresh. I have tested without the new window, and without the refresh - no change.
I have has a few (small) PDFs served by the Dev server. So I have raised every limit I can think of. Now it always fails.
So I have a Windows Apache2.2 server that fails when browsed from the same machine and succeeds when browsed from other machines in Firefox.
There is no proxy or cache mechanism involved other than that in the browsers.
Has anyone any ideas about what might be going wrong? As I said, I have been testing and eliminating things for nearly 4 weeks now, on and off, and I have not yet even identified the failing component.
This is really tough to troubleshoot - for starters, (please excuse my bluntness, but) this a prime example of what a pipeline should not look like:
Three different operating systems.
Probably at least two different versions of PHP.
Two different webservers.
But anyway, a few general hints on debugging PHP:
make sure to enable error_log and log_errors in php.ini (set display_errors = Off)
use the most verbose error_reporting
set access_log and error_log in nginx.
crank up log level in nginx (I'm guessing you use php-cgi or php-fpm, so you should be able to see what status the backend emits when the download attemp fails).
Furthermore:
You haven't shared how the PDF is generated - are you sure all libraries used here are the same or at least somewhat the same across all systems?
In any case, just to be sure I would save the PDF on the server before it is offered to download. This allows you to troubleshoot the actual file — to see if the PDF generation actually worked.
Since you're saving the PDF, I'd see about putting it in a public folder, so you can see if you can just redirect to it after it's generated. And only if this works, then I'd work on a force-download kind of thing.
I would replicate the production environment in all stages. ;-) You need your dev server to be exactly like the production environment. For your own workstation, I'd recommend a VM (e.g. through Virtualbox with Ubuntu 10.10).
Let me know this gets you somewhere and reply with updates. :-)
Update:
I'd investigate these two headers:
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
Definitely helps with cache busting.
These are the headers, which finally worked in a similar situation in one of my apps:
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header( "Content-Type: application/pdf" );
header("Content-Disposition: inline; filename=\"YourPDF_" . time() . ".pdf\";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ". strlen( $pdfData ) );
I added the time() code to make the filename change each time, so that it likely passes all proxies.
From time to time but seldom, the problem re-appears. Then, we ask our clients to download the file using the browser context menu.
PS: The app uses ezPDF found here: http://www.ros.co.nz/pdf/
<?php
$filename= './get/me/me_'.rand(1,100).'.zip';
header("Content-Length: " . filesize($filename));
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename=foo.zip');
readfile($filename);
?>
Hi,
I have this simple code that forces a random file download, my problem is that if I call the script two or more times from the same browser the second download won't start until the first is completed or interrupted. Thus I can download only one file per time.
Do you have any clue?
This may be related to PHP's session handling.
Using the default session handler, when a PHP script opens a session it locks it. Subsequent scripts that need to access it have to wait until the first script is finished with it and unlocks it (which happens automatically at shutdown, or by session_write_close() ). This will manifest as the script not doing anything till the previous one finishes in exactly the same way you describe.
Clearly you aren't starting the session explicitly, but there's a config flag that causes the session to start automatically: session.auto_start - http://www.php.net/manual/en/session.configuration.php
Either use phpinfo() to determine if this is set to true, or look in your config. You could also try adding session_write_close() to the top of the script, see if it makes the issue go away.
just guesses. There could be different reasons.
first, your server could restrict the number of connections or childs in paralell. But I guess this sin't the problem
second, it is more likely that the client restricts the number of connections. The "normal" browser opens only two connections at a time to a certain server. Modern browsers allow up to 8 (?) connections. This is a simple restriction in order to avoid problems which could occur with slow servers.
One workaround could be to place every download on a "virtual" subdomain.
give it a try!
Just to say that the session_write_close(); solved the problem for me.
I was using session_destroy(); (that worked) but was not much good if I needed to keep session data :)
All you need to do I place session_write_close(); just before you start streaming the file data.
Example:
<?php
$filename= './get/me/me_'.rand(1,100).'.zip';
session_write_close();
header("Content-Length: " . filesize($filename));
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename=foo.zip');
readfile($filename);
?>
I'd further investigate Ralf's suggestion about the server restrictions and start with checking the logfiles to ensure that the second request is received by the server at all. Having that knowledge, you can eliminate one of the possibilities and at least see which side the problem resides on.
From the client's browser - you didn't mention which one is it - if Firefox, try to install the Live Http Headers extension to see what happens to request you send and if browser receives any response from the server side.
As far as I can find, there is no php configuration setting that restricts max downloads or anything like that - besides, such a configuration is outside the scope of php.
Therefore, I can come to only two conclusions:
The first is that this is browser behaviour, see if the problem is repeated across multiple browsers (let me know if it is). The HTTP spec does say that only two connections to the same domain should be active at any one time, but I wasn't aware that affected file downloads as well as page downloads. A way of getting round such a limitation is to allocate a number of sub-domains to the same site (or do a catch-all subdomains DNS entry), and when generating a link to the download, select a random sub domain to download from. This should work around the multiple request issue if it is a browser problem.
A second and much more unlikely option is that (and this only applys if you are using Apache), your MaxKeepAliveRequests configuration option is set to something ridiculously low and KeepAlives are enabled. However, I highly doubt that is the issue, so I suggest investigating the browser possibility.
Are you getting an error message from the browser when the second download is initiated, or does it just hang? If it just hangs, this suggests it is a browser issue.