Spring Cloud: AWS SNS

    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 鈥嬧媡odos 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.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *