2009-09-09 14 views
9

मैं एफ़टीपी से HTTP के माध्यम से उपयोगकर्ता के ब्राउज़र में फ़ाइल को स्ट्रीम/पाइप करने की कोशिश कर रहा हूं। यही है, मैं एक 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 
} 
इस दृष्टिकोण के साथ

, मैं केवल उपयोगकर्ता के ब्राउज़र के लिए छोटे फ़ाइलें भेजने के लिए कर रहा हूँ। बड़ी फ़ाइलों के लिए, कुछ भी नहीं मुद्रित हो जाता है और मैं एक गंभीर त्रुटि (अपाचे लॉग से पठनीय) मिलती है:

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

मैं सफलता के बिना php://stdout साथ php://output की जगह की कोशिश की (कुछ भी नहीं ब्राउज़र के लिए भेजा जा रहा है)।

ब्राउज़र में उस डेटा को एक ही समय में भेजने के दौरान मैं कुशलता से एफ़टीपी से कैसे डाउनलोड कर सकता हूं?

नोट: मैं file_get_contents('ftp://user:[email protected]:port/path/to/file'); या इसी तरह का उपयोग नहीं करना चाहूंगा।

+0

मुझे भी इस उत्तर में वास्तव में दिलचस्पी होगी! – knittl

उत्तर

8

मिले एक समाधान!

सॉकेट जोड़ी (अनाम पाइप?) बनाएं। पाइप के एक छोर पर लिखने के लिए गैर-अवरुद्ध ftp_nb_fget फ़ंक्शन का उपयोग करें, और पाइप के दूसरे छोर पर echo का उपयोग करें।

तेजी से (100 एमबीपीएस कनेक्शन पर आसानी से 10 एमबी/एस) का परीक्षण किया गया है इसलिए बहुत अधिक 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); 
    } 
} 
+0

नोट: इसे काम करने के लिए बंद होने के लिए सामग्री बफरिंग की आवश्यकता है। वास्तविक उत्तर के लिए – LiraNuna

+0

+1। उन लोगों के लिए जो 'stream_socket_pair' में सभी स्थिरांक के लिए सोच रहे हैं, यहां एक नज़र डालें: http://php.net/manual/en/stream.constants.php –

1

लगता है जैसे आपको उस पृष्ठ के लिए आउटपुट बफरिंग बंद करना होगा, अन्यथा PHP सभी मेमोरी में फिट करने का प्रयास करेगा। > मिल(), और मुझे लगता है कि आपकी समस्या का समाधान होगा -

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

रखें कि आपकी कॉल से आगे रहे हैं:

यह करने के लिए एक आसान तरीका है की तरह कुछ है।

+0

मुझे 'while (ob_get_length()) ob_end_clean() का उपयोग करना था, इसके बजाए इसे चलाने के लिए। हालांकि, मुझे अभी भी ओपी में वर्णित घातक त्रुटि मिलती है। – strager

0

(मैं कभी नहीं इस समस्या को अपने आप को मिले हैं, जिससे कि सिर्फ एक जंगली अनुमान है, हो सकता है लेकिन, ...)

शायद "फ़ाइल" के लिए ouput बफर के आकार बदलने आप लिख रहे हैं मदद करने के लिए?

इसके लिए, stream_set_write_buffer देखें।

उदाहरण के लिए:

$fp = fopen('php://output', 'w+'); 
stream_set_write_buffer($fp, 0); 
इस के साथ

, अपने कोड एक गैर बफ़र धारा का उपयोग करना चाहिए - यह मदद कर सकता है ...

+0

एक अच्छा समाधान की तरह लगता है, लेकिन यह काम नहीं किया। – strager

5

प्रयास करें:

@readfile('ftp://username:[email protected]/path/file')); 

मैं के साथ लगता है बहुत सारे फाइल ऑपरेशंस यह अंतर्निहित है कि अंतर्निहित ओएस कार्यक्षमता आपके लिए इसका ख्याल रखती है।

+2

क्या उपयोगकर्ता नाम और पासवर्ड से बचने का कोई तरीका है ताकि यदि उनमें '@' या '/' जैसे वर्ण हों, तो यह ठीक से पढ़ा जाएगा? – strager

0

एक त्वरित खोज php के flush लाया।

इस लेख भी ब्याज की हो सकती है: http://www.net2ftp.org/forums/viewtopic.php?id=3774

+0

मैंने सफलता के बिना 'ftp_nb_fget' के साथ 'fflush' का उपयोग करने का प्रयास किया। – strager

+0

फ्लश, एफएफएलश नहीं;) – knittl

+0

नहीं, 'फ्लश' या तो काम नहीं करता था। =] – strager

1

मैं जानता हूँ कि इस पुरानी है, लेकिन कुछ अभी भी सोच सकते हैं यह उपयोगी है।

मैं एक 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]); 

मैं 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:[email protected]$host/$filename", false, $ctx); 

यह पीएचपी 5.4.5 के साथ एक आकर्षण की तरह काम किया:

तो मैं एक और दृष्टिकोण का उपयोग करने का फैसला किया। बुरा हिस्सा यह है कि आप स्थानांतरित डेटा, केवल खंड आकार को पकड़ नहीं सकते हैं।

संबंधित मुद्दे