Spring Cloud: AWS SNS

S

Introducción

Enviar notificaciones a los usuarios es una tarea bastante común, ya sea a través de correo electrónico, mensajes SMS o incluso a través de solicitudes POST HTTP / HTTPS.

los Servicio de notificación simple (SNS) es un sistema de mensajería para editores / suscriptores proporcionado por Servicios web de Amazon (AWS). Es una opción popular para muchos desarrolladores y muy confiable.

En este artículo, crearemos una aplicación Spring Cloud con soporte de mensajería (SMS y correo electrónico) con la ayuda de AWS SNS.

¿Por qué elegir AWS SNS?

El servicio de notificación simple de AWS permite a un editor (generalmente un microservicio) enviar (publicar) notificaciones sobre ciertos temas a los receptores (suscriptores) a través de varios medios: SMS, correo electrónico, HTTP, AWS Lambda y AWS SQS.

Estos receptores se suscriben deliberadamente a un tema del que desean recibir notificaciones:

Estas son algunas de las razones por las que AWS SNS es extremadamente popular:

  • Permite la publicación en puntos finales HTTP y otros servicios de AWS
  • Admite más de 205 países para envío de SMS y correo electrónico. Este nivel de disponibilidad es especialmente importante si sus usuarios van a ser de origen global.
  • Garantiza la entrega de mensajes siempre que la dirección de correo electrónico / SMS sea válida.
  • AWS ofrece una amplia gama de funciones y SDK para Java para SNS con excelente documentación.
  • A través de los módulos fácilmente integrados de Spring, la molestia de integrar el SDK de AWS para Java se hace extremadamente simple.
  • Si ya está utilizando otros servicios de AWS para el almacenamiento o la implementación, entonces es una obviedad permanecer en el mismo ecosistema y utilizar SNS.

Casos de uso de Spring Boot para AWS SNS

Hay muchas áreas en las que puede usar notificaciones por SMS, correo electrónico o HTTP / S en una aplicación Spring Web:

  • Notifique a todos los microservicios de un evento en toda la aplicación.
  • Notifique a los administradores / desarrolladores sobre errores críticos o servicios caídos.
  • Verificación del número de teléfono a través de OTP (contraseña de un solo uso) durante el registro del usuario o el restablecimiento de la contraseña.
  • Notifique a los usuarios sobre un evento que está directamente asociado con el usuario (por ejemplo, se acepta una aplicación).
  • Aumente la participación del usuario, ya que las notificaciones por correo electrónico y SMS pueden llevar al usuario de regreso a su aplicación.

Cuenta de AWS

Al igual que con cualquier servicio de AWS, necesitamos obtener la clave de acceso ID y la clave secreta de nuestra cuenta de AWS. Inicie sesión en su Consola de AWS y visite la página “Mis credenciales de seguridad” que aparece en el menú desplegable de su cuenta:

Expanda la pestaña “Claves de acceso (ID de clave de acceso y clave de acceso secreta)” y haga clic en “Crear nueva clave de acceso”:

Descargue su archivo de credenciales y guárdelo en un lugar seguro. Nadie debería tener acceso a este archivo, ya que entonces también tendrá autorización completa para usar su cuenta de AWS:

Necesitas decidir sobre un Región de AWS para utilizar como ubicación de procesamiento de sus solicitudes de servicio SNS. Tenga en cuenta que el precio de los SMS puede diferir según la región elegida y que no todas las regiones admiten mensajes SMS.

Asegúrese de elegir una ubicación compatible con SMS de aquí.

En aras de la brevedad, hemos utilizado la cuenta raíz para generar el AWS Key Id y Secret Key – pero esta práctica se desaconseja y AWS recomienda usar Roles de usuario de IAM en lugar.

Proyecto Spring Boot

Como siempre, para un proyecto Spring Boot de arranque rápido, usaremos Spring Initializr:

Alternativamente, podemos usar el Spring Boot CLI:

$ spring init --dependencies=web sns-demo

Dependencias

Con la herramienta de compilación que elija, agregue las dependencias necesarias:

Gradle

dependencies {
    implementation platform('software.amazon.awssdk:bom:2.5.29') // BOM for AWS SDK For Java
    implementation 'software.amazon.awssdk:sns' // We only need to get SNS SDK in our case
    implementation 'software.amazon.awssdk:ses' // Needed for sending emails with attachment
    implementation 'com.sun.mail:javax.mail' // Needed for sending emails with attachment
    compile group: 'org.springframework.cloud', name: 'spring-cloud-aws-messaging', version: '2.2.1.RELEASE'
    compile group: 'org.springframework.cloud', name: 'spring-cloud-aws-autoconfigure', version: '2.2.1.RELEASE'
}

Maven

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-aws-messaging</artifactId>
    <version>{version}</version>
</dependency>

Envío de correos electrónicos mediante SNS

Crear un tema de SNS

Un tema de SNS es un punto de acceso que agrupa diferentes puntos finales entre un editor (nuestro proyecto Spring Boot) y suscriptores. Un editor publica un mensaje sobre un tema y ese mensaje se entregará a todos los suscriptores de ese tema.

Primero, definamos un método auxiliar que nos permitirá obtener un cliente SNS:

private SnsClient getSnsClient() throws URISyntaxException {
    return SnsClient.builder()
            .credentialsProvider(getAwsCredentials(
                    "Access Key ID",
                    "Secret Key"))
            .region(Region.US_EAST_1) // Set your selected region
            .build();
}

Este método utiliza otro método auxiliar, getAWSCredentials():

private AwsCredentialsProvider getAwsCredentials(String accessKeyID, String secretAccessKey {
    AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(accessKeyID, secretAccessKey);
    AwsCredentialsProvider awsCredentialsProvider = () -> awsBasicCredentials;
    return awsCredentialsProvider;
}

Realmente, puede configurar el cliente cuando lo usa, pero los métodos de ayuda son un poco más elegantes. Con eso fuera del camino, creemos un punto final para la creación de temas:

@RequestMapping("/createTopic")
private String createTopic(@RequestParam("topic_name") String topicName) throws URISyntaxException {

    // Topic name cannot contain spaces
    final CreateTopicRequest topicCreateRequest = CreateTopicRequest.builder().name(topicName).build();

    // Helper method makes the code more readable
    SnsClient snsClient = getSnsClient();

    final CreateTopicResponse topicCreateResponse = snsClient.createTopic(topicCreateRequest);

    if (topicCreateResponse.sdkHttpResponse().isSuccessful()) {
        System.out.println("Topic creation successful");
        System.out.println("Topic ARN: " + topicCreateResponse.topicArn());
        System.out.println("Topics: " + snsClient.listTopics());
    } else {
        throw new ResponseStatusException(
            HttpStatus.INTERNAL_SERVER_ERROR, topicCreateResponse.sdkHttpResponse().statusText().get()
        );
    }

    snsClient.close();

    return "Topic ARN: " + topicCreateResponse.topicArn();
}

Nota: Si su sistema está detrás de un proxy, entonces necesita configurar su SnsClient con un cliente HTTP personalizado configurado para trabajar con su proxy:

SnsClient snsClient = SnsClient.builder()
        .credentialsProvider(getAwsCredentials(
                "Access Key ID",
                "Secret Key"))
        .httpClient(getProxyHTTPClient("http://host:port"))
        .region(Region.US_EAST_1) // Set your selected region
        .build();

private SdkHttpClient getProxyHTTPClient(String proxy) throws URISyntaxException {
    URI proxyURI = new URI(proxy);
    // This HTTP Client supports custom proxy
    final SdkHttpClient sdkHttpClient = ApacheHttpClient.builder()
            .proxyConfiguration(ProxyConfiguration.builder()
                    .endpoint(proxyURI)
                    .build())
            .build();

    return sdkHttpClient;
}

O puede usar el proxy del sistema:

private SdkHttpClient getProxyHTTPClient() throws URISyntaxException {
    // This HTTP Client supports system proxy
    final SdkHttpClient sdkHttpClient = ApacheHttpClient.builder()
            .proxyConfiguration(ProxyConfiguration.builder()
                    .useSystemPropertyValues(true)
                    .build())
            .build();

    return sdkHttpClient;
}

Finalmente, hagamos un curl solicitud para probar si nuestra creación de temas funciona:

$ curl http://localhost:8080/createTopic?topic_name=Stack-Abuse-Demo
Topic ARN: arn:aws:sns:us-east-1:123456789:Stack-Abuse-Demo

También puede confirmar si el tema se creó o no desde su consola de AWS:

Guarde el ARN del tema (nombre de recurso de Amazon) en algún lugar (por ejemplo, en una base de datos junto con los registros de usuario), ya que lo necesitaremos más adelante.

Suscribirse a un tema

Con la configuración del tema fuera del camino, creemos un punto final para la suscripción. Como estamos haciendo correo electrónico, configuraremos el protocol a para "email". Tenga en cuenta que, en términos de AWS, un “suscriptor” se denomina “punto final”, por lo que utilizaremos nuestra dirección de correo electrónico para endpoint propiedad:

@RequestMapping("/addSubscribers")
private String addSubscriberToTopic(@RequestParam("arn") String arn) throws URISyntaxException {

    SnsClient snsClient = getSnsClient();

    final SubscribeRequest subscribeRequest = SubscribeRequest.builder()
            .topicArn(arn)
            .protocol("email")
            .endpoint("[email protected]")
            .build();

    SubscribeResponse subscribeResponse = snsClient.subscribe(subscribeRequest);

    if (subscribeResponse.sdkHttpResponse().isSuccessful()) {
        System.out.println("Subscriber creation successful");
    } else {
        throw new ResponseStatusException(
            HttpStatus.INTERNAL_SERVER_ERROR, subscribeResponse.sdkHttpResponse().statusText().get()
        );
    }
    snsClient.close();

    return "Subscription ARN request is pending. To confirm the subscription, check your email.";
}

Enviemos otra solicitud de curl:

$ curl http://localhost:8080/addSubscribers?arn=arn:aws:sns:us-east-1:123456789:Stack-Abuse-Demo
Subscription ARN request is pending. To confirm the subscription, check your email.

Nota: El suscriptor debe confirmar la suscripción visitando su dirección de correo electrónico y haciendo clic en el correo electrónico de confirmación enviado por AWS:

Mandando correos electrónicos

Ahora puede publicar correos electrónicos sobre su tema, y ​​todos los destinatarios que hayan confirmado su suscripción deberían recibir el mensaje:

@RequestMapping("/sendEmail")
private String sendEmail(@RequestParam("arn") String arn) throws URISyntaxException {

    SnsClient snsClient = getSnsClient();

    final SubscribeRequest subscribeRequest = SubscribeRequest.builder()
                                              .topicArn(arn)
                                              .protocol("email")
                                              .endpoint("[email protected]")
                                              .build();

    final String msg = "This Stack Abuse Demo email works!";

    final PublishRequest publishRequest = PublishRequest.builder()
                                          .topicArn(arn)
                                          .subject("Stack Abuse Demo email")
                                          .message(msg)
                                          .build();

    PublishResponse publishResponse = snsClient.publish(publishRequest);

    if (publishResponse.sdkHttpResponse().isSuccessful()) {
        System.out.println("Message publishing successful");
    } else {
        throw new ResponseStatusException(
            HttpStatus.INTERNAL_SERVER_ERROR, publishResponse.sdkHttpResponse().statusText().get());
    }

    snsClient.close();
    return "Email sent to subscribers. Message-ID: " + publishResponse.messageId();
}

Enviemos otra solicitud de curl:

$ curl http://localhost:8080/sendEmail?arn=arn:aws:sns:us-east-1:650924441247:Stack-Abuse-Demo
Email sent to subscribers. Message-ID: abdcted-8bf8-asd54-841b-5e0be960984c

Y revisando nuestro correo electrónico, nos reciben con:

Manejo de archivos adjuntos de correo electrónico

AWS SNS solo admite tamaños de mensajes de hasta 256 Kb y no admite archivos adjuntos. La característica principal de SNS es enviar mensajes de notificación, no archivos adjuntos.

Si necesita enviar archivos adjuntos con su correo electrónico, deberá usar Servicio de correo electrónico simple de AWS (SES), junto con su SendRawEmail para lograr esta funcionalidad. Construiremos los correos electrónicos con el javax.mail biblioteca.

Si no está familiarizado con él, no dude en consultar Cómo enviar correos electrónicos en Java.

Primero, configuremos el SesClient, al igual que configuramos el SnsClient y agregue una dirección de correo electrónico:

SesClient sesClient = SesClient.builder()
        .credentialsProvider(getAwsCredentials(
                "Access Key ID",
                "Secret Key"))
        .region(Region.US_EAST_1) //Set your selected region
        .build();

VerifyEmailAddressRequest verifyEmailAddressRequest = VerifyEmailAddressRequest.builder()
        .emailAddress("[email protected]").build();
sesClient.verifyEmailAddress(verifyEmailAddressRequest);

Las direcciones de correo electrónico que agregue aquí recibirán un mensaje de confirmación y los propietarios de la dirección de correo electrónico deben confirmar la suscripción.

Y luego, creemos un objeto de correo electrónico y usemos AWS ‘ SendRawEmail para enviarlos:

@RequestMapping("/sendEmailWithAttachment")
private String sendEmailWithAttachment(@RequestParam("arn") String arn) throws URISyntaxException, MessagingException, IOException {

    String subject = "Stack Abuse AWS SES Demo";

    String attachment = "{PATH_TO_ATTACHMENT}";

    String body = "<html>"
                    + "<body>"
                        + "<h1>Hello!</h1>"
                        + "<p>Please check your email for an attachment."
                    + "</body>"
                + "</html>";

    Session session = Session.getDefaultInstance(new Properties(), null);
    MimeMessage message = new MimeMessage(session);

    // Setting subject, sender and recipient
    message.setSubject(subject, "UTF-8");
    message.setFrom(new InternetAddress("[email protected]")); // AWS Account Email
    message.setRecipients(Message.RecipientType.TO,
            InternetAddress.parse("[email protected]")); // Recipient email

    MimeMultipart msg_body = new MimeMultipart("alternative");
    MimeBodyPart wrap = new MimeBodyPart();

    MimeBodyPart htmlPart = new MimeBodyPart();
    htmlPart.setContent(body, "text/html; charset=UTF-8");
    msg_body.addBodyPart(htmlPart);
    wrap.setContent(msg_body);

    MimeMultipart msg = new MimeMultipart("mixed");

    message.setContent(msg);
    msg.addBodyPart(wrap);

    MimeBodyPart att = new MimeBodyPart();
    DataSource fds = new FileDataSource(attachment);
    att.setDataHandler(new DataHandler(fds));
    att.setFileName(fds.getName());
    msg.addBodyPart(att);

    // Build SesClient
    SesClient sesClient = SesClient.builder()
            .credentialsProvider(getAwsCredentials(
                    "Access Key ID",
                    "Secret Key"))
            .region(Region.US_EAST_1) // Set your preferred region
            .build();

    // Send the email
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    message.writeTo(outputStream);

    RawMessage rawMessage = RawMessage.builder().data(SdkBytes.fromByteArray(outputStream.toByteArray())).build();

    SendRawEmailRequest rawEmailRequest = SendRawEmailRequest.builder().rawMessage(rawMessage).build();

    // The .sendRawEmail method is the one that actually sends the email
    SendRawEmailResponse sendRawEmailResponse = sesClient.sendRawEmail(rawEmailRequest);

    if (sendRawEmailResponse.sdkHttpResponse().isSuccessful()) {
        System.out.println("Message publishing successful");
    } else {
        throw new ResponseStatusException(
            HttpStatus.INTERNAL_SERVER_ERROR, sendRawEmailResponse.sdkHttpResponse().statusText().get()
        );
    }

    return "Email sent to subscribers. Message-ID: " + sendRawEmailResponse.messageId();
}

Y finalmente, enviemos una solicitud para probar si esto funciona:

$ curl http://localhost:8080/sendEmailWithAttachment?arn=arn:aws:sns:Stack-Abuse-Demo
Email sent to subscribers. Message-ID: 0100016fa375071f-4824-2b69e1050efa-000000

Nota: Si no puede encontrar el correo electrónico, asegúrese de revisar su carpeta de correo no deseado:

Envío de mensajes SMS

Algunos prefieren enviar mensajes SMS en lugar de correos electrónicos, principalmente porque es más probable que se vean los mensajes SMS. Hay dos tipos de mensajes SMS:

  • Promocional: Como su nombre lo indica, estos tipos de mensajes se utilizan únicamente con fines promocionales. Estos mensajes se envían entre las 9 a. M. Y las 9 p. M. Y solo deben contener material promocional.
  • Transaccional: Estos mensajes se utilizan para notificaciones críticas y de gran valor. Por ejemplo, para verificación de número de teléfono y OTP. Este tipo de mensajes no se pueden utilizar con fines promocionales, ya que violan las regulaciones establecidas para los mensajes transaccionales.

Enviar SMS a un solo número de teléfono

@RequestMapping("/sendSMS")
private String sendSMS(@RequestParam("phone") String phone) throws URISyntaxException {
    SnsClient snsClient = getSnsClient();

    final PublishRequest publishRequest = PublishRequest.builder()
            .phoneNumber(phone)
            .message("This is Stack Abuse SMS Demo")
            .build();

    PublishResponse publishResponse = snsClient.publish(publishRequest);

    if (publishResponse.sdkHttpResponse().isSuccessful()) {
        System.out.println("Message publishing to phone successful");
    } else {
        throw new ResponseStatusException(
            HttpStatus.INTERNAL_SERVER_ERROR, publishResponse.sdkHttpResponse().statusText().get()
        );
    }
    snsClient.close();
    return "SMS sent to " + phone + ". Message-ID: " + publishResponse.messageId();
}

Probémoslo con un curl solicitud:

$ curl http://localhost:8080/sendSMS?phone=%2B9112345789
SMS sent to +919538816148. Message-ID: 82cd26aa-947c-a978-703d0841fa7b

Enviar SMS a granel

El envío de SMS a granel no se realiza simplemente repitiendo el enfoque anterior. Esta vez, crearemos un tema de SNS y en lugar de email, usaremos el sms protocolo. Cuando deseamos enviar un mensaje masivo, todos los números de teléfono suscritos recibirán la notificación:

@RequestMapping("/sendBulkSMS")
private String sendBulkSMS(@RequestParam("arn") String arn) throws URISyntaxException {

    SnsClient snsClient = getSnsClient();

    String[] phoneNumbers = new String[]{"+917760041698", "917760041698", "7760041698" };

    for (String phoneNumber: phoneNumbers) {
        final SubscribeRequest subscribeRequest = SubscribeRequest.builder()
                                                  .topicArn(arn)
                                                  .protocol("sms")
                                                  .endpoint(phoneNumber)
                                                  .build();

        SubscribeResponse subscribeResponse = snsClient.subscribe(subscribeRequest);
        if (subscribeResponse.sdkHttpResponse().isSuccessful()) {
            System.out.println(phoneNumber + " subscribed to topic "+arn);
        }
    }

    final PublishRequest publishRequest = PublishRequest.builder()
                                          .topicArn(arn)
                                          .message("This is Stack Abuse SMS Demo")
                                          .build();

    PublishResponse publishResponse = snsClient.publish(publishRequest);

    if (publishResponse.sdkHttpResponse().isSuccessful()) {
        System.out.println("Bulk Message sending successful");
        System.out.println(publishResponse.messageId());
    } else {
        throw new ResponseStatusException(
            HttpStatus.INTERNAL_SERVER_ERROR, publishResponse.sdkHttpResponse().statusText().get()
        );
    }
    snsClient.close();
    return "Done";
}

Conclusión

Spring Cloud AWS hace que sea extremadamente fácil incorporar servicios de AWS en un proyecto de Spring Boot.

AWS SNS es un servicio de suscriptor / editor confiable y simple, utilizado por muchos desarrolladores en todo el mundo para enviar notificaciones simples a otros puntos finales HTTP, correos electrónicos, teléfonos y otros servicios de AWS.

Hemos creado una aplicación Spring Boot simple que genera un tema SNS, puede agregar suscriptores y enviarles mensajes por correo electrónico y SMS.

El código fuente está disponible en GitHub.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad