【问题标题】:Stream FTP download to output流 FTP 下载到输出
【发布时间】:2010-11-26 16:47:16
【问题描述】:

我正在尝试通过 HTTP 从 FTP 将文件流式传输/管道传输到用户的浏览器。也就是说,我正在尝试在 FTP 服务器上打印文件的内容。

这是我目前所拥有的:

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 看起来像这样:

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

使用这种方法,我只能将小文件发送到用户的浏览器。对于较大的文件,不会打印任何内容,并且出现致命错误(可从 Apache 日志中读取):

PHP 致命错误:第 200 行 /xxx/ftpconnection.php 中允许的内存大小为 16777216 字节已用尽(尝试分配 15994881 字节)

我尝试用php://stdout 替换php://output,但没有成功(似乎没有任何东西发送到浏览器)。

如何在将数据发送到浏览器的同时有效地从 FTP 下载?

注意:我不想使用file_get_contents('ftp://user:pass@host:port/path/to/file'); 或类似的。

【问题讨论】:

  • 我也会对这个答案很感兴趣!

标签: php ftp


【解决方案1】:

听起来您需要关闭该页面的输出缓冲,否则 PHP 会尝试将其放入所有内存中。

一个简单的方法是这样的:

while (ob_end_clean()) {
    ; # do nothing
}

把它放在你调用 ->get() 之前,我认为这会解决你的问题。

【讨论】:

  • 我不得不使用while(ob_get_length()) ob_end_clean(); 来让它运行。但是,我仍然收到 OP 中描述的致命错误。
【解决方案2】:

(我自己从来没有遇到过这个问题,所以这只是一个疯狂的猜测;但是,也许......)

也许更改您正在写入的“文件”的输出缓冲区的大小可能会有所帮助?

为此,请参阅stream_set_write_buffer

例如:

$fp = fopen('php://output', 'w+');
stream_set_write_buffer($fp, 0);

这样,您的代码应该使用非缓冲流——这可能会有所帮助...

【讨论】:

    【解决方案3】:

    试试:

    @readfile('ftp://username:password@host/path/file');
    

    我发现有很多文件操作值得让底层操作系统功能为您处理。

    【讨论】:

    • 有什么办法可以让用户名和密码转义,这样如果它们包含@/ 之类的字符,它就会正确读取?
    【解决方案4】:

    快速搜索找到了 php 的flush

    这篇文章可能也很有趣:http://www.net2ftp.org/forums/viewtopic.php?id=3774

    【讨论】:

      【解决方案5】:

      找到了解决办法!

      创建一个套接字对(匿名管道?)。使用非阻塞ftp_nb_fget函数写入管道的一端,echo管道的另一端。

      测试为快速(在 100Mbps 连接上轻松达到 10MB/s),因此没有太多 I/O 开销。

      请务必清除所有输出缓冲区。框架通常会缓冲您的输出。

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

      【讨论】:

      【解决方案6】:

      我知道这是旧的,但有些人可能仍然认为它很有用。

      我已经在 Windows 环境中尝试过您的解决方案,它几乎完美运行:

      $conn_id = ftp_connect($host);
      ftp_login($conn_id, $user, $pass) or die();
      
      $sockets = stream_socket_pair(STREAM_PF_INET, STREAM_SOCK_STREAM,
              STREAM_IPPROTO_IP) or die();
      
      stream_set_write_buffer($sockets[0], 0);
      stream_set_timeout($sockets[1], 0);
      
      set_time_limit(0);
      $status = ftp_nb_fget($conn_id, $sockets[0], $filename, FTP_BINARY);
      
      while ($status === FTP_MOREDATA) {
          echo stream_get_contents($sockets[1]);
          flush();
          $status = ftp_nb_continue($conn_id);
      }
      echo stream_get_contents($sockets[1]);
      flush();
      
      fclose($sockets[0]);
      fclose($sockets[1]);
      

      由于 Windows,我使用了 STREAM_PF_INET 而不是 STREAM_PF_UNIX,它完美地工作......直到最后一个块,即 false,没有明显的原因,我不明白为什么。所以输出缺少最后一部分。

      所以我决定使用另一种方法:

      $ctx = stream_context_create();
      stream_context_set_params($ctx, array('notification' =>
              function($code, $sev, $message, $msgcode, $bytes, $length) {
          switch ($code) {
              case STREAM_NOTIFY_CONNECT:
                  // Connection estabilished
                  break;
              case STREAM_NOTIFY_FILE_SIZE_IS:
                  // Getting file size
                  break;
              case STREAM_NOTIFY_PROGRESS:
                  // Some bytes were transferred
                  break;
              default: break;
          }
      }));
      @readfile("ftp://$user:$pass@$host/$filename", false, $ctx);
      

      这就像 PHP 5.4.5 的魅力一样。不好的部分是您无法捕获传输的数据,只能捕获块大小。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-16
        • 1970-01-01
        • 2017-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多