Я хотел бы оставаться полностью реактивным в моем новом весеннем приложении. Поэтому я использую web-flux/reactor и ReactiveRepository с MongoDB.
Вы знаете, как реактивно интегрировать java-mail в технический стек? Любые альтернативы?
Я хотел бы оставаться полностью реактивным в моем новом весеннем приложении. Поэтому я использую web-flux/reactor и ReactiveRepository с MongoDB.
Вы знаете, как реактивно интегрировать java-mail в технический стек? Любые альтернативы?
Для отправки электронной почты и сохранения неблокировки вы можете запустить код для отправки электронной почты в другом потоке. Если вы используете Spring WebFlux, это можно легко сделать, обернув код для отправки электронной почты нижеприведенными заводскими методами Mono (издатель библиотеки Reactor).
Mono.fromCallable()
or
Mono.fromRunnable()
Короткий код
Mono.fromCallable(()-> sendEmail())
.subscribe();
Где sendEmail() — ваша функция для отправки электронной почты.
Это также рекомендуется в документах — Как мне обернуть синхронный , Блокировать вызов?
Длинный код
Ниже приведен полный пример кода, который я использую в своем приложении.
Mono.fromCallable(() -> {
try {
MimeMessageHelper helper = new MimeMessageHelper(sender.createMimeMessage());
helper.setTo(to);
helper.setSubject(subject);
helper.setText(body);
sender.send(helper.getMimeMessage());
log.info("Email send successfully, subject {} , to {}", subject, to);
return true;
} catch (Exception e) {
log.warn("Failed to send email with subject {}, due to {}",subject,e.getMessage(), e});
return false;
}
)
.subscribe(result -> log.info("Mail sent {}", result));
А когда это реактивный стек, никогда не забывайте подписываться :D
Я также искал реактивный SMTP-клиент.
И мне удалось его найти ;)
Вот он: https://github.com/HubSpot/NioSmtpClient
из README:
Высокопроизводительный SMTP-клиент на Java на основе Netty. Этот клиент хорошо протестирован и активно используется в HubSpot.
Я уже проверил это в локальной среде, и это действительно реактивно! Однако он использует completableFuture вместо Mono или Flux, поэтому его необходимо будет обернуть вручную.
В целом эта библиотека выглядит хорошо, но я думаю, что было бы лучше, если бы автор предоставил некоторые facade
, которые бы упростили использование SDK. (В любом случае это с открытым исходным кодом, поэтому мы можем его улучшить).
Вот пример, как его использовать (не говоря уже о codeStyle, это просто для примера):
private static final String MESSAGE_DATA = "From: <[email protected]\r\n" +
"To: <[email protected]>\r\n" +
"Subject: test mail\r\n\r\n" +
"Hello stackOverFlow!";
public static void main(String[] args) {
final SmtpSessionFactory smtpSessionFactory = createSmtpSessionFactory();
final SmtpSessionConfig smtpSessionConfig = SmtpSessionConfig.builder().remoteAddress(InetSocketAddress.createUnresolved("smtp.gmail.com", 587)).build();
Mono.fromFuture(smtpSessionFactory.connect(smtpSessionConfig))
.flatMap(connection -> doInSession(connection, req(EHLO, "gmail.com")))
.flatMap(connection -> Mono.fromFuture(connection.getSession().startTls()))
.flatMap(connection -> Mono.fromFuture(connection.getSession().authLogin("[email protected]", "SomeStrongPasswordLike123456")))
.flatMap(connection -> doInSession(connection, req(MAIL, "FROM:<" + "[email protected]" + ">")))
.flatMap(connection -> doInSession(connection, req(RCPT, "TO:<" + "[email protected]" + ">")))
.flatMap(connection -> doInSession(connection, req(DATA)))
.map(connection -> connection.getSession()
.send(MessageContent.of(Unpooled.wrappedBuffer(MESSAGE_DATA.getBytes(StandardCharsets.UTF_8)))))
.flatMap(Mono::fromFuture)
.flatMap(connection -> doInSession(connection, req(QUIT)))
.flatMap(connection -> Mono.fromFuture(connection.getSession().close()))
.block();
}
private static SmtpSessionFactory createSmtpSessionFactory() {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("niosmtpclient-%d").build();
final SmtpSessionFactoryConfig config = SmtpSessionFactoryConfig.builder()
.eventLoopGroup(new NioEventLoopGroup(4, threadFactory))
.executor(Executors.newCachedThreadPool(threadFactory))
.build();
return new SmtpSessionFactory(config);
}
private static Mono<SmtpClientResponse> doInSession(SmtpClientResponse connection, SmtpRequest request) {
return Mono.fromFuture(connection.getSession().send(request));
}
private static SmtpRequest req(SmtpCommand command, CharSequence... arguments) {
return new DefaultSmtpRequest(command, arguments);
}
Как это работает (в целом):
createSmtpSessionFactory
)MESSAGE_DATA
переменную)Единственный полезный неблокирующий SMTP-клиент, который я нашел и до сих пор использую, — это https://vertx.io/docs/vertx-mail-client/java/
Я даже интегрировал его с spring-webflux и mongodb-driver-reactivestreams, чтобы они использовали одну и ту же Netty EventLoopGroup.
Mono.create<MailResult> { sink ->
mailClient.sendMail(email) { asyncResult ->
if (asyncResult.succeeded()) {
sink.success(asyncResult.result()
} else {
sink.error(asyncResult.cause()
}
}
}
Как насчет использования Microsoft Graph API и серверов Microsoft exahnge для отправки электронных писем https://docs.microsoft.com/en-us/graph/use-the-api. Это не ответ на исходный вопрос, но мне интересно, что там можно применить ту же концепцию, или если у кого-то есть что-то подобное, использующее этот API.
Я нашел решение. Он использует spring-boot-starter-data-mongodb-reactive
и API внешних сервисов, таких как Mailgun или SendGrid. Ключевым моментом является использование реактивного WebClient :
Создайте экземпляр WebClient (например, с помощью Sendgrid API):
String endpoint = “https://api.sendgrid.com/v3/“;
String sendUri = endpoint + “mail/send”;
WebClient client = WebClient.builder().filter(…).clientConnector(new ReactorClientHttpConnector(HttpClient.create())).baseUrl(endpoint).build()
Реализовать объекты ответа:
@Data
class Response implements Serializable {
private boolean status;
private String id;
private String message;
}
@Data
class NotificationStatusResponse implements Serializable {
private LocalDateTime timestamp;
private int status;
private String message;
private String traceId;
private String responseId;
private String providerResponseId;
private String providerResponseMessage;
}
Отправьте свое сообщение:
public Mono<NotificationStatusResponse> send(NotificationMessage<EmailId> email) throws NotificationSendFailedException {
Mono<NotificationStatusResponse> response = null;
try {
MultiValueMap<String, Object> formMap = new LinkedMultiValueMap<>(); // email parameters here: from, to, subject, html etc.
response = client.post().uri(sendUri)
.header("Authorization", "Basic " + “your credentials here”)
.contentType(MediaType.MULTIPART_FORM_DATA).syncBody(formMap).retrieve()
.bodyToMono(Response.class).map(this::getNotificationStatusResponse)
.doOnSuccess(message -> log.debug("sent email successfully"))
.doOnError((error -> log.error("email failed ", error)));
} catch (WebClientException webClientException) {
throw new NotificationSendFailedException("webClientException received", webClientException);
}
return response;
NotificationStatusResponse getNotificationStatusResponse(Response response) {
NotificationStatusResponse notificationStatusResponse = new NotificationStatusResponse();
notificationStatusResponse.setStatus(200);
notificationStatusResponse.setTimestamp(LocalDateTime.now());
notificationStatusResponse.setProviderResponseId(response.getId());
notificationStatusResponse.setProviderResponseMessage(response.getMessage());
return notificationStatusResponse;
}
}