Таким образом, мы имеем ситуацию, когда в сравнительно небольшом закрытом списке рассылки рассматриваются те же темы, что в немодерируемой конференции, открытой всему миру. В конце концов кому-то пришла в голову мысль организовать зеркало — шлюз между обеими системами.
Подобный шлюз подходит не к любой ситуации, но в случае списка рассылки Ruby он вполне годится. Сообщения из конференции нужно скопировать в список, а сообщения, отправляемые в список рассылки, направить также и конференцию.
Эта задача была решена Дэйвом Томасом (Dave Thomas) — конечно, на Ruby, — и с его любезного разрешения мы приводим код в листингах 18.6 и 18.7.
Но сначала небольшое вступление. Мы уже немного познакомились с тем, как отправлять и получать электронную почту, но как быть с конференциями Usenet? Доступ к конференциям обеспечивает протокол NNTP (Network News Transfer Protocol — сетевой протокол передачи новостей). Кстати, создал его Ларри Уолл (Larry Wall), который позже подарил нам язык Perl.
В Ruby нет «стандартной» библиотеки для работы с NNTP. Однако один японский программист (известный нам только по псевдониму greentea) написал прекрасную библиотеку для этой цели.
В библиотеке nntp.rb
определен модуль NNTP
, содержащий класс NNTPIO
. В этом классе имеются, в частности, методы экземпляра connect
, get_head
, get_body
и post
. Чтобы получить сообщения, необходимо установить соединение с сервером и в цикле вызывать методы get_head
и get_body
(мы, правда, немного упрощаем). Чтобы отправить сообщение, нужно сконструировать его заголовки, соединиться с сервером и вызвать метод post
.
В приведенных ниже программах используется библиотека smtp
, с которой мы уже познакомились. В оригинальной версии кода производится также протоколирование хода процесса и ошибок, но для простоты мы эти фрагменты опустили.
Файл params.rb
нужен обеим программам. В нем описаны параметры, управляющие всем процессом зеркалирования: имена серверов, имена пользователей и т.д. Ниже приведен пример, который вы можете изменить самостоятельно. (Все доменные имена, содержащие слово «fake», очевидно, фиктивные.)
# Различные параметры, необходимые шлюзу между почтой и конференциями.
module Params
NEWS_SERVER = 'usenet.fake1.org' # Имя новостного сервера.
NEWSGROUP = 'comp.lang.ruby' # Зеркалируемая конференция.
LOOP_FLAG = 'X-rubymirror: yes' # Чтобы избежать циклов.
LAST_NEWS_FILE = '/tmp/m2n/last_news' # Номер последнего прочитанного
# сообщения.
SMTP_SERVER = 'localhost' # Имя хоста для исходящей почты.
MAIL_SENDER = '[email protected]' # От чьего имени посылать почту.
# (Для списков, на которые подписываются, это должно быть имя
# зарегистрированного участника списка.)
mailing_list = '[email protected]' # Адрес списка рассылки.
end
Модуль Params
содержит лишь константы, нужные обеим программам. Большая их часть не нуждается в объяснениях, упомянем лишь парочку. Во-первых, константа LAST_NEWS_FILE
содержит путь к файлу, в котором хранится идентификатор последнего прочитанного из конференции сообщения; эта «информация о состоянии» позволяет избежать дублирования или пропуска сообщений.
Константа LOOP_FLAG
определяет строку, которой помечаются сообщения, уже прошедшие через шлюз. Тем самым мы препятствуем возникновению бесконечной
рекурсии, а заодно негодованию возмущенных обитателей сети, получивших тысячи копий одного и того же сообщения.
Возникает вопрос: «А как вообще почта поступает в программу mail2news
?» Ведь она, похоже, читает из стандартного ввода. Автор рекомендует следующую настройку: сначала в файле .forward
программы sendmail
вся входящая почта перенаправляется на программу procmail
. Файл .procmail
конфигурируется так, чтобы извлекать сообщения, приходящие из списка рассылки, и по конвейеру направлять их программе mail2news
. Уточнить детали можно в документации, сопровождающей приложение RubyMirror (в архиве RAA). Если вы работаете не в UNIX, то придется изобрести собственную схему конфигурирования.
Ну а все остальное расскажет сам код, приведенный в листингах 18.6 и 18.7.
# mail2news: Принимает почтовое сообщение и отправляет
# его в конференцию.
require 'nntp'
include NNTP
require 'params'
# Прочитать сообщение, выделив из него заголовок и тело.
# Пропускаются только определенные заголовки.
HEADERS = %w{From Subject References Message-ID
Content-Type Content-Transfer-Encoding Date}
allowed_headers = Regexp.new(%{^(#{HEADERS.join('|')}):})
# Прочитать заголовок. Допускаются только некоторые заголовки.
# Добавить строки Newsgroups и X-rubymirror.
head = 'Newsgroups: #{Params::NEWSGROUP}
'
subject = 'unknown'
while line = gets
exit if line /^#{Params::LOOP_FLAG}/о # Такого не должно быть!
break if line =~ /^s*$/
next if line =~ /^s/
next unless line =~ allowed_headers
# Вырезать префикс [ruby-talk:nnnn] из темы, прежде чем
# отправлять в конференцию.
if line =~ /^Subject:s*(.*)/
subject = $1
# Следующий код вырезает специальный номер ruby-talk
# из начала сообщения в списке рассылки, перед тем
# как отправлять его новостному серверу.
line.sub!(/[ruby-talk:(d+)]s*/, '')
subject = '[#$1] #{line}'