RabbitMQ: отправка почты
Зачем это нужно? Предположим нам надо отправить несколько тысяч писем с сайта. Когда нажмете "отправить", то сайт зависнет на некоторое время пока не будут разосланы все письма. Идея состоит в том, чтобы вместо отправки писем закодировать данные в JSON и передать на очередь в RabbitMQ. Далее будет сделан отдельный скрипт, который будет получать данные для отправки почты от RabbitMQ (такие скрипты можно запускать через supervisord или systemd). Получается сайт продолжит работать в обычном режиме, а отправка почты будет осуществляться в фоновом режиме.
Сам по себе RabbitMQ ничем не занимается, кроме передачи сообщений между различными приложениями. Таким образом можно связать несколько абсолютно не совместимых программ.
Чтобы создать пользователя выполняем следующие команды:
rabbitmqctl add_user <username> <password>
rabbitmqctl set_user_tags <username> administrator
rabbitmqctl set_permissions -p / <username> ".*" ".*" ".*"
Пользователь по-умолчанию "guest". Чтобы получить доступ к веб-интерфейсу на порту 5672 надо включить плагин управления:
rabbitmq-plugins enable rabbitmq_management
systemctl restart rabbitmq-server
Для начала накидаем форму для отправки почты index.php:
<?php
if(!empty($_GET['sent'])){
?>
<div>
Message sent!
</div>
<?php
}
?>
<form action="rabbit.php" method="post">
<div>
<label for="from">From</label>
<input type="text" name="from" id="from">
</div>
<div>
<label for="from_email">From email</label>
<input type="text" name="from_email" id="from_email">
</div>
<div>
<label for="to_email">To email</label>
<input type="text" name="to_email" id="to_email">
</div>
<div>
<label for="subject">Subject</label>
<input type="text" name="subject" id="subject">
</div>
<div>
<label for="message">Message</label>
<textarea name="message" id="message" cols="30" rows="10"></textarea>
</div>
<div>
<button type="submit">Send</button>
</div>
</form>
Далее установим пакет php-amqplib для связи с RabbitMQ и swiftmailer для отправки почты на SMTP сервер:
composer require php-amqplib/php-amqplib
composer require swiftmailer/swiftmailer
Теперь напишем скрипт rabbit.php, на которую форма будет делать редирект при нажатии на кнопку:
<?php
require_once __DIR__.'/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
/* create connection to rabbit */
$conn = new AMQPStreamConnection(
'localhost', //server address
5672, //server port
'username', //username
'password', //password
'/' //vhost
);
/* create new channel */
$chan = $conn->channel();
/* create new queue */
$chan->queue_declare(
'outgoing-email', //queue name
false, //check existing exchange
false, //check queue on server crash
false, //check if queue used only by one connection
false //delete queue if last subscriber unsibscribed
);
/* get data from html form */
$data = json_encode($_POST);
/* prepare amqp message */
$msg = new AMQPMessage($data,array('delivery_mode' => 2)); //persistent message mode
/* send message to rabbit */
$chan->basic_publish(
$msg, //message
'', //exchange
'outgoing-email' //routing key
);
/* close connection and channel */
$chan->close();
$conn->close();
header('Location: index.php?sent=true');
?>
Напишем последний скрипт recv.php, который будет получать данные от RabbitMQ и отправлять письма на SMTP сервер:
<?php
require_once __DIR__.'/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use lib\swift_required;
/* create connection to rabbit */
$conn = new AMQPStreamConnection(
'localhost', //server
5672, //port
'username', //user
'password', //pass
'/' //vhost
);
/* create channel */
$chan = $conn->channel();
/* create queue */
$chan->queue_declare(
'outgoing-email',
false,
false,
false,
false
);
echo '[x] waiting for message',"\n";
/* send message to smtp relay */
$callback = function($msg){
echo '[x] message received',"\n";
$data = json_decode($msg->body,true);
$from = $data['from'];
$from_email = $data['from_email'];
$to_email = $data['to_email'];
$subject = $data['subject'];
$message = $data['message'];
$transport = (new Swift_SmtpTransport('x.x.x.x',25,false))
->setUsername(false)
->setPassword(false);
$mailer = new Swift_Mailer($transport);
$message = (new Swift_Message($transport))
->setSubject($subject)
->setFrom(array($from_email => $from))
->setTo(array($to_email))
->setBody($message);
$mailer->send($message);
echo '[x] message sent',"\n";
//send acknowledge to rabbit (tell rabbit that delivery arrived successfully)
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};
//check if message was processed (something like loadbalance between exchanges)
$chan->basic_qos(null,1,null);
//get message from queue and do something
$chan->basic_consume('outgoing-email','',false,false,false,false,$callback);
while(count($chan->callbacks)){
$chan->wait();
}
/* close channel and connection */
$chan->close();
$conn->close();
?>
Уточнение по swiftmailer. Где х.х.х.х это адрес SMTP сервера (либо IP-адрес либо доменный). Порт в моем случае 25 и поскольку без шифрования, то после порта стоит false. В setUsername и в setPassword, тоже стоят false поскольку мой SMTP релей не требует ввода логина и пароля.
Запускаем последний скрипт "php recv.php" и смотрим в терминал. В браузере открываем форму, заполняем и отправляем письмо. В терминале должны будете увидеть сообщение, а на почту должно прийти письмо.