I'm using shared hosting(hostgator).
I have site with video content like youtube written in php.
Implemented via direct links to mp4 files and html video tag.
I want to limit connections for file downloads(plays) to around 350.
Because if I have more than ~350 connections hostgator blocks my site.
Is there any way to do that?
Any other suggestions how to deal with this situation will also be helpful.
You could use a php script which handles the actual file download. If the script is executed, increment your download counter and if the file is sent completely to the client, close the connection.
To detect if the file is completely sent, you should send the file in small chunks and check after each transmitted chunk, if the connection is still open.
To do this
send the correct mime-types and http headers
use ignore_user_abort to keep the script running if the client closes the connection
send the file in small chunks and check after each chunk if the connection is still alive. ob_flush and flush are used to keep the output buffer empty. connection_status or connection_aborted to test if the connection is still open.
after the whole file is submited, decrement your connection counter
In addition to this, you might also implement HTTP_RANGE, to resume incomplete downloads. This should be important especially for video downloads, if you want to seek somewhere in the middle of the stream.
Below a little .htaccess that rewrite all the requests for the PHP file.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^ yourFile.php [L]
</IfModule>
Below the PHP file
// code to increment the counter..
// increment_counter(); ...
// Use the request path (URI) to choose what file to send.
$filename = 'video.mp4';
$size = filesize($filename);
$f = fopen($filename, 'rb');
if (!$f) {
// error...
}
ignore_user_abort(true);
set_time_limit(0);
header("Content-Length: $size");
header("Content-Type: video/mp4");
while (!feof($f)) {
echo fread($f, 8192);
ob_flush();
flush();
if (connection_status() != 0) {
// download aborted... decrement the counter
// decrement_counter(); ...
break;
}
}
fclose($f);
// download completed - decrement counter
// decrement_counter(); ...
This script is pretty simple, but should give you an idea. You might add more logic (as said above HTTP_RANGE) or send other headers, but this should give you a good starting point.
References:
Below the links to the documentation of the functions that could be less known.
connection_status
ignore_user_abort
Related
I'm having a download abuse issue with php Content-Disposition: attachment and readfile. It seems that my problem is with readfile, because although this script works, whether or not the client closes their browser, readfile reads the entire contents of the mp4, setting up the possibility of abuse with scripts initiating the download and immediately closing the progress. Something, somewhere, is running a script which clicks this link hundreds of times per second, running my php script and immediately cancelling their download, but my server is preparing that entire file to be offloaded each time.
Here's the script I'm running, when the user/abuser clicks a download link:
<?php
// get MP4 address
$MP4Address = $_GET["MP4Address"];
// We'll be outputting a MOV
header( 'Content-Type: application/octet-stream' );
$filename = basename($MP4Address);
// Name file
header('Content-Disposition: attachment; filename="'.$filename.'"');
// Source file
readfile($MP4Address);
?>
I suspect that readfile is the culprit here, but without it, the client will receive an empty file. There must be a more modern, proper way of doing this, but I'm not sure what it could be.
Unless you've called ignore_user_abort(true) PHP should get the signal that the connection has been aborted and cease execution. But it's possible that once you're inside the readfile() call PHP is not able to watch for that signal since it's busy doing low-level IO.
I would say you've got 2 options, the first being to simply write some code to detect and block the person that's abusing your service. You downloads are already backed by a PHP script, so adding in a bit of checking and filtering should be relatively simple.
The other would be to replace the readfile() call with a bit of [admittedly less efficient] code that should give PHP some breathing space to look for user aborts.
function read_file($filename, $chunksize=4096) {
if( ! $fh = fopen($filename, 'rb') ) {
throw new \Exception('Failed to open file');
}
while($chunk = fread($fh, $chunksize)) {
echo $chunk;
}
fclose($fh);
}
I am using php to download a file and I want the file should get delete automatically from the server after successful completion of download. I am using this code in php.
$fullPath = 'folder_name/download.txt';
if ($fd = fopen ($fullPath, "r")) {
$fsize = filesize($fullPath);
$path_parts = pathinfo($fullPath);
$ext = strtolower($path_parts["extension"]);
header("Content-type: application/octet-stream");
header("Content-Disposition: filename=\"".$path_parts["basename"]."\"");
header("Content-length: $fsize");
header("Cache-control: private"); //use this to open files directly
$fd = fopen ($fullPath, "r");
while(!feof($fd)) {
$buffer = fread($fd, 2048);
echo $buffer;
}
fclose ($fd);
}
unlink($fullPath);
You can see in the code after download I am unlink the file. But if I do so corrupted file is getting downloaded. Because sometime the file getting deleted before it get download fully. Is there anyway in php to know that client download the file successfully then I can delete it? Any idea will be highly appreciated.
As far as I'm aware, you cannot use server-side PHP to detect whether the download has finished for the client. It seems ignore_user_abort() is the answer to your question (see below), otherwise you may just delete the file after a set amount of time.
ignore_user_abort(true);
if (connection_aborted()) {
unlink($f);
}
Related/Duplicate on Stackoverflow:
deleting a file after user download it
check if download is completed
If you really are downloading (instead of uploading, like code in your posts suggests), you might be interested in tmpfile function that is specifically designed to create files, that will be immediately removed on having its descriptors closed.
There is no way to know when the user finished downloading a file with PHP, I'd use a queue system to delete the file after n seconds of the request:
How to Build a PHP Queue System
Check the hash code of the file on the server and on the client side...
You could check the hash code with the javascript(How to calculate md5 hash of a file using javascript) send it to server and then check if it is the same al on the server...
Check the request, if the Range HTTP header is set, the client is downloading the file in pieces, it wants to download only a small part of the data at once (for example: Range: bytes=500-999). Normally this is handled by the webserver automatically, but in this case you have to handle it and send only the requested range back. Store the progress in session and deny access only if the client downloaded all of the pieces.
This may be a little buggy for large files but small ones on a fast connection I use this with no problems.
<?php
### Check the CREATE FILE has been set and the file exists
if(isset($_POST['create_file']) && file_exists($file_name)):
### Download Speed (in KB)
$dls = 50;
### Delay time (in seconds) added to download time
$delay = 5;
## calculates estimated download time
$timer = round(filesize($file_name) / 1024 / $dls + $delay);
###Calculates file size in kb divides by download speed + 5 ?>
<iframe width="0" height="0" frameborder="0" src="<?php echo $file_name;?>"></iframe>
<h2>Please Wait, Your Download will complete in: <span id="logout-timer"></span></h2>
Redirects to SELF with File Value ?f=$file_name
<script>setTimeout(function(){ window.location = "<?php echo $_SERVER['PHP_SELF']?>?f=<?php echo $file_name;?>";},<?php echo $timer;?>000);</script>
Deletes the file
<?php
endif;
if (isset($_GET['f'])):
unlink($_GET['f']);
### removes GET value and returns to page's original url
echo "<script> location.replace('".$_SERVER['PHP_SELF']."')</script>";
endif;?>
Download timer set for each file in var seconds
<script>
var seconds=<?php echo $timer;?>;function secondPassed(){var a=Math.round((seconds-30)/60);var b=seconds%60;if(b<10){b="0"+b}document.getElementById('logout-timer').innerHTML=a+":"+b;if(seconds==0){clearInterval(countdownTimer);document.getElementById('logout-timer').innerHTML="Timer"}else{seconds--}}var countdownTimer=setInterval('secondPassed()',1000);
</script>
Not sure it will work in almost all cases but try sleep(10); something to delay the deletion of the file for some specific time.
I want to allow the user to download a file up to 1GB in size, but according to my code only a file of 113MB can be downloaded...
header('Content-type: application/zip');
//open/save dialog box
header('Content-Disposition: attachment; filename="check.zip"');
//read from server and write to buffer
readfile('check.zip');
Can anyone tell me how to download a larger file?
I'm going to guess from what you've said that you're getting an "out of memory" error.
In that case, perhaps this note from the documentation might be of interest:
Note:
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().
So, check ob_get_level() and call ob_end_flush() if necessary to stop output buffering.
Alternatively, you could do something like this:
$f = fopen("check.zip","rb");
while(!feof($f)) {
echo fgets($f);
flush();
}
Another option is this:
header("Location: check.zip");
This will redirect the browser to the check.zip file. Since it's a download, the existing page won't be affected. You can even output the rest of a page to say something like "Your download will begin momentarily" to the user.
Either read and echo the file a chunk at a time, or use something like mod_sendfile to make it Not Your Problem.
Increase your file write buffer chunk size to maximum of file. That will decrease the utilization of resources and your download works fine.
Edit:
Use HTML5 webworkers to download large. Webworkers works in background so you can able to download large files.
This is my final download page of my website where general public is able to download govt documents. From server my code is reading the to-be-downloaded-file and in a loop sending to the client browser.
$fp = fopen($file, "rb");
while (!feof($fp))
{
echo fread($fp, 65536);
flush(); // this is essential for large downloads
}
fclose($fp);
exit;
I want to send the file very slowly -- that is can I use Sleep function (or anything like that) within this loop and by how much maximum without causing the user client browser to timeout?
So that the user gets sufficient time to read the ads displayed on the page while he/she awaits for the file download to finish.
Also I'm not proficient with PHP environment.
(Pl. fogive me for the morality/immorality of this).
Try this approach: http://bytes.com/topic/php/answers/341922-using-php-limit-download-speed
You can use bandwidth sharing if you're willing to do this at the Apache level.
I am responsible for the backend portion of an API, written in PHP, which is primarily used by a Flash client. What happens right now is: the Flash client makes a call, the backend loads the necessary data, does any necessary processing and post processing, logging and caching and then returns the result to the client.
What I would like to have happen is return the data to the client as soon as possible, close the connection, and then do all the stuff that the client doesn't have to care about. This could make the API seem much more responsive. Following the suggestions here:
http://php.net/manual/en/features.connection-handling.php
actually works, except that I have to turn off gzip encoding in order to make it work, which isn't very practical. We use mod_deflate in apache, so a solution that works with that would be ideal, but I would also consider a different method to gzip our content if that is necessary.
It seems like there should be a way to let Apache know "I've sent you all the data I'm going to send," but I can't seem to find anything like that.
For those wondering, yes I can flush the results early, but the Flash client will not process them until the connection is closed.
You might try breaking it into two pages.
In the first page, do the necessary processing, then load the second page via curl, and die().
That would cause the first page to complete and close, independent of the second page processing.
ie:
Page 1:
<?php
// Do stuff
// Post or get second page...
// Send Data to client
die();
?>
Page 2:
<?php
// Do other stuff....
?>
See http://www.php.net/curl
There's a kind of hack to do this by placing the code you want to execute after the connection closes within a callback method registered via to register_shutdown_function();
#Theo.T since the comment system mangled the crap out of my code, I'm posting it here:
No luck. The following prints out the extra crap and takes the full execution time to close the connection when using mod_deflate:
function sleepLongTime() {
print "you can't see this";
sleep(30);
}
ob_end_clean();
register_shutdown_function('sleepLongTime');
header("Connection: close\r\n");
ignore_user_abort(true);
ob_start();
echo ('Text user will see');
ob_end_flush();
flush();
ob_end_clean();
die();
set_time_limit(0);
header("Connection: close");
header("Content-Length: " .(strlen($stream)+256));
ignore_user_abort(true);
echo $stream;
echo(str_repeat(' ',256));
#ob_flush();
#flush();
#ob_end_flush();
your_long_long_long_long_function_here();
this will tell the user to close the connection once all of $stream is received . but be careful not to echo anything before the header part u know :p
if you are sending binary data (swf) you might need to remove the '+256' and echo(str_repeat(' ',256)); but in this case the code 'might' fail if the data sent is les than 256 bytes .
Today I also met this case, after some tests around, I found this way works:
Two steps:
Make sure the php script output is not with gzip encoding, the solution can refer to this link:
<IfModule mod_env.c>
SetEnvIfNoCase Request_URI "\.php$" no-gzip dont-vary
</IfModule>
Add the above to .htaccess file under the prj web site, then avoid apache gzip it automatically.
As some people said at features.connection-handling.php,
set_time_limit(0);
ignore_user_abort(true);
// buffer all upcoming output - make sure we care about compression:
if(!ob_start("ob_gzhandler"))
ob_start();
echo $stringToOutput;
// get the size of the output
$size = ob_get_length();
// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');
// flush all output
ob_end_flush();
ob_flush();
flush();
// close current session
if (session_id()) session_write_close(); //close connection
// here, do what you want.