Книга: Программирование на языке Ruby

18.2.7. Пример: шлюз между почтой и конференциями

18.2.7. Пример: шлюз между почтой и конференциями

В онлайновых сообществах общение происходит разными способами. К наиболее распространенным относятся списки рассылки и конференции (новостные группы).

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

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

Подобный шлюз подходит не к любой ситуации, но в случае списка рассылки 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.

Листинг 18.6. Перенаправление почты в конференцию

# 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}n"
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}"
  head << "X-ruby-talk: #$1n"
 end
 head << line
end
head << "#{Params::LOOP_FLAG}n"
body = ""
while line = gets
 body << line
end
msg = head + "n" + body
msg.gsub!(/r?n/, "rn")
nntp = NNTPIO.new(Params::NEWS_SERVER)
raise "Failed to connect" unless nntp.connect
nntp.post(msg)

Листинг 18.7. Перенаправление конференции в почту

##
# Простой сценарий для зеркалирования трафика
# из конференции comp.lang.ruby в список рассылки ruby-talk.
#
# Вызывается периодически (скажем, каждые 20 минут).
# Запрашивает у новостного сервера все сообщения с номером,
# большим номера последнего сообщения, полученного
# в прошлый раз. Если таковые есть, то читает сообщения,
# отправляет их в список рассылки и запоминает номер последнего.
require 'nntp'
require 'net/smtp'
require 'params'
include NNTP
##

# # Отправить сообщения в список рассылки. Сообщение должно
# быть отправлено участником списка, хотя в строке From:
# может стоять любой допустимый адрес.
#
def send_mail(head, body)
 smtp = Net::SMTP.new
 smtp.start(Params::SMTP_SERVER)
 smtp.ready(Params::MAIL_SENDER, Params::MAILING_LIST) do |a|
  a.write head
  a.write "#{Params::LOOP_FLAG}rn"
  a.write "rn"
  a.write body
 end
end
##
# Запоминаем идентификатор последнего прочитанного из конференции
# сообщения.
begin
 last_news = File.open(Params::LAST_NEWS_FILE) {|f| f.read}.to_i
rescue
 last_news = nil
end
##
# Соединяемся с новостным сервером и получаем номера сообщений
# из конференции comp.lang.ruby.
#
nntp = NNTPIО.new(Params::NEWS_SERVER)
raise "Failed to connect" unless nntp.connect
count, first, last = nntp.set_group(Params::NEWSGROUP)
##
# Если номер последнего сообщения не был запомнен раньше,
# сделаем это сейчас.
if not last_news
 last_news = last
end
##
# Перейти к последнему прочитанному ранее сообщению
# и попытаться получить следующие за ним. Это может привести
# к исключению, если сообщения с указанным номером
# не существует, но мы не обращаем на это внимания.
begin
 nntp.set_stat(last_news)
rescue
end
##
# Читаем все имеющиеся сообщения и отправляем каждое
# в список рассылки.
new_last = last_news
begin
 loop do
  nntp.set_next
  head = ""
  body = ""
  new_last, = nntp.get_head do |line|
   head << line
  end
  # He посылать сообщения, которые программа mail2news
  # уже отправляла в конференцию ранее (иначе зациклимся).
  next if head =~ %r{^X-rubymirror:}
  nntp.get_body do |line|
   body << line
  end
  send_mail(head, body)
 end
rescue
end
##
#И записать в файл новую отметку.
File.open(Params::LAST_NEWS_FILE, "w") do |f|
 f.puts new_last
end unless new_last == last_news

Оглавление книги


Генерация: 1.673. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз