Хэл Фултон - Программирование на языке Ruby
# Предположим, что mailtext содержит текст сообщения.
filename = "new.gif"
bin = File.read(filename)
encoded = [bin].pack("u")
mailtext << "begin 644 #{filename}"
mailtext << encoded
mailtext << "end"
# ...
На принимающей стороне мы должны извлечь закодированную информацию и декодировать ее методом unpack:
# ...
# Предположим, что 'attached' содержит закодированные данные
# (включая строки begin и end).
lines = attached.split("n")
filename = /begin ddd (.*)/.scan(lines[0]).first.first
encoded = lines[1..-2].join("n")
decoded = encoded.unpack("u") # Все готово к записи в файл.
Современные почтовые клиенты работают с почтой в формате MIME; даже текстовая часть сообщения обернута в конверт (хотя клиент удаляет все заголовки, прежде чем показать сообщение пользователю).
Подробное рассмотрение формата MIME заняло бы слишком много места, да и не относится к рассматриваемой теме. Но в следующем простом примере показано, как можно закодировать и отправить сообщение, содержащее текстовую часть и двоичное вложение. Двоичные куски обычно представлены в кодировке base64:
require 'net/smtp'
def text_plus_attachment(subject, body, filename)
marker = "MIME_boundary"
middle = "--#{marker}n"
ending = "--#{middle}--n"
content = "Content-Type: Multipart/Related; " +
"boundary=#{marker}; " +
"typw=text/plain"
head1 = <<-EOF
MIME-Version: 1.0
#{content}
Subject: #{subject}
EOF
binary = File.read(filename)
encoded = [binary].pack("m") # base64
head2 = <<EOF
Content-Description: "#{filename}"
Content-Type: image/gif; name="#{filename}"
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename="#{filename}"
EOF
# Возвращаем...
head1 + middle + body + middle + head2 + encoded + ending
end
domain = "someserver.com"
smtp = "smtp.#{domain}"
user, pass = "elgar","enigma"
body = <<EOF
Это мое сообщение. Особо
говорить не о чем. Я вложил
небольшой GIF-файл.
-- Боб
EOF
mailtext = text_plus_attachment("Привет...",body,"new.gif")
Net::SMTP.start(smtp, 25, domain, user, pass, :plain) do |mailer|
mailer.sendmail(mailtext, ' [email protected]',
[' [email protected]'])
end
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