xmlhack.ru logo
>> статьи на xmlhack.ru

RSS-каналы для FTP-серверов

Автор: Марк Вудман
Перевод: Галина Сырку
Опубликовано на XML.com (22.03.2006, англ.): http://www.xml.com/pub/a/2006/03/22/rss-feeds-for-ftp-servers.html
Опубликовано на xmlhack.ru (01.04.2006, рус.): http://xmlhack.ru/texts/06/rss-feeds-for-ftp/rss-feeds-for-ftp.html
В закладки:   Del.icio.us   reddit

Применение технологии RSS (Really Simple Syndication, действительно простой сбор информации) вышло далеко за пределы простого способа распространения новостей. В настоящее время RSS используется для всего, начиная с программ слежения и вплоть до инвентаризации товаров торговца машинами. Это отражается в одном из главных аспектов технологии RSS: она может быть использована для того, чтобы сообщать о каком-либо интересующем событии, чем избавит вас от постоянных проверок. Данная статья расскажет, как написать сценарий PHP, который вместо вас будет наблюдать за FTP-сервером, уведомляя о новейших дополнениях или изменениях файлов.

PHP, FTP и вы

Несмотря на акцент, поставленный на связанные со Всемирной сетью функциональные возможности, команды FTP в PHP часто упускаются из виду. Хорошей новостью является то, что эти функции включены в стандартные функции PHP 4, следовательно, не требуются внешние библиотеки.

Однако очень важно удостовериться, чтобы при установке PHP на вашем сервере были включены функции FTP. Для этого надо использовать phpinfo() в обыкновенном файле, чтобы проверить, что включено в PHP:

<?php  
   phpinfo();  
?>

Просматривая результаты данного сценария с помощью вашего браузера, вы увидите классическую страницу PHP Info с почти всей информацией о конфигурации, которая может понадобиться. Прокрутите вниз до раздела FTP для того, чтобы удостовериться, что графа “FTP Support” была включена. Это должно выглядеть следующим образом:

Страница PHP Info с включенным FTP
Рис.1. Страница PHP Info, на которой показано, что FTP включен.

(Если функции FTP не были включены, вам необходимо принять меры для того, чтобы или включить их, или разместить сценарии данного учебника на другом сервере.)

Вы можете извлечь много информации из учебника PHP Manual on FTP functions (учебник PHP по функциям FTP), который будет для вас очень полезной ссылкой. Вы, скорее всего, захотите держать его неподалеку, пока будете изучать этот материал. (Ну, а если вы страдаете бессонницей, то всегда можете попытаться прочитать RFC 959, описание самого FTP).

Узнайте код

Для данного учебника мы создадим PHP-сценарий под именем ftp_monitor.php. Мы пройдем через этот сценарий по частям, но вы также можете скачать полную версию исходного текста для дальнейшего его использования.

Функция explorerFtpServer()

Давайте начнем с основ функциональных возможностей FTP, которые содержатся в функции explorerFtpServer(). Она принимает в качестве параметров имя хоста, имя пользователя, пароль и начальный путь. Цель данной функции — исследовать FTP-сервер, пробегая по любому каталогу, и затем возвратить ассоциативный массив с именами файлов и соответствующими этим файлам временными отметкам.

После объявления обозначения функции, используйте PHP-функцию ftp_connect() для попытки соединения с FTP-сервером. Если соединение было установлено, вы можете сохранить идентификатор соединения в переменной $cid для дальнейшего использования со всеми PHP-функциями, связанными с FTP-протоколом.

function exploreFtpServer($host, $user, $pass, $path)
{
   // Connect
   $cid = ftp_connect($host) or die("Couldn't connect to server");

Для упрощения, данный код без долгих рассуждений прерывает выполнение сценария, если FTP-соединение не может быть установлено. После использования этого сценария вам, возможно, захочется добавить немного больше обработки ошибок.

Раз мы установили соединение, попытаемся зарегистрироваться на сервере с помощью PHP-функции ftp_login(). Как и для многих других PHP-функций для FTP, первым параметром данной функции является идентификатор связи ($cid). Но данная функция также принимает как параметры имя пользователя и пароль.

Если вход в систему был выполнен успешно, будем использовать функцию ftp_pasv() для того, чтобы сообщить FTP-серверу о намерении использовать пассивный режим работы. Это означает, что все соединения будут запускаться с помощью сценария. Таким образом вы сможете запускать сценарий за файрволлом.

Теперь, когда соединение установлено, мы имеем возможность пройтись по каталогам FTP-сервера, начиная с указанного пути. Для этого будем использовать функцию scanDirectory(), которая будет описана сразу после этой функции.

И наконец, работает ли опознавание или нет, мы должны завершить соединение с сервером с помощью функции ftp_close(). В рамках данного примера мы остановим сценарий с помощью функции die() в случае отказа опознавания, но вы можете предпочесть обработку отказа каким-нибудь другим способом.

   // Login
   if (ftp_login($cid, $user, $pass))
   {
      // Passive mode
      ftp_pasv($cid, true);

      // Recurse directory structure
      $fileList = scanDirectory($cid, $path);

      // Disconnect
      ftp_close($cid);
   }
   else
   {
      // Disconnect
      ftp_close($cid);

      die("Couldn't authenticate.");
   }

На данный момент у нас есть заполненная переменная $fileList, которая является ассоциативным массивом. Ключами являются имена файлов, а значениями — временные отметки файлов. Массив будет более полезным в случае его сортировки по времени (по принципу «новейший — первый»), поэтому запустите сортировку с помощью функции arsort(), после чего возвратите новый массив.

   // Sort by timestamp, newest (largest number) first
   arsort($fileList);

   // Return the result
   return $fileList;
}

Функция scanDirectory()

Теперь мы готовы к тому, чтобы описать функцию scanDirectory(), которая вызывается из нашей функции exploreFtpServer(), описанной раннее. Цель данной функции заключается в просмотре FTP-каталога с поиском файлов и подкаталогов, добавляя первые к списку и рекурсивно просматривая последние. Передаваемыми параметрами являются идентификатор FTP соединения ( $cid) и стартовый каталог ( $dir). Мы также объявим статическую переменную $fileList, которая будет использоваться для сохранения списка наших файлов во время рекурсивных вызовов функции.

Для того, чтобы получить содержание данного каталога в FTP, будет использоваться функция ftp_nlist(). К сожалению, эта функция несовершенна. В случае большинства серверов, протестированных мной, она возвращала список имен файлов и каталогов. Но встретилось несколько серверов, например WU-FTPD, для которых возвращался только перечень имён файлов. Для таких серверов наш сценарий может наблюдать только за предусмотренным начальным каталогом; ни один подкаталог не будет исследован.

Альтернативой для использования ftp_nlist() является замена её функцией ftp_rawlist(), которая должна предоставить содержание всех каталогов независимо от их типов. К сожалению, формат данных, полученных от функции ftp_rawlist() не является стандартизированным. Как следствие, любые попытки предоставить «универсальный анализатор» оказываются обескураживающей задачей. (Читайте комментарии пользователей на функцию ftp_rawlist(), чтобы понять, что я имею в виду). Таким образом, для данного учебника будет лучше придерживаться несовершенной, но всё же намного более простой функции ftp_nlist().

function scanDirectory($cid, $dir)
{
   // Use static value to collect results
   static $fileList=array();

   // Get a listing of directory contents
   $contents = ftp_nlist($cid, $dir);

На данный момент переменная $contents заполнена элементарным списком имён файлов и каталогов. В зависимости от сервера, элементы списка могут содержать или не содержать путь к файлу. (Мы можем получить "foo.txt" либо "/i/pity/the/foo.txt").

Следующий раздел функции scanDirectory() будет повторно проходить через каждое имя и использовать функцию ftp_size() для того, чтобы определить, является ли данное имя именем файла или каталога. (Простейший трюк: каталоги всегда возвращают как объем -1.) Если же элемент является файлом, в случае необходимости мы прибавим косую черту к началу имени файла для согласования наших путей, а затем, используя функцию ftp_mdtm(), получим время последнего изменения. Затем мы добавим имя файла как ключевое поле в нашем ассоциативном массиве $fileList, а его время будет использоваться как значение.

     
   // Iterate through the directory contents
   if($contents!=null)
   {
      foreach ($contents as $item)
      {
         // Is the item a file?
         if (ftp_size($cid, $item)>=0)
         {
             // Prepend slash if not present
            if($item[0]!="/") $item = "/" . $item;

            // Add file and modify timestamp to results
            $fileList[$item] = ftp_mdtm($cid, $item);
         }

Теперь нам необходимо перейти к элементам, возвращенным функцией ftp_nlist(), как каталоги. Разумеется, мы будем игнорировать альтернативные имена одного и того же каталога или каталога родителя. Если у нас есть годное к употреблению имя каталога, мы можем вызвать функцию scanDirectory(), чтобы рекурсивно его достичь. (Данная процедура требует некоторой дополнительной логики для манипулирования различиями между серверами, которые используют полные или относительные пути.) Имея в наличии обработанные имена файлов, а также каталогов, мы можем возвратить переменную $fileList, содержащую теперь каждый найденный на данный момент файл. Вот как это выглядит на практике:

         else
         // Item is a directory
         {
            // Exclude self/parent aliases
            if($item!="." && $item!=".." &&
$item!="/")
            {
               // Server uses full path names
               if($item==strstr($item, $dir))
               {
                  scanDirectory($cid, $item);
               }
               else
               {
                  // Server uses relative path names
                  if($dir=="/")
                  {
                     scanDirectory($cid, $dir . $item);
                  }
                  else
                  {
                     scanDirectory($cid, $dir . "/" . $item);
                  }
               }
            }
         }
      }
   }

   // Return the results
   return $fileList;
}

Функция generateRssFeed()

Это всё начинает всё больше походить на справочник по PHP? Хорошая новость: трудная часть позади, и теперь всё, что нам осталось сделать — написать функцию, которая будет формировать RSS-канал.

Эта функция относится к тем, которые можно вызвать непосредственно из другого PHP-сценария, передав параметры, необходимые для её работы:

Первым делом мы должны вызвать нашу функцию exploreFtpServer() для получения списка файлов и их временных отрезков от FTP-сервера. Как только список получен, мы cможем определить, что меньше: список или значение переменной $itemCount, переданное функции, и использовать меньшее из двух значений:

function generateRssFeed($host, $user, $pass, $path,
$itemCount)
{   
   // Get array of file/time arrays
   $fileList = exploreFtpServer($host, $user, $pass, $path);

   // Use user's count or # of files found, whichever is smaller
   if(count($fileList)<$itemCount) $itemCount=count($fileList);

Перед тем, как продолжить, нам необходимо объявить несколько переменных. Первая из них — $linkPrefix — необходима для хранения имени хоста с FTP-протоколом в качестве приставки. Следующей переменной является $channelPubDate, необходимая для хранения даты выхода RSS-канала. Мы также создадим список $items для хранения элементов RSS, которые мы создадим.

   // Create link prefix for feed
   $linkPrefix = 'ftp://' . $host;

   // Declare date for channel/pubDate
   $channelPubDate = null;

   // Array for item strings
   $items = array();

Теперь мы готовы получить новейшие файлы и создать элементы RSS для каждого из них. Для данного учебника мы собираемся использовать простой и изящный способ хранения: для каждого элемента будут храниться заголовок, ссылка и дата. Однако вы можете свободно расширить канал большим количеством информации. (Одной из забавных идей было бы отображение пиктограммы файла согласно его расширению.)

Как вы помните, список $fileList, переданный функцией exploreFtpServer() сортируется по времени, новейший файл — первый. Проходя по списку, мы будем использовать временную отметку для создания даты выхода RSS-канала.


   // Create array of RSS items from most recent files
   foreach ($fileList as $filePath => $time)
   {
      // Create item/pubDate according to RFC822
      $itemPubDate = date("r", $time);

      // Also use first item/pubDate as channel/pubDate
      if($channelPubDate==null) $channelPubDate = itemPubDate;

Затем мы создадим путь к файлу URI, который начинается со строки «ftp://». Переменная $fileUri будет использоваться для заполнения заголовков элементов RSS, а также ссылок на них. Для обеспечения правильного формата URI все пробелы в названиях файлов должны быть заменены на закодированное значение "%20".

Теперь у нас есть вся необходимая информация и самое время создать файл XML для каждого элемента RSS. После создания, они будут добавлены к нашему списку $items для дальнейшего использования. Также мы должны обеспечить завершение цикла, когда достигнем пределов переменной $itemCount.

      // Create URI to ftp file
      $fileUri = ereg_replace(" ", "%20", $linkPrefix . $filePath);

      // Create item
      $item = '<item>' 
      .'<title>' . $fileUri . '</title>'
      .'<link>'. $fileUri . '</link>'
      .'<pubDate>' . $itemPubDate . '</pubDate>'
      .'</item>';

      // Add to item array
      array_push($items, $item);

      // If max items for feed reached, stop
      if(count($items)==$itemCount) break;
   }

В заключение мы создадим RSS-канал самостоятельно. Создать файл XML в PHP, используя строки, легко, но он редко выглядит хорошо. Обратите внимание, что мы используем функцию join() для объединения всех наших элементов RSS, добавляя символ конца строки после каждого из них. (Символы конца строки не обязательны, но они облегчают чтение каналов в случае исправления неполадок.)

   // Build the RSS feed
   $rss = '<rss version="2.0">'
   . '<channel>'
   . '<title>FTP Monitor: ' . $host . '</title>'
   . '<link>' . $linkPrefix . '</link>'
   . '<description>The ' . $itemCount.' latest changes on '
   . $host . $path . ' (out of ' . count($fileList) 
   . ' files)</description>'
   . '<pubDate>' . $channelPubDate . '</pubDate>' .
"\n"
   . 
join("\n", $items) . "\n"
   . '</channel>'
   . '</rss>';

Канал готов к использованию. Все, что осталось сделать — это установить заголовок HTTP для того, чтобы указать на наше возвращение к документу XML, а затем вывести канал на экран:

   // Set header for XML mime type
   header("Content-type: text/xml; charset=UTF-8");
   
   // Display RSS feed
   echo($rss);
}

И это последняя часть реальной работы. Если вам ещё не удалось выполнить её до конца, скачайте полную версию исходного текста ftp_monitor.php.

Заставьте это работать

После помещения файла ftp_monitor.php на ваш Веб сервер, поддерживающий PHP, вы сможете обращаться к нему из любого другого PHP-сценария. Приведем пример, как этот процесс может выглядеть:

<?php
  
   // Import the FTP Monitor
   require_once('ftp_monitor.php');
    
   // Connection params to monitor FreeBSD snapshots
   $host = "ftp.freebsd.org";
   $user = "anonymous";
   $pass = "guest@anon.com";
   $path = "/pub/FreeBSD/snapshots";
   
   // Generate RSS 2.0 feed showing newest FreeBSD snapshots
   generateRssFeed($host, $user, $pass, $path, 10);
?>

Далее приведен пример выходного файла после использования вышеуказанных параметров соединения freebsd.xml. При просмотре с помощью SharpReader элементы выглядят следующим образом:

Результаты работы FTP-монитора для ftp.freesbd.org
Рис. 2. Результаты работы FTP-монитора для ftp.freesbd.org

Многие RSS-агрегаторы автоматически следуют ссылкам к элементам, если они не указаны в их описании. Программа SharpReeder является одним из таким агрегаторов, она также поддерживает протокол ftp://. Таким образом, нажатие на один из элементов нашего FTP-монитора поведёт за собой автоматическое их закачивание. Это обычно действительно хорошо работает, если FTP-сервер допускает анонимные подключения. Однако если вам необходимо предоставлять действительные имя пользователя и пароль для ftp_monitor.php, ваша способность «нажать и закачать» будет зависеть от возможности читающего устройства RSS указать на ваши идентификационные данные FTP.

Улучшите производительность

Существует несколько предостережений, которые нужно иметь в виду, используя данный сценарий. Во-первых, многие FTP-серверы не совсем скоростные. Таким образом, производительность сценария будет ограничена временем отклика самих FTP-команд. Проще говоря, чем больше каталогов нужно проанализировать, тем дольше протекает процесс. Попробуйте ограничить диапазон, который необходимо просмотреть.

Во-вторых, данный сценарий не предназначен для одновременного использования большим количеством пересекающихся пользователей. Скорость выхода — это один из факторов, а второй — это FTP-соединения. Для каждого параллельного использования данного сценария создается соединение с FTP-сервером. Не так уж и трудно максимально увеличить количество доступных соединений. Итак, если вы хотите предоставить RSS-канал для большого количества пользователей, вам следует спрятать этот сценарий за кэш-памятью, которая периодически его вызывает. Предоставьте манипулировать реальным объемом кэш-памяти, но не сценарию ftp_monitor.

Если вы будете придерживаться этих ограничений, вы сможете предоставлять пользователям услуги хорошего качества, а именно, предоставлять информацию, в которой они нуждаются, не разочаровывая их долгим ожиданием доставки.

Ответьте нам

Если вы находите данный сценарий полезным или если вы придумали классное изменение для него, я бы хотел услышать от вас об этом. Пришлите комментарий или поделитесь тем, чему вы научились на сайте сообщества xml.com (или по-русски в форуме xmlhack.ru).



XML.com Copyright © 1998-2007 O'Reilly Media, Inc.
Перевод: xmlhack.ru Copyright © 2000-2007 xmlhack.ru