Micronaut GraalVM Sendgrid AWS lambda 应用程序在尝试发送电子邮件时不断收到从 Sendgrid 返回的 400 信息

问题描述 投票:0回答:1

我使用 micronaut、java 和 sendgrid 构建了一个简单的电子邮件应用程序。使用它作为部署到 AWS 的基本 Java 应用程序,可以发送电子邮件 美好的。我创建了另一个 lambda 来尝试使用 GraalVM 功能。我只是遵循 Micronaut 文档指南,一切都编译并构建 使用 ./gradlew buildNativeLambda。在 AWS 控制台中运行测试选项可以正常工作,并且应用程序不会返回任何错误,但是 sendgrid 正在发送 400回。我觉得我缺少一些基本的东西。我尝试将我能想到的所有内容添加到 resources/META-INF/native-image/reflect-config.json 。尝试了 micronaut 文档中显示的不同方式来运行 lambda ,即使用控制器 方法、FunctionRequestHandler 和 FunctionLambdaRuntime。每个 lambda 都会起作用,并返回一个字符串或 APIGatewayProxyResponseEvent 没问题。似乎有一些与建立电子邮件有关的事情,我觉得我此时只是在墙上扔屎,但什么也没有 坚持下去,无论我改变什么,我都没有收到新的错误代码或任何将我推向正确方向的东西。

  • 当前的 build.gradle - 此时它非常臃肿,但就像我说的,我一直在努力让它工作,所以我似乎只是保留 添加一些东西希望能有所改变
    plugins {
        id("com.github.johnrengelman.shadow") version "8.1.1"
        id("io.micronaut.application") version "4.4.2"
        id("com.diffplug.spotless") version "6.23.3"
        id("io.micronaut.aot") version "4.4.2"
    }
    
    version = "0.1"
    group = "example.micronaut"
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        annotationProcessor("io.micronaut:micronaut-http-validation")
        annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
        implementation("io.micronaut.email:micronaut-email-sendgrid")
        implementation("io.micronaut:micronaut-http-client-jdk")
        implementation("jakarta.mail:jakarta.mail-api:2.1.3")
        implementation("io.micronaut.aws:micronaut-aws-lambda-events-serde")
        implementation("io.micronaut.serde:micronaut-serde-jackson")
        runtimeOnly("org.yaml:snakeyaml")
        runtimeOnly("ch.qos.logback:logback-classic")
    }
    
    
    application {
        mainClass = "example.micronaut.Application"
    }
    java {
        sourceCompatibility = JavaVersion.toVersion("17")
        targetCompatibility = JavaVersion.toVersion("17")
    }
    
    shadowJar {
        // Ensure resources are included
        mergeServiceFiles()
        include 'EmailTemplate/**'
    }
    
    sourceSets {
        main {
            resources {
                srcDirs = ['src/main/resources']
    //            include([ '**/*.properties', '**/*.yml', '**/*.json', '**/*.png', '**/*.html', '**/*.css','**/*.JPG'])
            }
        }
    }
    
    graalvmNative {
        toolchainDetection = false
    
        binaries {
            main {
                javaLauncher = javaToolchains.launcherFor {
                    languageVersion = JavaLanguageVersion.of(17)
                    vendor = JvmVendorSpec.matching("GraalVM Community")
                }
                resources.autodetect()
                metadataRepository { enabled = true }
                imageName.set('graal-mail')
                buildArgs.add('--verbose')
                buildArgs.add('--initialize-at-build-time=kotlin.coroutines.intrinsics.CoroutineSingletons')
                buildArgs.add('--initialize-at-run-time=reactor.core.publisher.Traces$StackWalkerCallSiteSupplierFactory')
                buildArgs.add('--initialize-at-run-time=reactor.core.publisher.Traces$ExceptionCallSiteSupplierFactory')
            }
        }
    }
    
    micronaut {
        runtime("lambda_provided")
        testRuntime("junit5")
        processing {
            incremental(true)
            annotations("example.micronaut.*")
        }
        aot {
            // Please review carefully the optimizations enabled below
            // Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details
            optimizeServiceLoading = false
            convertYamlToJava = false
            precomputeOperations = true
            cacheEnvironment = true
            optimizeClassLoading = true
            deduceEnvironment = true
            optimizeNetty = true
            replaceLogbackXml = true
        }
    }
    
    
    tasks.named("dockerfileNative") {
        baseImage = "amazonlinux:2023"
        jdkVersion = "17"
        args(
                "-XX:MaximumHeapSizePercent=80",
                "-Dio.netty.allocator.numDirectArenas=0",
                "-Dio.netty.noPreferDirect=true"
        )
    }
    
    spotless {
        java {
            licenseHeaderFile(file("LICENSEHEADER"))
        }
    } 
  • 电子邮件生成器 - 再次在这一点上,我在可能不需要的地方添加注释,但似乎没有什么影响它,应用程序运行,但从 lambda GraalVM 电子邮件无法正确发送
    package example.micronaut.services
    
    import com.sendgrid.Response;
    import example.micronaut.Util.MimeType;
    import example.micronaut.Util.UtilMailService;
    import io.micronaut.context.annotation.Value;
    import io.micronaut.core.annotation.ReflectiveAccess;
    import io.micronaut.email.BodyType;
    import io.micronaut.email.Email;
    import io.micronaut.email.sendgrid.SendgridEmailSender;
    import jakarta.inject.Inject;
    import jakarta.inject.Singleton;
    import jakarta.mail.internet.MimeBodyPart;
    
    import java.util.Optional;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Singleton
    @ReflectiveAccess
    public class TestCampaign {
        private final UtilMailService utilMailService;
        private final SendgridEmailSender sendgridEmailSender;
    
        @Value("${micronaut.email.from.email}")
        private String fromEmail;
    
        @Inject
        public TestCampaign(SendgridEmailSender sendgridEmailSender, UtilMailService utilMailService) {
            this.sendgridEmailSender = sendgridEmailSender;
            this.utilMailService = utilMailService;
        }
    
        public Response sendTestEmail() throws Exception {
            AtomicInteger index = new AtomicInteger(0);
            Email.Builder emailBuilder = getEmailBuilder();
    
            utilMailService.getContacts("EmailTemplate/EmailListTest.json").forEach(contact -> {
                if (index.getAndIncrement() == 0) {
                    emailBuilder.to(contact);
                } else {
                    emailBuilder.bcc(contact);
                }
            });
    
            return sendgridEmailSender.send(emailBuilder.build());
        }
    
        private Email.Builder getEmailBuilder() throws Exception {
            Optional<String> bodyOption = utilMailService.readHtmlFile("EmailTemplate/StdEmail.html");
            String body = bodyOption.orElse("Be Aware of Your Prescriptions at work");
            return Email.builder()
                    .from(fromEmail)
                    .subject("subject")
                    .body(body, BodyType.HTML)
                    .attachment(utilMailService.buildAttachment("EmailTemplate/Meds1.png", "meds1.png", MimeBodyPart.ATTACHMENT, MimeType.IMAGE_PNG).build())
                    .attachment(utilMailService.buildAttachment("EmailTemplate/Meds2.png", "meds2.png", MimeBodyPart.ATTACHMENT, MimeType.IMAGE_PNG).build())
                    .attachment(utilMailService.buildAttachment("EmailTemplate/Meds3.JPG", "meds3.JPG", MimeBodyPart.ATTACHMENT, MimeType.IMAGE_JPEG).build())
                    .attachment(utilMailService.buildAttachment("EmailTemplate/AllMeds.png", "AllMeds.png", MimeBodyPart
  • 当前实用程序可从 json 文件、附件和资源中的 html 页面添加联系人
    package example.micronaut.Util;
    
    import io.micronaut.core.annotation.NonNull;
    import io.micronaut.core.annotation.Nullable;
    import io.micronaut.core.annotation.ReflectiveAccess;
    import io.micronaut.core.io.IOUtils;
    import io.micronaut.core.io.ResourceResolver;
    import io.micronaut.core.type.Argument;
    import io.micronaut.email.Attachment;
    import io.micronaut.email.Contact;
    import io.micronaut.serde.ObjectMapper;
    import jakarta.inject.Inject;
    import jakarta.inject.Singleton;
    import jakarta.mail.internet.MimeBodyPart;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.URL;
    import java.util.*;
    
    @Singleton
    @ReflectiveAccess
    public class UtilMailService {
        private final ResourceResolver resourceResolver;
        private final ObjectMapper objectMapper;
    
    
        @Inject
        public UtilMailService(ResourceResolver resourceResolver, ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
            this.resourceResolver = resourceResolver;
        }
    
        public @NonNull Attachment.Builder buildAttachment(String path, String name, String disposition, MimeType type) throws Exception {
            Optional<byte[]> fileBytes = getClasspathResourceAsBytes(path);
    
            if (fileBytes.isEmpty()) {
                throw new IllegalArgumentException("File not found! " + path);
            }
    
            Attachment.Builder newAttachment = Attachment.builder().filename(name).contentType(type.getMimeType()).content(fileBytes.get());
    
            if (Objects.equals(disposition, MimeBodyPart.INLINE)) {
    
                newAttachment.id(name).disposition(disposition);
            }
    
            return newAttachment;
        }
    
        public Optional<byte[]> getClasspathResourceAsBytes(String path) throws Exception {
            Optional<URL> url = resourceResolver.getResource("classpath:" + path);
            if (url.isPresent()) {
                try (InputStream inputStream = url.get().openStream()) {
                    return Optional.of(inputStream.readAllBytes());
                }
            }
            else {
                return Optional.empty();
            }
        }
    
        public List<Contact> getContacts(String path) throws IOException {
            List<Contact> contactList = new ArrayList<>();
            Map<String, String> contactMap = readJsonFileToMap(path).orElse(Map.of("[email protected]", "crash"));
            contactMap.forEach((key, value) -> {
                contactList.add(new Contact(key, value));
            });
    
            return contactList;
        }
    
        public @Nullable Optional<Map<String, String>> readJsonFileToMap(String resourcePath) throws IOException {
            Optional<URL> url = resourceResolver.getResource("classpath:" + resourcePath);
            if (url.isPresent()) {
                try (InputStream inputStream = url.get().openStream()) {
                    return Optional.of(objectMapper.readValue(inputStream.readAllBytes(), Argument.mapOf(String.class, String.class)));
                }
            }
            else {
                return Optional.empty();
            }
        }
    
        public Optional<String> readHtmlFile(String path) throws Exception {
            Optional<URL> url = resourceResolver.getResource("classpath:" + path);
            if (url.isPresent()) {
                return Optional.of(IOUtils.readText(new BufferedReader(new InputStreamReader(url.get().openStream()))));
            }
            else {
                return Optional.empty();
            }
        }
    
        public Optional<String> getClasspathResourceAsText(String path) throws Exception {
            Optional<URL> url = resourceResolver.getResource("classpath:" + path);
            if (url.isPresent()) {
                return Optional.of(IOUtils.readText(new BufferedReader(new InputStreamReader(url.get().openStream()))));
            }
            else {
                return Optional.empty();
            }
        }
    }
  • 控制器代码
package example.micronaut;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.sendgrid.Response;
import example.micronaut.services.EmailSendingService;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;
import io.micronaut.serde.ObjectMapper;
import jakarta.inject.Inject;

import java.util.Collections;

@Controller
public class HomeController {
    private final EmailSendingService emailSendingService;
    @Inject
    ObjectMapper objectMapper;
    APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();

    @Inject
    public HomeController(EmailSendingService emailSendingService) {
        this.emailSendingService = emailSendingService;
    }

    @Get
    public APIGatewayProxyResponseEvent index(@QueryValue(defaultValue = "test") String campaign) {
        try {

            Response sendGridResponse = emailSendingService.sendCustomizedEmail(campaign);
            String json = new String(objectMapper.writeValueAsBytes(Collections.singletonMap("message", response.getHeaders())));
            response.setStatusCode(sendGridResponse.getStatusCode());
            response.setBody(json);
        }
        catch (Exception e) {
            response.setStatusCode(500);
            response.setBody(String.valueOf(e.getMessage()));
        }

        return response;
    }
}
  • 当前运行时配置

enter image description here

  • 测试响应

--执行日志

    {
      "statusCode": 200,
      "headers": {
        "Date": "Sat, 14 Sep 2024 18:05:23 GMT",
        "Content-Type": "application/json"
      },
      "multiValueHeaders": {
        "Date": [
          "Sat, 14 Sep 2024 18:05:23 GMT"
        ],
        "Content-Type": [
          "application/json"
        ]
      },
      "body": "{\"statusCode\":400,\"body\":\"{\\\"message\\\":null}\"}",
      "isBase64Encoded": false
    }

--日志输出

    START RequestId: b984c570-f7af-4d4c-a929-0ae8cf1fdcd7 Version: $LATEST
     [36m18:05:23.612 [0;39m  [1;30m[main] [0;39m  [34mINFO  [0;39m  [35mi.m.e.sendgrid.SendgridEmailSender [0;39m - Status Code: 400
     [36m18:05:23.612 [0;39m  [1;30m[main] [0;39m  [34mINFO  [0;39m  [35mi.m.e.sendgrid.SendgridEmailSender [0;39m - Body: {"errors":[{"message":"The from object must be provided for every email send. It is an object that requires the email parameter, but may also contain a name parameter.  e.g. {\"email\" : \"[email protected]\"}  or {\"email\" : \"[email protected]\", \"name\" : \"Example Recipient\"}.","field":"from.email","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.from"},{"message":"The personalizations field is required and must have at least one personalization.","field":"personalizations","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#-Personalizations-Errors"},{"message":"Unless a valid template_id is provided, the content parameter is required. There must be at least one defined content block. We typically suggest both text/plain and text/html blocks are included, but only one block is required.","field":"content","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.content"}]}
     [36m18:05:23.612 [0;39m  [1;30m[main] [0;39m  [34mINFO  [0;39m  [35mi.m.e.sendgrid.SendgridEmailSender [0;39m - Headers {Strict-Transport-Security=max-age=600; includeSubDomains, Server=nginx, Access-Control-Allow-Origin=https://sendgrid.api-docs.io, Access-Control-Allow-Methods=POST, Connection=keep-alive, X-No-CORS-Reason=https://sendgrid.com/docs/Classroom/Basics/API/cors.html, Content-Length=980, Access-Control-Max-Age=600, Date=Sat, 14 Sep 2024 18:05:23 GMT, Access-Control-Allow-Headers=Authorization, Content-Type, On-behalf-of, x-sg-elas-acl, Content-Type=application/json}
    END RequestId: b984c570-f7af-4d4c-a929-0ae8cf1fdcd7
    REPORT RequestId: b984c570-f7af-4d4c-a929-0ae8cf1fdcd7  Duration: 2722.27 ms    Billed Duration: 2723 ms    Memory Size: 128 MB Max Memory Used: 113 MB 

就像我说的,应用程序在未部署为 GraalVM 时也能正常工作。任何帮助或其他尝试都非常感谢

我尝试将我能想到的所有内容添加到 resources/META-INF/native-image/reflect-config.json 中。尝试了中显示的不同方法 micronaut 文档来运行 lambda ,即使用 Controller 方法、 FunctionRequestHandler 和 FunctionLambdaRuntime 。每个 lambda 都会 工作并且它返回一个字符串或 APIGatewayProxyResponseEvent 没有问题。建立电子邮件似乎有些问题

java aws-lambda build.gradle graalvm micronaut-aws
1个回答
0
投票

我认为用于构建电子邮件的 io.micronaut.email.Email 类在某处使用反射,并且一旦编译到 GraalVM 就没有注册。在浏览完 io.micronaut.email.sendgrid.SendgridEmailSender 工作原理的代码后,反射问题来自 io.micronaut.email.sendgrid.SendgridEmailComposer,特别是在创建 sendgrid 对象个性化的方法中 和内容。

使用GraalConfig.java文件进行反射配置。在 micronaut 页面反射元数据

上进行了解释
@ReflectionConfig(type = Content.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Content.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Content.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Personalization.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Personalization.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Personalization.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Mail.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Mail.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Mail.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Attachments.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Attachments.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Attachments.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Email.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Email.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
@ReflectionConfig(type = Email.class, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
class GraalConfig {
}
© www.soinside.com 2019 - 2024. All rights reserved.