Home > Back-end >  How to enable X-Accel-Redirect
How to enable X-Accel-Redirect

Time:02-19

I need to send files created by tmpfile() on the serverside to the browser.

I am using two difeerent Docker containers: php and nginx (docker compose).

My nginx conf (Docker container nginx):

server {
    listen 80 default_server;
 
    index index.php index.html index.htm;
 
    root /app;

    location ~ [^/]\.php(/|$) {
        autoindex on;
        autoindex_exact_size on;
        try_files $uri =404;
        fastcgi_split_path_info ^(. \.php)(/. )$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
 
    error_log  stderr warn;
    access_log  /dev/stdout main;
}

My PHP code (Docker container php):

<?php

$file = tmpfile();
fwrite($file, "hello world");

$metaData = stream_get_meta_data($file);
$fileName = $metaData['uri'];

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Content-type: text/plain');
header('Content-Length: ' . filesize($fileName));
header('X-Sendfile: ' . $fileName);

fclose($file); 

But the browser only gets an empty page istead of a download popup.

CodePudding user response:

Ok, lets start from the beginning.

  1. It should be obvious that the nginx will need a direct access to the file that will be served via X-Accel-Redirect header. That means that you will need some kind of a shared volume between your nginx and php containers. Usually it isn't a problem (see the How To Share Data between Docker Containers article for example). But tmpfile() will create a temporary file in a temporary directory (/tmp or something like that) and making it a shared volume between the containers can be quite tricky.

  2. Even if you'd manage to solve the previous part, after the fclose($file); line the temporary file will be closed and deleted. That's the main part why trying to use tmpfile() and X-Accel-Redirect together is a bad idea. Well, you can try to flash the output buffers and wait a couple of time to give nginx a chance to read the file before your script closes and deletes the file, but I'm not sure it will work at all and it doesn't seems to be a good solution either.

What can you do instead?

First of all, you don't need to stuck with the X-Accel-Redirect at all. It can give you significant performance benefit when you want to serve the already existed file using nginx. But you are about to create that file first, writing a couple of data to disk with PHP and reading it from disk with the nginx. You can simply write that data directly to the STDOUT.

The disadvantage of that approach is that you don't know an amount of your data beforehand and can't set the proper Content-Length header making your data served via chunked-encoded HTTP stream. If you don't want to do it that way, you can use the output buffering ob_... functions. Start buffering with the ob_start(), then after all your data has been written to the buffer use ob_get_contents() to get its contents and ob_get_length() to get the data size and then close output buffer with the ob_end_clean(). Now you will be able to properly set the Content-Length header before sending the data to the STDOUT (or return a proper error code if something goes wrong).

As a last resort, if you are still want to use exactly the tmpfile() one, you can output its contents to the STDOUT before closing/deleting it using the readfile() function.

  • Related