Use X SendFile header in PHP for faster file download

  • 2021-06-28 11:42:33
  • OfStack

1 Generally speaking, we can direct URL to a file located under Document Root to guide users to download the file.

However, in doing so, there is no way to do some statistics, permission checking, and so on. So, many times, we use PHP for forwarding, providing users with file downloads.


<?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);

However, there is a problem, that is, if the file name is Chinese, some users may download the file name is garbled.

So let's make a change:


<?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 = rawurlencode($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-Length: ". filesize($file));
    readfile($file);

Yes, it looks much better now, but there is one more problem: readfile. Although readfile of PHP tries to be as efficient as possible without taking up PHP's own memory, it actually needs to use MMAP (if supported) or a fixed buffer to read files in a loop and output them directly.

When outputting, if Apache + PHP mod, then you also need to send the output buffer to Apache. Last sent to the user. For Nginx + fpm, if they are deployed separately, that will also result in additional network IO.

So can Webserver send files directly to users without going through the PHP layer?

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

We can use module mod_from Apachexsendfile, let Apache 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 = rawurlencode($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 . '"');
    }

    // Give Way Xsendfile send files 
    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 have similar modules, which you may find interesting


Related articles: