2010-12-09 35 views
0

J'utilise actuellement des services Web SOAP externes qui permettent des téléchargements/uploads groupés de fichiers binaires (devraient permettre des fichiers plus volumineux). Je dois permettre à un utilisateur final de télécharger des fichiers via le navigateur avec une application PHP. Le traitement de petits fichiers fonctionne bien, mais les fichiers de plus de 25 Mo font que le serveur Web manque de mémoire. J'utilise le natif PHP Soap Client (pas de support MTOM), et demande le téléchargement en soumettant un formulaire. Actuellement, il semble que le serveur Web tente de télécharger l'ensemble du fichier avant d'envoyer quoi que ce soit au navigateur (par exemple, l'invite "Télécharger" n'apparaît qu'après que le fichier entier ait été traité via PHP).Diffusion de fichiers volumineux à partir d'un service Web externe via PHP

Ma méthode ressemble à quelque chose comme ça (désolé si c'est désordonné, j'ai été piratage à ce problème pendant un certain temps).

public function download() 
{ 
    $file_info_from_ws ... //Assume setup from $_REQUEST params 

    //Don't know if these are needed 
    gc_enable(); 
    set_time_limit(0); 
    @apache_setenv('no-gzip', 1); 
    @ini_set('zlib.output_compression', 0); 

    //File Info 
    $filesize = $file_info_from_ws->get_filesize(); 
    $fileid = $file_info_from_ws->get_id(); 
    $filename = $file_info_from_ws->get_name(); 
    $offset = 0; 
    $chunksize = (1024 * 1024); 

    //Clear any previous data 
    ob_clean(); 
    ob_start(); 

    //Output headers 
    header('Content-Type: application/octet-stream'); 
    header('Content-Length: ' . $filesize); 
    header('Content-Transfer-Encoding: binary'); 
    header('Content-Disposition: attachment; filename="' . $filename . '"'); 
    header('Accept-Ranges: bytes'); 

    while($offset < $filesize) 
    { 
     $chunk = $this->dl_service()->download_chunked_file($fileid, $offset, $chunksize); 
     if($chunk) 
     { 
     //Immediately echo out the stream 
     $chunk->render(); 
     $offset += $chunksize; 
     unset($chunk); //Shouldn't this trigger GC? 
     ob_flush(); 
     } 
    } 
    ob_end_flush(); 
} 

Ma question principale est: Quelle est la meilleure façon de sortie de gros morceaux binaires de ressources externes (Webservices, DB, etc.) par le biais de PHP à l'utilisateur final? De préférence sans tuer la mémoire/CPU trop.

Aussi je suis curieux de savoir ce qui suit:
Pourquoi pas le téléchargement rapide pop-up après la première sortie?
Pourquoi la mémoire n'est-elle pas libérée après chaque boucle dans la méthode about?

Répondre

1

Eh bien, je me sens idiot. Cela s'est avéré être juste un autre PHP-isme. Apparemment, même si je vidais le tampon de sortie avec ob_flush, ce qui (je pensais) aurait dû envoyer les en-têtes et les morceaux au navigateur, les en-têtes et la sortie n'étaient pas réellement envoyés au navigateur jusqu'à la fin du script.

Même si la sortie est auto a été vidée, vous devez toujours explicitement flush les tampons d'écriture de PHP et le serveur Web vers le client. Ne pas le faire conduire à l'expansion de la mémoire, et l'invite de téléchargement ne s'affiche pas jusqu'à ce que le téléchargement complet terminé.

Voici une version de la méthode de travail:

public function download() 
{ 
    $file_info ... //Assume init'ed from WS or DB 

    //Allow for long running process 
    set_time_limit(0); 

    //File Info 
    $filesize = $file_info->get_filesize(); 
    $fileid = $file_info->get_id(); 
    $filename = $file_info->get_name(); 
    $offset = 0; 
    $chunksize = (1024 * 1024); 

    //Clear any previous data 
    ob_clean(); 
    ob_start(); 

    //Output headers to notify browser it's a download 
    header('Content-Type: application/octet-stream'); 
    header('Content-Length: ' . $filesize); 
    header('Content-Disposition: attachment; filename="' . $filename . '"'); 

    while($offset < $filesize) 
    { 
     //Retrieve chunk from service 
     $chunk = $this->dl_service()->download_chunked_file($fileid, $offset, $chunksize); 
     if($chunk) 
     { 
     //Immediately echo out the stream 
     $chunk->render(); 
     //NOTE: The order of flushing IS IMPORTANT 
     //Flush the data to the output buffer 
     ob_flush(); 
     //Flush the write buffer directly to the browser 
     flush(); 
     //Cleanup and prepare next request 
     $offset += $chunksize; 
     unset($chunk); 
     } 
    } 
    //Exit the script immediately to prevent other output from corrupting the file 
    exit(0); 
} 
+0

Vous pouvez également utiliser ob_implicit_flush au lieu d'appeler flush() à chaque fois. –

1

http://php.net/manual/en/function.fpassthru.php

Cela peut être un peu d'aide. Cela peut aussi changer la façon dont vous voulez tout faire.

+0

fopen peut ouvrir des URL externes. Cela devrait donc fonctionner ... en théorie. – DampeS8N

+0

Malheureusement, l'API ne peut pas être appelée directement par URL. Parce que WebService est un service SOAP d'entreprise très complexe, et que la méthode download_chunk nécessite en réalité des objets assez complexes en tant que paramètres. –

+0

C'est vraiment dommage. C'est l'une des principales raisons pour lesquelles je déteste SOAP. – DampeS8N

0

Essayez ci-dessous le code qui fonctionne pour moi

public function testUpload(){ 

    $response = array(); 
    if (empty($_FILES) || $_FILES['file']['error']) { 
    $response["code"] = 2; 
      $response["message"] = "failed to move uploaded file"; 
      echo json_encode($response); 
    } 

    $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0; 
    $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0; 

    $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"]; 
    $filePath = "uploads/$fileName"; 

    // Open temp file 
    $out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab"); 

    if ($out) { 
     // Read binary input stream and append it to temp file 
     $in = @fopen($_FILES['file']['tmp_name'], "rb"); 

     if ($in) { 
     while ($buff = fread($in, 4096)) 
       fwrite($out, $buff); 

     } else 

    $response["code"] = 2; 
    $response["message"] = "Oops! Failed to open input Stream error occurred."; 
    echo json_encode($response); 
     @fclose($in); 

     @fclose($out); 

     @unlink($_FILES['file']['tmp_name']); 
    } else 

     $response["code"] = 2; 
     $response["message"] = "Oops! Failed to open output error occurred."; 
     echo json_encode($response); 


    // Check if file has been uploaded 
    if (!$chunks || $chunk == $chunks - 1) { 
     // Strip the temp .part suffix off 
     rename("{$filePath}.part", $filePath); 
    } 
    $response["code"] = 2; 
    $response["message"] = "successfully uploaded"; 
    echo json_encode($response); 
} 

to know more click