I am creating an server-side api for my app. One of the steps requires the app do download a very large zip file from my server, but it can't be done by just downloading http://mydomain.com/file.zip. My app passes some authentication headers and other safety stuff which must be processed by a php script (e.g. http://mydomain.com/download.php?auth=foo&key=value&etc)
I have done this kind of thing earlier using images:
<?php
$can_download = /* some complicated auth stuff */
if($can_download) {
header('Content-Type: image/jpeg');
echo file_get_contents('image.jpg');
}
?>
My question is: can it be done with 200+ MB zip file? I know I have to modify headers somehow and probably use some advanced php functions, but I couldn't find any tutorial neither here, on Stack Overflow, nor anywhere else.
Edit: I also have to be able to resume my downloads, because it's very likely that user would quit the app, though it should be able to resume the download (e.g. from 47%). Can it be done?
For those looking for the answer: the correct function is readfile and 'resuming' problem can be solved using this custom function.
The correct function to use in this instance is readfile.
The given code would encounter problems because it unnecessarily loads the file contents into memory before forwarding them to the browser. This does not help performance, and can easily result in running out of memory.
Yes, however you want to use something like readfile instead. The reason being is that file_get_contents reads the entire file into memory before passing it to the client.
Related
i am running multiple websites with high traffic , as a requirement , all images are downloaded via image.php?id=IMAGE_ID_HERE .
If you ever done that before , you know that that file will be reading the file image and echoing it to the browser with special headers .
My problem is , the load on the server is very high (150-200) and TOP command shows multiple instances of image.php , so image.php is running slow !
the problem probably is fopen loading the image to the memory before sending it to the client. How to read a file and pass it through directly?
Thank you guys
UPDATE
After you optimized the code, used caching wherever possible, do create a CDN . couple of servers, sync methods, load balancers and no need to worry about requests anymore :)
fopen and file_get_contents are nearly equivalent
to speed up with consistence the page load you can use
http://www.php.net/fpassthru
or, even better
http://www.php.net/readfile
with those functions, content of file is printed directly, byte per byte
as opposed to file_get_contents, for example, where you store the whole data inside a variable
$var = file_get_contents();
so, to make these work correctly you will need to disable output buffering (otherwise it would make readfile() pointless) in the page that serves the images
hope this helps!
Why dont you cache the image content with apc ?
if(!apc_exists('img_'.$id)){
apc_store('img_'.$id,file_get_content(...));
}
echo apc_fetch('img_'.$id);
this way image content will not be read from your disk more than once.
Solved
I actually found out what is going on here.
Turns out it was sending the whole file, but Excel (which I was using to open the result file for testing), will only display 65536 rows. If there are more than that, it will alert something to the effect of "the file is incompletely displayed" and then cut it off after that many records.
(Note to Joel Spolsky - please call your friends from the original Excel development team and yell at them for me =o)
Thanks!
I have a very simple script that pulls some data from a database, and sends it to the visitor as a .csv file.
I have the memory and execution time set to acceptable levels, but for a few large reports, I notice that the download cuts off after about 10 seconds.
This ONLY happens if I set it as a download in the headers. If I comment out the content-type, content-disposition, etc, and just write the data to the browser, then the entire file will download and display in the browser.
Code is as follows:
// Code removed.
Anyone have any ideas? Could this be a browser issue with file download?
Thanks!
I don't know what's causing your problem, but here is something that you can try:
Write the data to a file and then send it to the user using the X-Sendfile(see this) header. Alternatively you can redirect to the file.
I had a slightly similar issue, but with very slow downloads. The culprit was an overly aggressive antivirus package affecting only IE downloads. Check for Mcshield.exe.
I've got a Mac server and I'm building PHP code to allow users to upload images, documents, and even video files. Researching this has definitely gotten me nervous, I want the uploaded content to be virus free.
Is building something myself going to be a huge challenge? Would you do it, or would you find some OS or OTS product? (And do you know of any you can recommend)?
Conceptually, what you're talking about is pretty straightforward. Accepting and processing uploads is pretty simple, it's definitely not something I think you need to worry about buying a pre-built solution for.
Generally things like images and videos can't really have "viruses" (unless the viewer application is really poor and lets them run code somehow - also known as "Internet Explorer"), but it's not really difficult to virus-scan them anyway if you'd like to. Just find a command-line scanner that can run on the server (something like Clam AV), and whenever a file is uploaded, run it through the scanner and reject the upload (and log the event) if it fails the scan.
If you're uploading very large files, you might also consider a Flash upload/status bar so that users can see how much of the file is uploaded. SWFUpload is a good choice for that.
You can scan files with ClamAV by doing something like this in PHP:
$out = '';
$int = -1;
exec('/usr/local/bin/clamscan --stdout /path/to/file.ext', $out, $int);
if ($int == 0)
{
print('No virus!');
}
/*
Return codes from clamscan:
0 : No virus found.
1 : Virus(es) found.
40: Unknown option passed.
50: Database initialization error.
52: Not supported file type.
53: Can't open directory.
54: Can't open file. (ofm)
55: Error reading file. (ofm)
56: Can't stat input file / directory.
57: Can't get absolute path name of current working directory.
58: I/O error, please check your file system.
59: Can't get information about current user from /etc/passwd.
60: Can't get information about user '' from /etc/passwd.
61: Can't fork.
62: Can't initialize logger.
63: Can't create temporary files/directories (check permissions).
64: Can't write to temporary directory (please specify another one).
70: Can't allocate memory (calloc).
71: Can't allocate memory (malloc).
*/
The short answer: Don't buy anything. The experience and sense of accomplishment you will gain from coding this yourself is far more worth it.
The long answer: Trusting any form of user input is generally a bad idea. However, being sensible about what you do with user data is always the best way to go. If you don't do foolish things*, you'll be fine, and you'll gain tremendously from the experience.
( * I know that's a little ambiguous, but hey, try identifying a mistake before you've made it. I know I rarely can. ;)
I'm building sort of the same right now using FancyUpload from digitarald for Mootools 1.2.1
check this example: http://localhost/fancyupload/showcase/photoqueue/ to see how cool that is.
Just make sure you read up on how to pass a session to Flash (using GET / POST parameters!! Your session cookies will not work. ) and do some checks on the filetype.
Personally, i'd not let my users upload video's. Just use youtube and embed that stuff.
Oh yeah, and if you want to have thumbnails of thet stuff that's uploaded, go for ImageMagick installed on your server along with Ghostscript. Imagemagick can then even generate thumbnails from PDF's!
"Is building something myself going to be a huge challenge?" Yes, it is. Not as huge as to outsource it to a third party solution, but what you want to code here is possibly the most dangerous thing that you can get to code on a php web script: allowing users to upload files to your server. You need to be extremelly careful to filter the files you are going to accept to prevent users from uploading php scripts to your server. Common mistakes that people do while filtering are:
Not filter at all.
Filter based on incorrect regular expressions easily bypassables.
Not using is_uploaded_file and move_uploaded_file functions can get to LFI vulnerabilities.
Not using the $_FILES array (using global variables instead) can get to RFI vulns.
Filter based on the type from the $_FILES array, fakeable as it comes from the browswer.
Filter based on server side checked mime-type, fooled by simulating what the magic files contain (i.e. a file with this content GIF8 is identified as an image/gif file but perfectly executed as a php script)
Use blacklisting of dangerous files or extensions as opposed to whitelisting of those that are explicitely allowed.
Incorrect apache settings that allow to upload an .htaccess files that redefines php executable extensions (i.e. txt)..
I could go on, but I think you were already scared before asking :)
As per the viruses thing, yeah, just run an AV.
Here’s code to process the uploaded files, just so you get the idea:
foreach ($_FILES as $file) {
if (!$file['error']) {
move_uploaded_file ($file['tmp_name'], 'uploads/'. $file['name']);
} elseif (4 != $file['error']) {
$error_is = $file['error'];
// do something with the error :-)
}
}
header ('Location: ...'); // go to the updated page, like, with the new files
die;
You're better off using a third-party virus scanner to make sure the uploads are virus-free.
(Writing your own code to check for virus sounds like a daunting task)
Examples:
Gmail I think is using Norton, while Yahoo!Mail I think is using McAfee.
Keith Palmer suggestes small script.
Use clamdscan instead of clamscan.
clamdscan communicates with setup clamd (clamav daemon), while clamscan is a standalone application so virus signatures are loaded EACH TIME you call it, this could be generate quite a big load on your server.
Besides you could also try clamuko (this gives you on-access scanning), so you could just drop files into dir observed by clamuko.
There is also FUSE-based ClamFS which could probably be better solution, if you can't insert modules into the kernel.
I'm sure this has been asked before, but as I can't seem to find a good answer, here I am, asking... again. :)
Is there any way, using only a mixture of HTML, JavaScript/AJAX, and PHP, to report the actual progress of a file upload?
In reply to anyone suggesting SWFUpload or similar:
I know all about it. Been down that road. I'm looking for a 100% pure solution (and yes, I know I probably won't get it).
Monitoring your file uploads with PHP/Javascript requires the PECL extension:
uploadprogress
A good example of the code needed to display the progress to your users is:
Uber Uploader
If I'm not mistaken it uses JQuery to communicate with PHP.
You could also write it yourself, It's not that complex.
Add a hidden element as the first element of upload form, named UPLOAD_IDENTIFIER.
Poll a PHP script that calls uploadprogress_get_info( UPLOAD_IDENTIFIER )
It return an array containing the following:
time_start - The time that the upload began (unix timestamp),
time_last - The time that the progress info was last updated,
speed_average - Average speed in bytes per second,
speed_last - Last measured speed in bytes per second,
bytes_uploaded - Number of bytes uploaded so far,
bytes_total - The value of the Content-Length header sent by the browser,
files_uploaded - Number of files uploaded so far,
est_sec - Estimated number of seconds remaining.
Let PHP return the info to Javascript and you should have plenty of information.
Depending on the audience, you will likely not use all the info available.
If you have APC installed (and by this point, you really should; it'll be standard in PHP6), it has an option to enable upload tracking.
There's some documentation, and Rasmus has written a code sample that uses YUI.
If you're able to add PECL packages into your PHP, there is the uploadprogress package.
The simplest way would be to just use swfupload, though.
Is there any way, using only a mixture of HTML, JavaScript/AJAX, and PHP, to report the actual progress of a file upload?
I don't know of any way to monitor plain HTML (multipart/form-data) file uploads in webserver-loaded PHP.
You need to have access to the progress of the multipart/form-data parser as the data comes in, but this looks impossible because the ways of accessing the HTTP request body from PHP ($HTTP_RAW_POST_DATA and php://input) are documented as being “not available with enctype="multipart/form-data"”.
You could do a script-assisted file upload in Firefox using an upload field's FileList to grab the contents of a file to submit in a segmented or non-multipart way. Still a bunch of work to parse though.
(You could even run a PHP script as a standalone server on another port just for receiving file uploads, using your own HTTP-handling code. But that's a huge amount of work for relatively little gain.)
I'd recommend you to five FancyUpload a try it's a really cool solution for progress bar and it's not necesarely attached to php. Checkout also the other tools at digitarald.de
cheers
IMHO, this is the problem that Web browsers should solve. We have progress meter for downloads, so why not for uploads as well?
Take a look at this for example:
http://www.fireuploader.com/
To quote some famous words:
“Programmers… often take refuge in an understandable, but disastrous, inclination towards complexity and ingenuity in their work. Forbidden to design anything larger than a program, they respond by making that program intricate enough to challenge their professional skill.”
While solving some mundane problem at work I came up with this idea, which I'm not quite sure how to solve. I know I won't be implementing this, but I'm very curious as to what the best solution is. :)
Suppose you have this big collection with JPG files and a few odd SWF files. With "big" I mean "a couple thousand". Every JPG file is around 200KB, and the SWFs can be up to a few MB in size. Every day there's a few new JPG files. The total size of all the stuff is thus around 1 GB, and is slowly but steadily increasing. Files are VERY rarely changed or deleted.
The users can view each of the files individually on the webpage. However there is also the wish to allow them to download a whole bunch of them at once. The files have some metadata attached to them (date, category, etc.) that the user can filter the collection by.
The ultimate implementation would then be to allow the user to specify some filter criteria and then download the corresponding files as a single ZIP file.
Since the amount of criteria is big enough, I cannot pre-generate all the possible ZIP files and must do it on-the-fly. Another problem is that the download can be quite large and for users with slow connections it's quite likely that it will take an hour or more. Support for "resume" is therefore a must-have.
On the bright side however the ZIP doesn't need to compress anything - the files are mostly JPEGs anyway. Thus the whole process shouldn't be more CPU-intensive than a simple file download.
The problems then that I have identified are thus:
PHP has execution timeout for scripts. While it can be changed by the script itself, will there be no problems by removing it completely?
With the resume option, there is the possibility of the filter results changing for different HTTP requests. This might be mitigated by sorting the results chronologically, as the collection is only getting bigger. The request URL would then also include a date when it was originally created and the script would not consider files younger than that. Will this be enough?
Will passing large amounts of file data through PHP not be a performance hit in itself?
How would you implement this? Is PHP up to the task at all?
Added:
By now two people have suggested to store the requested ZIP files in a temporary folder and serving them from there as usual files. While this is indeed an obvious solution, there are several practical considerations which make this infeasible.
The ZIP files will usually be pretty large, ranging from a few tens of megabytes to hundreads of megabytes. It's also completely normal for a user to request "everything", meaning that the ZIP file will be over a gigabyte in size. Also there are many possible filter combinations and many of them are likely to be selected by the users.
As a result, the ZIP files will be pretty slow to generate (due to sheer volume of data and disk speed), and will contain the whole collection many times over. I don't see how this solution would work without some mega-expensive SCSI RAID array.
This may be what you need:
http://pablotron.org/software/zipstream-php/
This lib allows you to build a dynamic streaming zip file without swapping to disk.
Use e.g. the PhpConcept Library Zip library.
Resuming must be supported by your webserver except the case where you don't make the zipfiles accessible directly. If you have a php script as mediator then pay attention to sending the right headers to support resuming.
The script creating the files shouldn't timeout ever just make sure the users can't select thousands of files at once. And keep something in place to remove "old zipfiles" and watch out that some malicious user doesn't use up your diskspace by requesting many different filecollections.
You're going to have to store the generated zip file, if you want them to be able to resume downloads.
Basically you generate the zip file and chuck it in a /tmp directory with a repeatable filename (hash of the search filters maybe). Then you send the correct headers to the user and echo file_get_contents to the user.
To support resuming you need to check out the $_SERVER['HTTP_RANGE'] value, it's format is detailed here and once your parsed that you'll need to run something like this.
$size = filesize($zip_file);
if(isset($_SERVER['HTTP_RANGE'])) {
//parse http_range
$range = explode( '-', $seek_range);
$new_length = $range[1] - $range[0]
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes {$range[0]}-$range[1]");
echo file_get_contents($zip_file, FILE_BINARY, null, $range[0], $new_length);
} else {
header("Content-Range: bytes 0-$size");
header("Content-Length: ".$size);
echo file_get_contents($zip_file);
}
This is very sketchy code, you'll probably need to play around with the headers and the contents to the HTTP_RANGE variable a bit. You can use fopen and fwrite rather than file_get contents if you wish and just fseek to the right place.
Now to your questions
PHP has execution timeout for scripts. While it can be changed by the script itself, will there be no problems by removing it completely?
You can remove it if you want to, however if something goes pear shaped and your code get stuck in an infinite loop at can lead to interesting problems should that infinite loop be logging and error somewhere and you don't notice, until a rather grumpy sys-admin wonders why their server ran out of hard disk space ;)
With the resume option, there is the possibility of the filter results changing for different HTTP requests. This might be mitigated by sorting the results chronologically, as the collection is only getting bigger. The request URL would then also include a date when it was originally created and the script would not consider files younger than that. Will this be enough?
Cache the file to the hard disk, means you wont have this problem.
Will passing large amounts of file data through PHP not be a performance hit in itself?
Yes it wont be as fast as a regular download from the webserver. But it shouldn't be too slow.
i have a download page, and made a zip class that is very similar to your ideas.
my downloads are very big files, that can't be zipped properly with the zip classes out there.
and i had similar ideas as you.
the approach to give up the compression is very good, with that you not even need fewer cpu resources, you save memory because you don't have to touch the input files and can pass it throught, you can also calculate everything like the zip headers and the end filesize very easy, and you can jump to every position and generate from this point to realize resume.
I go even further, i generate one checksum from all the input file crc's, and use it as an e-tag for the generated file to support caching, and as part of the filename.
If you have already download the generated zip file the browser gets it from the local cache instead of the server.
You can also adjust the download rate (for example 300KB/s).
One can make zip comments.
You can choose which files can be added and what not (for example thumbs.db).
But theres one problem that you can't overcome with the zip format completely.
Thats the generation of the crc values.
Even if you use hash-file to overcome the memory problem, or use hash-update to incrementally generate the crc, it will use to much cpu resources.
Not much for one person, but not recommend for professional use.
I solved this with an extra crc value table that i generate with an extra script.
I add this crc values per parameter to the zip class.
With this, the class is ultra fast.
Like a regular download script, as you mentioned.
My zip class is work in progress, you can have a look at it here: http://www.ranma.tv/zip-class.txt
I hope i can help someone with that :)
But i will discontinue this approach, i will reprogram my class to a tar class.
With tar i don't need to generate crc values from the files, tar only need some checksums for the headers, thats all.
And i don't need an extra mysql table any more.
I think it makes the class easier to use, if you don't have to create an extra crc table for it.
It's not so hard, because tars file structure is easier as the zip structure.
PHP has execution timeout for scripts. While it can be changed by the script itself, will there be no problems by removing it completely?
If your script is safe and it closes on user abort, then you can remove it completely.
But it would be safer, if you just renew the timeout on every file that you pass throught :)
With the resume option, there is the possibility of the filter results changing for different HTTP requests. This might be mitigated by sorting the results chronologically, as the collection is only getting bigger. The request URL would then also include a date when it was originally created and the script would not consider files younger than that. Will this be enough?
Yes that would work.
I had generated a checksum from the input file crc's.
I used this as an e-tag and as part of the zip filename.
If something changed, the user can't resume the generated zip,
because the e-tag and filename changed together with the content.
Will passing large amounts of file data through PHP not be a performance hit in itself?
No, if you only pass throught it will not use much more then a regular download.
Maybe 0.01% i don't know, its not much :)
I assume because php don't do much with the data :)
You can use ZipStream or PHPZip, which will send zipped files on the fly to the browser, divided in chunks, instead of loading the entire content in PHP and then sending the zip file.
Both libraries are nice and useful pieces of code. A few details:
ZipStream "works" only with memory, but cannot be easily ported to PHP 4 if necessary (uses hash_file())
PHPZip writes temporary files on disk (consumes as much disk space as the biggest file to add in the zip), but can be easily adapted for PHP 4 if necessary.