Make PHP faster to provide file download code

  • 2020-05-17 04:56:53
  • OfStack

However, there is no way to do some statistics, permission checking, etc. Therefore, in many cases, we use PHP to do forwarding and provide users with files to download.
 
<?php 
$file = "/tmp/dummy.tar.gz"; 
header("Content-type: application/octet-stream"); 
header('Content-Disposition: attachment; filename="' . basename($file) . '"'); 
header("Content-Length: ". filesize($file)); 
readfile($file); 

But this has a problem, is that if the file is Chinese name, some users may download the file name is garbled.

Therefore, we make 1 modification (see:
 
<?php 
$file = "/tmp/ Chinese name .tar.gz"; 
$filename = basename($file); 
header("Content-type: application/octet-stream"); 
// Processing Chinese file names  
$ua = $_SERVER["HTTP_USER_AGENT"]; 
$encoded_filename = urlencode($filename); 
$encoded_filename = str_replace("+", "%20", $encoded_filename); 
if (preg_match("/MSIE/", $ua)) { 
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"'); 
} else if (preg_match("/Firefox/", $ua)) { 
header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"'); 
} else { 
header('Content-Disposition: attachment; filename="' . $filename . '"'); 
} 
header('Content-Disposition: attachment; filename="' . $filename . '"'); 
header("Content-Length: ". filesize($file)); 
readfile($file); 

Well, it looks much better now, but there is one problem, and that is readfile. Although PHP's readfile is trying to be as efficient as possible without using PHP's own memory, it still needs to use MMAP(if supported), or a fixed buffer to read files in a loop and output them directly.

If the output is Apache + PHP mod, then it needs to be sent to the output buffer of Apache before it is sent to the user. For Nginx + fpm, if they are deployed separately, there will be additional network IO.

So, how about getting Webserver to send the file directly to the user without going through the PHP layer?

Today, I came across an interesting article: How I PHP: X-SendFile.

We can use module mod_xsendfile of Apache to ask Apache to send this file directly to the user:

 
<?php 
$file = "/tmp/ Chinese name .tar.gz"; 
$filename = basename($file); 
header("Content-type: application/octet-stream"); 
// Processing Chinese file names  
$ua = $_SERVER["HTTP_USER_AGENT"]; 
$encoded_filename = urlencode($filename); 
$encoded_filename = str_replace("+", "%20", $encoded_filename); 
if (preg_match("/MSIE/", $ua)) { 
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"'); 
} else if (preg_match("/Firefox/", $ua)) { 
header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"'); 
} else { 
header('Content-Disposition: attachment; filename="' . $filename . '"'); 
} 
header('Content-Disposition: attachment; filename="' . basename($file) . '"'); 
// let Xsendfile Send a file  
header("X-Sendfile: $file"); 

The X-Sendfile header will be processed by Apache and the response file will be sent directly to Client.
Lighttpd and Nginx also have similar modules, so you can look for them if you are interested


Related articles: