Как отправить электронную почту в ответ на весенний веб-флюс

Я хотел бы оставаться полностью реактивным в моем новом весеннем приложении. Поэтому я использую web-flux/reactor и ReactiveRepository с MongoDB.

Вы знаете, как реактивно интегрировать java-mail в технический стек? Любые альтернативы?


person kalamar    schedule 28.06.2018    source источник


Ответы (5)


Для отправки электронной почты и сохранения неблокировки вы можете запустить код для отправки электронной почты в другом потоке. Если вы используете 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

person Goro    schedule 08.06.2020
comment
Разве мы не должны использовать подписку (Schedulers.boundedElastic()) - person Muhammad Ilyas; 18.08.2020

Я также искал реактивный 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);
}

Как это работает (в целом):

  1. мы определяем фабрику сеансов (см. метод createSmtpSessionFactory)
  2. мы открываем диалог(соединение) с сервером
  3. мы запускаем TLS
  4. мы делаем аутентификацию с нашими учетными данными
  5. мы говорим адрес электронной почты отправителя на сервер
  6. мы говорим адрес электронной почты получателя на сервер
  7. начинаем фазу отправки данных
  8. мы отправляем данные (данные должны соответствовать некоторому шаблону. От: ... Кому: ... Тема: .... см. MESSAGE_DATA переменную)
  9. мы уведомляем сервер, что мы заканчиваем диалог.
  10. мы закрываем сессию
person Vladyslav Diachenko    schedule 10.05.2020

Единственный полезный неблокирующий 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()
    }
  }
}
person Dmytro Voloshyn    schedule 15.07.2020
comment
Не могли бы вы поделиться примером, как вам это удалось? - person Ricard Kollcaku; 27.05.2021
comment
@RicardKollcaku, я добавил пример кода. Это то, о чем вы просили? - person Dmytro Voloshyn; 28.05.2021
comment
но работает ли это в цикле событий netty, я пытался использовать blockhund для обнаружения блокировки, и он дает исключение и является вершинным потоком, а не реакторным. и blockhund обнаруживает блокировку - person Ricard Kollcaku; 29.05.2021

Как насчет использования Microsoft Graph API и серверов Microsoft exahnge для отправки электронных писем https://docs.microsoft.com/en-us/graph/use-the-api. Это не ответ на исходный вопрос, но мне интересно, что там можно применить ту же концепцию, или если у кого-то есть что-то подобное, использующее этот API.

person athenatechie    schedule 14.02.2021

Я нашел решение. Он использует spring-boot-starter-data-mongodb-reactive и API внешних сервисов, таких как Mailgun или SendGrid. Ключевым моментом является использование реактивного WebClient :

  1. Создайте экземпляр 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()
    
  2. Реализовать объекты ответа:

    @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;
    }
    
  3. Отправьте свое сообщение:

    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;
        }
    }
    
person Vladimir Pirogov    schedule 21.08.2019