Stream FTP download to output

后端 未结 6 1191
耶瑟儿~
耶瑟儿~ 2020-12-03 06:06

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

6条回答
  •  醉酒成梦
    2020-12-03 06:39

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

提交回复
热议问题