Отправка HTML-формы в remote.cgi - написанной на Ruby?
Вопрос
Я работаю над веб-сайтом, размещенным в службе Microsoft Office live.Здесь есть контактная форма, позволяющая посетителям связаться с владельцем.Я хочу написать скрипт Ruby, который находится на отдельном сервере и в который будет отправляться форма.Он проанализирует данные формы и отправит подробную информацию по электронной почте на заранее установленный адрес.Затем скрипт должен перенаправить браузер на страницу подтверждения.
У меня есть выносливая машина Ubuntu, работающая под управлением nginx и postfix.Ruby установлен, и мы посмотрим, как использовать функциональность Thin и it's Rack для обработки скрипта.Теперь дело дошло до написания сценария, и я нарисовал пробел.
Это было давно, и, если я правильно помню, процесс выглядит примерно так;
- прочитать HTTP-заголовок
- анализировать параметры
- отправить электронное письмо
- отправить заголовок перенаправления
Вообще говоря, на этот вопрос был дан ответ.Выяснить, как использовать ответ, оказалось сложнее, чем ожидалось, и я подумал, что стоит поделиться.
Первые шаги:
Я довольно внезапно узнал, что nginx напрямую не поддерживает cgi-скрипты.Вы должны использовать какой-то другой процесс, чтобы запустить скрипт и перенаправить запросы nginx на прокси.Если бы я делал это на php (что, на мой взгляд, было бы более естественным выбором), я мог бы использовать что-то вроде php-fcgi и ожидать, что жизнь была бы довольно простой.
Ruby и fcgi чувствовали себя довольно устрашающе.Но если мы отказываемся от идеала загрузки этих вещей во время выполнения, то Rack, вероятно, является самым простым решением, и Thin включает в себя все, что нам нужно.Изучение того, как создавать с их помощью простые небольшие приложения, было чрезвычайно полезным для такого относительного новичка в Rails, как я.Основы приложения Rails могут долгое время казаться скрытыми, и Rack помогла мне немного приоткрыть завесу.
Тем не менее, последовать совету Иегуды и поискать синатру стало еще одним сюрпризом.Теперь у меня есть базовое приложение sinatra, работающее в тонком экземпляре.Он взаимодействует с nginx через сокет unix, как я понимаю, стандартным способом.Sinatra предоставляет действительно элегантный способ обработки различных запросов и маршрутов в приложении.Все, что вам нужно, это get '/' {}
чтобы начать обработку запросов к виртуальному хосту.Чтобы добавить больше (чистым способом), мы просто включаем routes/script.rb в основной файл.
# cgi-bin.rb
# main file loaded as a sinatra app
require 'sinatra'
# load cgi routes
require 'routes/default'
require 'routes/contact'
# 404 behaviour
not_found do
"Sorry, this CGI host does not recognize that request."
end
Эти файлы маршрутов будут вызывать функциональность, хранящуюся в отдельной библиотеке классов:
# routes/contact.rb
# contact controller
require 'lib/contact/contactTarget'
require 'lib/contact/contactPost'
post '/contact/:target/?' do |target|
# the target for the message is taken from the URL
msg = ContactPost.new(request, target)
redirect msg.action, 302
end
Явный ужас от осознания такой простой вещи останется со мной на некоторое время.Я ожидал спокойно сообщить nginx, что файлы .rb должны быть выполнены, и просто продолжить с этим.Теперь, когда это маленькое приложение sinatra запущено, я смогу сразу погрузиться в работу, если захочу добавить дополнительную функциональность в будущем.
Реализация:
Класс ContactPost обрабатывает аспект обмена сообщениями.Все, что ему нужно знать, - это параметры в запросе и цель для электронного письма.ContactPost::действие запускает все и возвращает адрес, на который контроллер должен перенаправить.
Существует отдельный класс ContactTarget, который выполняет некоторую аутентификацию, чтобы убедиться, что указанный целевой объект принимает сообщения с URL, указанного в request.referrer .Это обрабатывается в ContactTarget::accept?как мы можем догадаться из метода ContactPost::action;
# lib/contact/contactPost.rb
class ContactPost
# ...
def action
return failed unless @target.accept? @request.referer
if send?
successful
else
failed
end
end
# ...
end
ContactPost::successful и ContactPost::failed возвращают адрес перенаправления, комбинируя пути, предоставленные в HTML-форме, с URI запроса.refererer.Таким образом, все поведение задается в HTML-форме.Будущие веб-сайты, использующие этот скрипт, просто должны быть перечислены в личном файле пользователя ~/cgi/contact.conf, и они исчезнут.Это связано с тем, что ContactTarget ищет подробности в /home/:target/cgi/contact.conf.Может быть, когда-нибудь это будет неуместно, но сейчас это просто отлично подходит для моих целей.
Метод отправки достаточно прост, он создает экземпляр простого класса электронной почты и отправляет его.Класс Email в значительной степени основан на стандартном примере использования, приведенном в документации Ruby net / smtp;
# lib/email/email.rb
require 'net/smtp'
class Email
def initialize(from_alias, to, reply, subject, body)
@from_alias = from_alias
@from = "cgi_user@host.domain.com"
@to = to
@reply = reply
@subject = subject
@body = body
end
def send
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message to_s, @from, @to
end
end
def to_s
<<END_OF_MESSAGE
From: #{@from_alias}
To: #{@to}
Reply-To: #{@from_alias}
Subject: #{@subject}
Date: #{DateTime::now().to_s}
#{@body}
END_OF_MESSAGE
end
end
Все, что мне нужно сделать, это настроить приложение, сообщить nginx, к какому сокету обращаться, и мы ушли.
Спасибо всем вам за ваши полезные указания в правильном направлении!Да здравствует Синатра!
Решение
Вероятно, лучшим способом сделать это было бы использовать существующую библиотеку Ruby, такую как Sinatra:
require "rubygems"
require "sinatra"
get "/myurl" do
# params hash available here
# send email
end
Вероятно, вы захотите использовать MailFactory для отправки фактического электронного письма, но вам определенно не нужно возиться с заголовками или параметрами синтаксического анализа.
Другие советы
Все это есть в сетевом модуле, вот пример:
@net = Net::HTTP.new 'http://www.foo.com', 80
@params = {:name => 'doris', :email => 'doris@foo.com'}
# Create HTTP request
req = Net::HTTP::Post.new( 'script.cgi', {} )
req.set_form_data @params
# Send request
response = @net.start do |http|
http.read_timeout = 5600
http.request req
end
Класс CGI Ruby может быть использован для написания CGI-скриптов.Пожалуйста, проверьте: http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/index.html
Кстати, нет необходимости читать HTTP-заголовок.Синтаксический анализ параметров будет простым с помощью CGI-класса.Затем отправьте электронное письмо и сделайте перенаправление.