Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stream FTP download to output

Tags:

php

ftp

I am trying to stream/pipe a file to the user's browser through HTTP from FTP. That is, I am trying to print the contents of a file on an FTP server.

This is what I have so far:

public function echo_contents() {                    
    $file = fopen('php://output', 'w+');             

    if(!$file) {                                     
        throw new Exception('Unable to open output');
    }                                                

    try {                                            
        $this->ftp->get($this->path, $file);         
    } catch(Exception $e) {                          
        fclose($file);  // wtb finally               

        throw $e;                                    
    }                                                

    fclose($file);                                   
}                                                    

$this->ftp->get looks like this:

public function get($path, $stream) {
    ftp_fget($this->ftp, $stream, $path, FTP_BINARY);  // Line 200
}

With this approach, I am only able to send small files to the user's browser. For larger files, nothing gets printed and I get a fatal error (readable from Apache logs):

PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 15994881 bytes) in /xxx/ftpconnection.php on line 200

I tried replacing php://output with php://stdout without success (nothing seems to be sent to the browser).

How can I efficiently download from FTP while sending that data to the browser at the same time?

Note: I would not like to use file_get_contents('ftp://user:pass@host:port/path/to/file'); or similar.

like image 547
strager Avatar asked Sep 09 '09 01:09

strager


People also ask

Can I stream from FTP?

If you are looking for an app in android, there is a player for android which will allow streaming from a ftp server. it's called FIPE Video Player. It's free, neat and stream in high quality.

How do I download data from an FTP site?

To download a file, drag the file from the browser window to the desktop. You can also double-click the filename, and you will be prompted to either save or open the file. To upload a file, drag the file from your hard drive to the browser window.

How do I automatically download files from an FTP server?

Go to File >> New Connection Profile. On the create connection profile dialog, select the Automated Profile option. On the next page, provide the details required to connect to the FTP server. On the action rules page, select the option to download files from your FTP server.

How do I download and upload files using FTP?

Upload Single File to FTP Server To upload file on FTP server use put command from FTP prompt. First, navigate to the desired directory on the FTP server where to upload a file and use the following command. It will upload local system file c:\files\file1. txt to uploads directory on FTP server.


1 Answers

Found a solution!

Create a socket pair (anonymous pipe?). Use the non-blocking ftp_nb_fget function to write to one end of the pipe, and echo the other end of the pipe.

Tested to be fast (easily 10MB/s on a 100Mbps connection) so there's not much I/O overhead.

Be sure to clear any output buffers. Frameworks commonly buffer your output.

public function echo_contents() {
    /* FTP writes to [0].  Data passed through from [1]. */
    $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);

    if($sockets === FALSE) {
        throw new Exception('Unable to create socket pair');
    }

    stream_set_write_buffer($sockets[0], 0);
    stream_set_timeout($sockets[1], 0);

    try {
        // $this->ftp is an FtpConnection
        $get = $this->ftp->get_non_blocking($this->path, $sockets[0]);

        while(!$get->is_finished()) {
            $contents = stream_get_contents($sockets[1]);

            if($contents !== false) {
                echo $contents;
                flush();
            }

            $get->resume();
        }

        $contents = stream_get_contents($sockets[1]);

        if($contents !== false) {
            echo $contents;
            flush();
        }
    } catch(Exception $e) {
        fclose($sockets[0]);    // wtb finally
        fclose($sockets[1]);

        throw $e;
    }

    fclose($sockets[0]);
    fclose($sockets[1]);
}

// class FtpConnection
public function get_non_blocking($path, $stream) {
    // $this->ftp is the FTP resource returned by ftp_connect
    return new FtpNonBlockingRequest($this->ftp, $path, $stream);
}

/* TODO Error handling. */
class FtpNonBlockingRequest {
    protected $ftp = NULL;
    protected $status = NULL;

    public function __construct($ftp, $path, $stream) {
        $this->ftp = $ftp;

        $this->status = ftp_nb_fget($this->ftp, $stream, $path, FTP_BINARY);
    }

    public function is_finished() {
        return $this->status !== FTP_MOREDATA;
    }

    public function resume() {
        if($this->is_finished()) {
            throw BadMethodCallException('Cannot continue download; already finished');
        }

        $this->status = ftp_nb_continue($this->ftp);
    }
}
like image 70
strager Avatar answered Oct 05 '22 22:10

strager