Appliquer à tout fichier généré le principe de Drupal Imagecache

Il arrive régulièrement d'avoir à générer des fichier fruits de traitement lourd pour téléchargement. Cela peut être le PDF d'un article, une archive TGZ, etc. Dans ce cas de figure il n'est pas acceptable de répéter cette opération à chaque requête d'un visiteur, il faut donc mettre tout cela en cache. Et dans ce domaine, rien de mieux que d'exploiter le comportement d'imagecache, c'est à dire générer le fichier une première fois en PHP, pour laisser Apache faire son boulot.

A l'origine de cette Snippet, se trouvait cet article sur la génération des PDF grace à webkittohtml. En effet, la génération par ce biais a beau être beaucoup plus véloce qu'avec n'importe quelle librairie PHP, il n'en reste pas moins ridicule de générer systématiquement un PDF à chaque click.

Le principe est de mettre sur la page un lien vers sites/default/files/cachepdf/NID.pdf (typiquement dans le node.tpl.php). Lorsque l'utilisateur click dessus la première fois, Apache ne trouve pas le fichier et passe du coup la main à l'index.php de Drupal. Il suffit donc d'implémenter dans un module custom un hook_menu un handler de menu liant sites/default/files/cachepdf à une fonction callback qui va fabriquer le pdf :

function anycache_menu() {
  $items[file_directory_path() . "/cachepdf"] = array(
      'type' => MENU_CALLBACK,
      'page callback' => 'mon_module_pages_download_pdf',
      'access arguments' => array(
          'access content'
      )
  );

  return $items;
}
implémentation de hook_menu dans mon_module.module

Et maintenant la callback :

function mon_module_pages_download_pdf() {
  // On récupéré ici les éléments du chemin sites/default/files/cachepdf/NID.pdf
  $base_path = file_directory_path() . "/cachepdf";
  $file_name = trim(substr($_GET['q'], strlen($base_path) + 1), '/');
  list($nid,$ext)=explode(".", $file_name);

  // Génération et stockage du PDF. C'est totalement inefficient (car peut utiliser beaucoup
  // de mémoire) mais c'est juste pour l'exemple.
  // Dans la vraie vie, dans le cas du PDF, on utiliserait lirait/uploaderait et écrirait dans
  // la même boucle.
  $content=file_get_contents(url("printpdf/$nid", array('absolute'=>TRUE))); // Lecture du PDF à partir de l'URL fournie par le module printer
  file_put_contents("$base_path/$file_name"); // Maintenant Apache pourra trouver directement le fichier

  // Et maintenant on upload le fichier vers le client. On aurait pu utiliser la fonction
  // Drupal file_transfert (file.inc) mais sa valeur ajoutée est ici assez faible.
  header('Content-Disposition: attachment; filename="$file_name";');
  header("Content-Type: " . file_get_mimetype($file));
  header("Cache-Control: public, must-revalidate, max-age=0"); // HTTP/1.1
  header("Pragma: public");
  header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
  header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
  header("Content-Transfer-Encoding: binary");
  header("Content-Length: " . filesize($file));
  $block_size = 4096;
  $buffer = '';
  $handle = fopen($file, 'rb');
  if ($handle === FALSE) {
    return FALSE;
  }
  while (!feof($handle)) {
    $buffer = fgets($handle, $block_size);
    echo $buffer;
  }
  fclose($handle);
  exit();
}    
implémentation de la callback

Et voilà, maintenant le première appel à l'URL http://mon_site/sites/default/files/cachepdf/NID.pdf provoque sa génération et le renvoie au client web, et pour le second, Apache détecte la présence du fichier sites/default/files/cachepdf/NID.pdf et le renvoie directement à l'utilisateur, SANS faire appel à Drupal.

Pour compléter le dispositif, notre module devra en outre implémente nodeapi pour détecter une modification sur un node et ainsi détruire l'éventuel pdf associé de sorte à ce que la génération puisse se faire au prochain coup.

Vus : 782
Publié par arNuméral : 54