⬆️ ⬇️

Limit the speed of downloading files using PHP

Sometimes it becomes necessary to limit the speed of downloading files by users. The reasons for this may be many (free and paid modes, user registration, etc.), but it is not always possible to purchase a server or configure it properly. In this topic, I propose to shift the task onto pure PHP.



Function:



 function loadfile ($filename, $speed=false) { //    } 
$ filename - the address of the file that we are going to give.

$ speed - download speed.





Remove limits and restrictions, start output buffering:



  // time limit
 set_time_limit (0);

 // continue to run when the user is disconnected
 ignore_user_abort (true);

 // start buffering
 ob_start ();


')

We collect the necessary data about the file:



  // file size
 $ filesize = filesize ($ filename);

 // time of the last file change
 $ filetime = gmdate ('r', filemtime ($ filename));

 // generate etag
 $ etag = md5 ($ filename. "=". $ filesize. "=". $ filetime);
 $ etag = substr ($ etag, 0, 8) .'- '. substr ($ etag, 8, 7) .'-'. substr ($ etag, 15, 8);




We form and give the headings:



  // If they “ask” not to give the whole file, but only a part of it (resume), then so “answer” 
 if (isset ($ _ SERVER ['HTTP_RANGE'])) {

	 // divide the string $ _SERVER ['HTTP_RANGE'] into substrings to have
	 // idea of ​​the necessary part of the file to the user
	 $ range = substr ($ _ SERVER ['HTTP_RANGE'], strpos ($ _ SERVER ['HTTP_RANGE'], '=') + 1);
	 $ from = (integer) (strtok ($ range, "-"));  // from which byte to start the part
	 $ to = (integer) (strtok ("-"));  // which byte to finish

	 // Give Headers
	 header ('HTTP / 1.1 206 Partial Content');
	 header ('Content-Range: bytes'. $ from .'-'. ($ to-1). '/'. $ filesize);
 } else {
	 header ('HTTP / 1.1 200 Ok');
 }

 // If the client has not transmitted the information about the last byte, then assign
 // independently.
 if ($ to == 0) $ to = $ filesize;
 if (empty ($ from)) $ from = 0;




Thread limit per user:



In order for the user to download the file at the required speed and cannot bypass the restriction with the help of programs providing downloads in several streams, we need to set their limit for one visitor. To do this, we will need to introduce an additional function that will check for the presence of already established connections. As a DBMS, I suggest using MySQL.



So, create a table. So let's call it `file_session`. In this example, we need only one field `session_ip` which will contain the IP-address of the download. If you have the necessary IP in the database, we return true, otherwise we write it down and return false.



  function is_active_user ($ clear = false) {
	 global $ dbi;  // this is a database connection
	 // check for connections from the user
	 $ result = mysql_query ("SELECT` session_ip` FROM `file_session` WHERE` session_ip` = '". $ _ SERVER [' REMOTE_ADDR ']."' LIMIT 1 ", $ dbi);
	 if (mysql_num_rows ($ result)) {
		 // if $ clear is set, then user
		 // broke the connection.  Delete the record.
		 if (! $ clear) {
			 return true;
		 } else {
			 mysql_query ("DELETE FROM` file_session` WHERE session_ip = '". $ _ SERVER [' REMOTE_ADDR ']."' LIMIT 1 ", $ dbi);
		 }
	 } else {
		 // if there is no entry, then add
		 mysql_query ("INSERT INTO` file_session` VALUES ('". $ _ SERVER [' REMOTE_ADDR ']."') ", $ dbi);
		 return false;
	 }
 }


Subsequently, the function can be upgraded by adding a check for logging, payment, etc.



Give the file:



  // Give Headers
 header ('ETag: "'. $ etag. '"');
 header ('Accept-Ranges: bytes');
 header ('Content-Length:'. ($ filesize- $ from));
 header ('Content-Type: application / octet-stream');
 header ('Last-Modified:'. gmdate ('r', filemtime ($ filename)));
 header ('Content-Disposition: attachment; filename = "'. $ filename. '";');

 // Check if the user has
 // active streams is_active_user ()
 // and whether it has disconnected connection_status ().
 while (is_active_user () and! connection_status ()) {
	 // we sleep while the user has active threads
	 sleep (1);
 }

 // Open the file
 $ f = fopen ($ filename, 'rb');

 // Set the pointer to the desired position
 fseek ($ f, $ from, SEEK_SET);

 // Set the total volume of the part and declare a variable that stores the downloaded volume
 $ size = $ to - $ from;
 $ isready = 0;

 // Getting Returns
 while (! feof ($ f) and! connection_status () and ($ isready <$ size)) {
	 // If there are no restrictions, then we read 0.5 MB each and give it without delay.
	 // Is the limit set?  We read and give exactly as much as set by the limit and fall asleep for 1 second.
	 echo fread ($ f,! $ speed? 512000: $ speed);  // read and give
	 flush ();  ob_flush ();  // clear the buffer and output to the user
	 if ($ speed) sleep (1);  // fall asleep
	 $ isready + =! $ speed? 512,000: $ speed;  // update counter
 }

 // Close the file
 fclose ($ f);

 // Delete the connection information from the database
 is_active_user (true);




Function call:



  loadfile ("/files/moifilm.avi", 10240);  // Specify the speed in bytes.




Total:



The full code and working example of downloading without restriction and with restriction in 10 Kb / sec - a file of 1 Mb.



PS My first habrapost (:

Source: https://habr.com/ru/post/51442/



All Articles