diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.aws-module.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.aws-module.gradle index 17ff9d59a9..0a6336d4a9 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.aws-module.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.aws-module.gradle @@ -11,13 +11,10 @@ if (sonatypePluginConfigured) { username = ossIndexUsername password = ossIndexPassword excludeCompileOnly = true - excludeCoordinates = [ - "org.eclipse.jetty:jetty-http:11.0.25" // no version of Jetty 11 patched https://ossindex.sonatype.org/component/pkg:maven/org.eclipse.jetty/jetty-http - ] } } configurations.all { resolutionStrategy { force("commons-io:commons-io:2.18.0") // first version patched https://ossindex.sonatype.org/component/pkg:maven/commons-io/commons-io } -} \ No newline at end of file +} diff --git a/function-aws-api-proxy-test/build.gradle.kts b/function-aws-api-proxy-test/build.gradle.kts index 05ef65887a..1a85db4a30 100644 --- a/function-aws-api-proxy-test/build.gradle.kts +++ b/function-aws-api-proxy-test/build.gradle.kts @@ -1,11 +1,9 @@ plugins { id("io.micronaut.build.internal.aws-module") } - dependencies { api(mn.micronaut.http.server) api(projects.micronautFunctionAwsApiProxy) - implementation(libs.jetty.server) testImplementation(mn.micronaut.http.client) testImplementation(mn.micronaut.jackson.databind) } diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServer.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServer.java deleted file mode 100644 index b7f6ee138f..0000000000 --- a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServer.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2017-2023 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.function.aws.proxy.test; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; -import io.micronaut.context.ApplicationContext; -import io.micronaut.context.ApplicationContextBuilder; -import io.micronaut.context.env.Environment; -import io.micronaut.context.env.PropertySource; -import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; -import io.micronaut.function.aws.proxy.payload2.APIGatewayV2HTTPEventFunction; -import io.micronaut.http.server.HttpServerConfiguration; -import io.micronaut.http.server.exceptions.HttpServerException; -import io.micronaut.http.server.exceptions.ServerStartupException; -import io.micronaut.runtime.ApplicationConfiguration; -import io.micronaut.runtime.server.EmbeddedServer; -import jakarta.inject.Singleton; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implementation that spins up an HTTP server based on Jetty that proxies request to a Lambda. - * - * @author gkrocher - * @since 2.1.0 - */ -@Singleton -@Internal -public class AwsApiProxyTestServer implements EmbeddedServer { - private final ApplicationContext applicationContext; - private final ServerPort serverPort; - private final AtomicBoolean running = new AtomicBoolean(false); - private Server server; - - public AwsApiProxyTestServer(ApplicationContext applicationContext, - HttpServerConfiguration httpServerConfiguration) { - this.applicationContext = applicationContext; - this.serverPort = createServerPort(httpServerConfiguration); - } - - private ServerPort createServerPort(HttpServerConfiguration httpServerConfiguration) { - Optional portOpt = httpServerConfiguration.getPort(); - if (portOpt.isPresent()) { - Integer port = portOpt.get(); - if (port == -1) { - return new ServerPort(true, 0); - - } else { - return new ServerPort(false, port); - } - } else { - if (applicationContext.getEnvironment().getActiveNames().contains(Environment.TEST)) { - return new ServerPort(true, 0); - } else { - return new ServerPort(false, 8080); - } - } - } - - @Override - public EmbeddedServer start() { - if (running.compareAndSet(false, true)) { - int port = serverPort.getPort(); - try { - this.server = new Server(port); - this.server.setHandler(new AwsProxyHandler(applicationContext)); - this.server.start(); - } catch (Exception e) { - throw new ServerStartupException(e.getMessage(), e); - } - } - return this; - } - - @Override - public EmbeddedServer stop() { - if (running.compareAndSet(true, false)) { - try { - server.stop(); - } catch (Exception e) { - // ignore / unrecoverable - } - } - return this; - } - - @Override - public int getPort() { - return server.getURI().getPort(); - } - - @Override - public String getHost() { - return "localhost"; - } - - @Override - public String getScheme() { - return "http"; - } - - @Override - public URL getURL() { - String spec = getScheme() + "://" + getHost() + ":" + getPort(); - try { - return new URL(spec); - } catch (MalformedURLException e) { - throw new HttpServerException("Invalid server URL " + spec); - } - } - - @Override - public URI getURI() { - try { - return getURL().toURI(); - } catch (URISyntaxException e) { - throw new HttpServerException("Invalid server URL " + getURL()); - } - } - - @Override - public ApplicationContext getApplicationContext() { - // Return the applicationContext of the handler constructed below, not that of the test-server - return ((AwsProxyHandler) server.getHandler()).getApplicationContext(); - } - - @Override - public ApplicationConfiguration getApplicationConfiguration() { - return getApplicationContext().getBean(ApplicationConfiguration.class); - } - - @Override - public boolean isRunning() { - return running.get(); - } - - private static class AwsProxyHandler extends AbstractHandler { - private static final Logger LOG = LoggerFactory.getLogger(AwsProxyHandler.class); - - private final APIGatewayV2HTTPEventFunction lambdaHandler; - private final ServletToAwsProxyRequestAdapter requestAdapter; - private final ServletToAwsProxyResponseAdapter responseAdapter; - private final ConversionService conversionService; - private final ContextProvider contextProvider; - - public AwsProxyHandler(ApplicationContext proxyTestApplicationContext) { - ApplicationContextBuilder builder = ApplicationContext.builder(); - for (PropertySource propertySource : proxyTestApplicationContext.getEnvironment().getPropertySources()) { - builder = builder.propertySources(propertySource); - } - lambdaHandler = new APIGatewayV2HTTPEventFunction(builder.build()); - ApplicationContext ctx = lambdaHandler.getApplicationContext(); - this.contextProvider = ctx.getBean(ContextProvider.class); - this.requestAdapter = ctx.getBean(ServletToAwsProxyRequestAdapter.class); - this.responseAdapter = ctx.getBean(ServletToAwsProxyResponseAdapter.class); - this.conversionService = ctx.getBean(ConversionService.class); - } - - ApplicationContext getApplicationContext() { - return lambdaHandler.getApplicationContext(); - } - - @Override - public void destroy() { - super.destroy(); - this.lambdaHandler.close(); - } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { - APIGatewayV2HTTPEvent awsProxyRequest = requestAdapter.createAwsProxyRequest(request); - APIGatewayV2HTTPResponse apiGatewayV2HTTPResponse = lambdaHandler.handleRequest(awsProxyRequest, contextProvider.getContext()); - responseAdapter.handle(conversionService, request, apiGatewayV2HTTPResponse, response); - baseRequest.setHandled(true); - } - } -} diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/AwsProxyHttpHandler.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/AwsProxyHttpHandler.java new file mode 100644 index 0000000000..c68d182fc6 --- /dev/null +++ b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/AwsProxyHttpHandler.java @@ -0,0 +1,159 @@ +/* + * Copyright 2017-2025 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.function.aws.proxy.test; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import io.micronaut.context.ApplicationContext; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.util.CollectionUtils; +import io.micronaut.core.util.StringUtils; +import io.micronaut.function.aws.proxy.payload2.APIGatewayV2HTTPEventFunction; +import io.micronaut.http.HttpHeaders; +import io.micronaut.http.HttpMethod; +import io.micronaut.http.uri.QueryStringDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@Internal +class AwsProxyHttpHandler implements HttpHandler { + private static final Logger LOG = LoggerFactory.getLogger(AwsProxyHttpHandler.class); + + APIGatewayV2HTTPEventFunction handler; + private final ContextProvider contextProvider; + + AwsProxyHttpHandler(APIGatewayV2HTTPEventFunction handler) { + this.handler = handler; + ApplicationContext ctx = handler.getApplicationContext(); + this.contextProvider = ctx.getBean(ContextProvider.class); + } + + @Override + public void handle(HttpExchange httpExchange) throws IOException { + APIGatewayV2HTTPEvent awsProxyRequest = createAwsProxyRequest(httpExchange); + APIGatewayV2HTTPResponse apiGatewayV2HTTPResponse = handler.handleRequest(awsProxyRequest, contextProvider.getContext()); + String payload = apiGatewayV2HTTPResponse.getBody(); + String contentLengthObject = apiGatewayV2HTTPResponse.getHeaders().get(HttpHeaders.CONTENT_LENGTH); + int contentLength = StringUtils.isNotEmpty(contentLengthObject) ? Integer.parseInt(contentLengthObject) : 0; + for (String headerName : apiGatewayV2HTTPResponse.getHeaders().keySet()) { + String headerValue = apiGatewayV2HTTPResponse.getHeaders().get(headerName); + List headerValues = List.of(headerValue.split(",")); + httpExchange.getResponseHeaders().put(headerName, StringUtils.isEmpty(headerValue) ? Collections.emptyList() : headerValues); + } + httpExchange.sendResponseHeaders(apiGatewayV2HTTPResponse.getStatusCode(), contentLength); + if (StringUtils.isNotEmpty(payload)) { + final OutputStream output = httpExchange.getResponseBody(); + byte[] payloadBytes = payload.getBytes(); + if (apiGatewayV2HTTPResponse.getIsBase64Encoded()) { + payloadBytes = Base64.getDecoder().decode(payloadBytes); + } + output.write(payloadBytes); + output.flush(); + } + httpExchange.close(); + } + + private APIGatewayV2HTTPEvent createAwsProxyRequest(HttpExchange httpExchange) { + final boolean isBase64Encoded = true; + return new APIGatewayV2HTTPEvent() { + private String body; + + @Override + public Map getHeaders() { + Map result = new HashMap<>(); + Set headerNames = httpExchange.getRequestHeaders().keySet(); + + for (String headerName : headerNames) { + List values = httpExchange.getRequestHeaders().get(headerName); + result.put(headerName, String.join(",", values)); + } + return result; + } + + @Override + public List getCookies() { + return httpExchange.getRequestHeaders().get(HttpHeaders.COOKIE); + } + + private Optional firstHeaderValue(String headerName) { + List headerValues = httpExchange.getRequestHeaders().get(headerName); + if (CollectionUtils.isEmpty(headerValues)) { + return Optional.empty(); + } + return Optional.of(headerValues.get(0)); + } + + @Override + public Map getQueryStringParameters() { + Map> parameters = new QueryStringDecoder(httpExchange.getRequestURI()).parameters(); + Map queryParams = new HashMap<>(); + for (String k : parameters.keySet()) { + queryParams.put(k, String.join(",", parameters.get(k))); + } + return queryParams; + } + + @Override + public RequestContext getRequestContext() { + RequestContext.Http.HttpBuilder httpBuilder = RequestContext.Http.builder() + .withPath(httpExchange.getRequestURI().getPath()) + .withMethod(httpExchange.getRequestMethod()) + .withProtocol(httpExchange.getProtocol()); + firstHeaderValue(HttpHeaders.USER_AGENT).ifPresent(httpBuilder::withUserAgent); + return RequestContext.builder() + .withHttp(httpBuilder.build()) + .build(); + } + + @Override + public boolean getIsBase64Encoded() { + return isBase64Encoded; + } + + @Override + public String getBody() { + if (body == null) { + HttpMethod httpMethod = HttpMethod.parse(httpExchange.getRequestMethod()); + if (HttpMethod.permitsRequestBody(httpMethod)) { + try (InputStream requestBody = httpExchange.getRequestBody()) { + byte[] data = requestBody.readAllBytes(); + if (isBase64Encoded) { + body = Base64.getEncoder().encodeToString(data); + } + } catch (IOException e) { + // ignore + } + } + } + return body; + } + }; + + } +} diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/DefaultServletToAwsProxyRequestAdapter.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/DefaultServletToAwsProxyRequestAdapter.java deleted file mode 100644 index b8a7744881..0000000000 --- a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/DefaultServletToAwsProxyRequestAdapter.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2017-2023 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.function.aws.proxy.test; - -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; -import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.http.HttpHeaders; -import io.micronaut.http.HttpMethod; -import jakarta.inject.Singleton; -import jakarta.servlet.http.HttpServletRequest; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * {@link io.micronaut.context.annotation.DefaultImplementation} of {@link ServletToAwsProxyRequestAdapter}. - * - * @author Sergio del Amo - */ -@Internal - -@Singleton -public class DefaultServletToAwsProxyRequestAdapter implements ServletToAwsProxyRequestAdapter { - - @Override - @NonNull - public APIGatewayV2HTTPEvent createAwsProxyRequest(@NonNull HttpServletRequest request) { - final boolean isBase64Encoded = true; - return new APIGatewayV2HTTPEvent() { - private String body; - - @Override - public Map getHeaders() { - Map result = new HashMap<>(); - Enumeration headerNames = request.getHeaderNames(); - - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - Enumeration headerValues = request.getHeaders(headerName); - List values = new ArrayList<>(); - while (headerValues.hasMoreElements()) { - values.add(headerValues.nextElement()); - } - result.put(headerName, String.join(",", values)); - } - return result; - } - - @Override - public List getCookies() { - Enumeration headerValues = request.getHeaders(HttpHeaders.COOKIE); - List cookies = new ArrayList<>(); - while (headerValues.hasMoreElements()) { - cookies.add(headerValues.nextElement()); - } - return cookies; - } - - private Optional firstHeaderValue(String headerName) { - Enumeration headerValues = request.getHeaders(headerName); - if (headerValues == null) { - return Optional.empty(); - } - if (headerValues.hasMoreElements()) { - return Optional.of(headerValues.nextElement()); - } - return Optional.empty(); - } - - @Override - public Map getQueryStringParameters() { - Map parameterMap = request.getParameterMap(); - Map result = new HashMap<>(); - for (String paramterName : parameterMap.keySet()) { - result.put(paramterName, String.join(",", parameterMap.get(paramterName))); - } - return result; - } - - @Override - public RequestContext getRequestContext() { - RequestContext.Http.HttpBuilder httpBuilder = RequestContext.Http.builder() - .withPath(request.getRequestURI()) - .withMethod(request.getMethod()) - .withProtocol(request.getProtocol()); - firstHeaderValue(HttpHeaders.USER_AGENT).ifPresent(httpBuilder::withUserAgent); - return RequestContext.builder() - .withHttp(httpBuilder.build()) - .build(); - } - - @Override - public boolean getIsBase64Encoded() { - return isBase64Encoded; - } - - @Override - public String getBody() { - if (body == null) { - HttpMethod httpMethod = HttpMethod.parse(request.getMethod()); - if (HttpMethod.permitsRequestBody(httpMethod)) { - try (InputStream requestBody = request.getInputStream()) { - byte[] data = requestBody.readAllBytes(); - if (isBase64Encoded) { - body = Base64.getEncoder().encodeToString(data); - } - } catch (IOException e) { - // ignore - } - } - } - return body; - } - }; - } -} diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/DefaultServletToAwsProxyResponseAdapter.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/DefaultServletToAwsProxyResponseAdapter.java deleted file mode 100644 index 1e01913a15..0000000000 --- a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/DefaultServletToAwsProxyResponseAdapter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2017-2020 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.function.aws.proxy.test; - -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.convert.ConversionService; -import io.micronaut.function.aws.proxy.MutableMapListOfStringAndMapStringConvertibleMultiValue; -import io.micronaut.http.HttpMethod; - -import jakarta.inject.Singleton; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.List; -import java.util.Map; - -/** - * {@link io.micronaut.context.annotation.DefaultImplementation} of {@link ServletToAwsProxyResponseAdapter}. - * - * @author Sergio del Amo - */ -@Singleton -public class DefaultServletToAwsProxyResponseAdapter implements ServletToAwsProxyResponseAdapter { - @Override - public void handle(@NonNull ConversionService conversionService, - @NonNull HttpServletRequest request, - @NonNull APIGatewayV2HTTPResponse awsProxyResponse, - @NonNull HttpServletResponse response) throws IOException { - populateHeaders(conversionService, awsProxyResponse, response); - response.setStatus(awsProxyResponse.getStatusCode()); - HttpMethod httpMethod = HttpMethod.parse(request.getMethod()); - if (httpMethod != HttpMethod.HEAD) { - - byte[] bodyAsBytes = parseBodyAsBytes(awsProxyResponse); - if (bodyAsBytes != null) { - response.setContentLength(bodyAsBytes.length); - if (bodyAsBytes.length > 0) { - try (OutputStream responseBody = response.getOutputStream()) { - responseBody.write(bodyAsBytes); - responseBody.flush(); - } - } - } - } - } - - private void populateHeaders(@NonNull ConversionService conversionService, - @NonNull APIGatewayV2HTTPResponse apiGatewayV2HTTPResponse, - @NonNull HttpServletResponse response) { - Map singleHeaders = apiGatewayV2HTTPResponse.getHeaders(); - Map> multiValueHeaders = apiGatewayV2HTTPResponse.getMultiValueHeaders(); - MutableMapListOfStringAndMapStringConvertibleMultiValue entries = new MutableMapListOfStringAndMapStringConvertibleMultiValue(conversionService, multiValueHeaders, singleHeaders); - - for (String name: entries.names()) { - response.addHeader(name, String.join(",", entries.getAll(name))); - } - } - - /** - * Returns the response's body bytes considering whether the body was Base64 encoded. - * @param awsProxyResponse The response - * @return The response's body bytes. - */ - @Nullable - protected byte[] parseBodyAsBytes(APIGatewayV2HTTPResponse awsProxyResponse) { - String body = awsProxyResponse.getBody(); - if (body == null) { - return null; - } - return awsProxyResponse.getIsBase64Encoded() ? Base64.getMimeDecoder().decode(body) : body.getBytes(getBodyCharset()); - } - - /** - * - * @return The charset used to read the response's body bytes. - */ - protected Charset getBodyCharset() { - return StandardCharsets.UTF_8; - } -} diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/EmbeddedServerFactory.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/EmbeddedServerFactory.java new file mode 100644 index 0000000000..9a009382d9 --- /dev/null +++ b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/EmbeddedServerFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2025 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.function.aws.proxy.test; + +import com.sun.net.httpserver.HttpHandler; +import io.micronaut.context.ApplicationContext; +import io.micronaut.context.ApplicationContextBuilder; +import io.micronaut.context.ApplicationContextProvider; +import io.micronaut.context.annotation.Factory; +import io.micronaut.context.env.PropertySource; +import io.micronaut.context.exceptions.ConfigurationException; +import io.micronaut.core.annotation.Experimental; +import io.micronaut.core.annotation.Internal; +import io.micronaut.function.aws.proxy.payload2.APIGatewayV2HTTPEventFunction; +import jakarta.inject.Named; +import jakarta.inject.Singleton; + +@Experimental +@Internal +@Factory +class EmbeddedServerFactory { + + @Named("HttpServer") + @Singleton + ApplicationContextProvider httpServerApplicationContextProvider(ApplicationContext applicationContext) { + ApplicationContextBuilder builder = ApplicationContext.builder(); + for (PropertySource propertySource : applicationContext.getEnvironment().getPropertySources()) { + builder = builder.propertySources(propertySource); + } + return new APIGatewayV2HTTPEventFunction(builder.build()); + } + + @Singleton + HttpHandler createHandler(@Named("HttpServer") ApplicationContextProvider applicationContextProvider) { + if (applicationContextProvider instanceof APIGatewayV2HTTPEventFunction function) { + return new AwsProxyHttpHandler(function); + } + throw new ConfigurationException("ApplicationContextProvider with name qualifier HttpServer should be of type APIGatewayV2HTTPEventFunction"); + } +} diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServerPort.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServerPort.java index e536b87b56..c93cef34f2 100644 --- a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServerPort.java +++ b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServerPort.java @@ -15,60 +15,40 @@ */ package io.micronaut.function.aws.proxy.test; +import io.micronaut.context.env.Environment; +import io.micronaut.core.annotation.Internal; +import io.micronaut.http.server.HttpServerConfiguration; + +import java.util.Optional; +import java.util.Set; + /** * Encapsulates the port assignment to be used when starting a server. * * @author Sergio del Amo + * @param random + * @param port */ -public class ServerPort { - private boolean random; - private Integer port; - - /** - * Constructor. - */ - public ServerPort() { - } - - /** - * - * @param random Whether the port was randomly assigned - * @param port Port number - */ - public ServerPort(boolean random, Integer port) { - this.random = random; - this.port = port; - } +@Internal +record ServerPort(boolean random, Integer port) { - /** - * - * @return Whether the port was randomly assigned - */ - public boolean isRandom() { - return random; - } - - /** - * - * @param random true if the port was randomly assigned - */ - public void setRandom(boolean random) { - this.random = random; - } - - /** - * - * @return The port number - */ - public Integer getPort() { - return port; - } + static ServerPort of(HttpServerConfiguration httpServerConfiguration, + Set activeNames) { + Optional portOpt = httpServerConfiguration.getPort(); + if (portOpt.isPresent()) { + Integer port = portOpt.get(); + if (port == -1) { + return new ServerPort(true, 0); - /** - * - * @param port Port number - */ - public void setPort(Integer port) { - this.port = port; + } else { + return new ServerPort(false, port); + } + } else { + if (activeNames.contains(Environment.TEST)) { + return new ServerPort(true, 0); + } else { + return new ServerPort(false, 8080); + } + } } } diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServletToAwsProxyRequestAdapter.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServletToAwsProxyRequestAdapter.java deleted file mode 100644 index 8ec4ddd24e..0000000000 --- a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServletToAwsProxyRequestAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2017-2020 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.function.aws.proxy.test; - -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.context.annotation.DefaultImplementation; -import jakarta.servlet.http.HttpServletRequest; - -/** - * Adapts from {@link HttpServletRequest} to {@link APIGatewayV2HTTPEvent}. - * @author Sergio del Amo - */ -@FunctionalInterface -@DefaultImplementation(DefaultServletToAwsProxyRequestAdapter.class) -public interface ServletToAwsProxyRequestAdapter { - /** - * - * @param request Servlets request - * @return An AWS Proxy request built from the servlet request supplied as a parameter - */ - @NonNull - APIGatewayV2HTTPEvent createAwsProxyRequest(@NonNull HttpServletRequest request); - -} diff --git a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServletToAwsProxyResponseAdapter.java b/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServletToAwsProxyResponseAdapter.java deleted file mode 100644 index 42d2c9a6f5..0000000000 --- a/function-aws-api-proxy-test/src/main/java/io/micronaut/function/aws/proxy/test/ServletToAwsProxyResponseAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2017-2020 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.function.aws.proxy.test; - -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.context.annotation.DefaultImplementation; - - -import io.micronaut.core.convert.ConversionService; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * Writes the contents of a {@link APIGatewayV2HTTPResponse} to a {@link HttpServletResponse}. - * @author Sergio del Amo - */ -@DefaultImplementation(DefaultServletToAwsProxyResponseAdapter.class) -@FunctionalInterface -public interface ServletToAwsProxyResponseAdapter { - - /** - * - * Writes the contents of a {@link APIGatewayV2HTTPResponse} to a {@link HttpServletResponse}. - * - * @param conversionService The conversion service - * @param request Servlet Request - * @param awsProxyResponse The AWS proxy response - * @param response The Servlet Response - * @throws IOException can be thrown while writing the response - */ - void handle(@NonNull ConversionService conversionService, - @NonNull HttpServletRequest request, - @NonNull APIGatewayV2HTTPResponse awsProxyResponse, - @NonNull HttpServletResponse response) throws IOException; -} diff --git a/function-aws-api-proxy-test/src/test/groovy/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServerSpec.groovy b/function-aws-api-proxy-test/src/test/groovy/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServerSpec.groovy index fbd43183bc..178c3ec4d9 100644 --- a/function-aws-api-proxy-test/src/test/groovy/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServerSpec.groovy +++ b/function-aws-api-proxy-test/src/test/groovy/io/micronaut/function/aws/proxy/test/AwsApiProxyTestServerSpec.groovy @@ -11,12 +11,15 @@ import io.micronaut.http.annotation.Post import io.micronaut.http.annotation.QueryValue import io.micronaut.http.client.HttpClient import io.micronaut.http.client.annotation.Client +import io.micronaut.http.uri.UriBuilder import io.micronaut.test.extensions.spock.annotation.MicronautTest import spock.lang.Issue import spock.lang.Specification import jakarta.inject.Inject +import java.nio.charset.StandardCharsets + @MicronautTest class AwsApiProxyTestServerSpec extends Specification { @Inject @@ -51,11 +54,12 @@ class AwsApiProxyTestServerSpec extends Specification { void 'query values with special chars are not double decoded'() { when: - String result = client.toBlocking().retrieve(HttpRequest.GET('/test-param?foo=prebar%2Bpostbar') - .contentType(MediaType.TEXT_PLAIN), String) + URI uri = UriBuilder.of("/test-param").queryParam("foo", "prebar postbar").build() + HttpRequest request = HttpRequest.GET(uri).contentType(MediaType.TEXT_PLAIN) + String result = client.toBlocking().retrieve(request, String) then: - result == 'get:prebar+postbar' + result == 'get:prebar postbar' } void 'test invoke post that returns empty body'() { @@ -72,11 +76,11 @@ class AwsApiProxyTestServerSpec extends Specification { void 'can return a ByteArray'() { when: HttpResponse response = client.toBlocking() - .exchange(HttpRequest.GET('/byte-array'), byte[]) + .exchange(HttpRequest.GET('/byte-array'), String) then: response.status == HttpStatus.OK - response.body.get() == (1..256).collect { it as byte } as byte[] + "Hello World" == response.body.get() } @Controller @@ -103,7 +107,9 @@ class AwsApiProxyTestServerSpec extends Specification { @Get(value = "/byte-array", produces = MediaType.APPLICATION_OCTET_STREAM) HttpResponse byteArray() { - return HttpResponse.ok((1..256).collect { it as byte } as byte[]) + String helloWorld = "Hello World" + byte[] byteArr = helloWorld.getBytes(StandardCharsets.UTF_8) + return HttpResponse.ok(byteArr) } } } diff --git a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerResponseEventAdapter.java b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerResponseEventAdapter.java index 07011f977b..f23b7542be 100644 --- a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerResponseEventAdapter.java +++ b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerResponseEventAdapter.java @@ -73,7 +73,7 @@ public MutableConvertibleValues getAttributes() { @Override public Optional getBody() { if (event.getIsBase64Encoded()) { - return (Optional) Optional.ofNullable(Base64.getMimeDecoder().decode(event.getBody())); + return (Optional) Optional.ofNullable(Base64.getDecoder().decode(event.getBody())); } return (Optional) Optional.ofNullable(event.getBody()); } diff --git a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerServletResponse.java b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerServletResponse.java index d1e7bbb3fc..dda8f114f5 100644 --- a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerServletResponse.java +++ b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/alb/ApplicationLoadBalancerServletResponse.java @@ -47,7 +47,7 @@ public ApplicationLoadBalancerResponseEvent getNativeResponse() { nativeResponse.setStatusCode(status); if (binaryTypeConfiguration.isMediaTypeBinary(getHeaders().getContentType().orElse(null))) { nativeResponse.setIsBase64Encoded(true); - nativeResponse.setBody(Base64.getMimeEncoder().encodeToString(body.toByteArray())); + nativeResponse.setBody(Base64.getEncoder().encodeToString(body.toByteArray())); } else { nativeResponse.setIsBase64Encoded(false); String bodyStr = body.toString(getCharacterEncoding()); diff --git a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyResponseEventAdapter.java b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyResponseEventAdapter.java index 0cfd1f7109..8797cf1602 100644 --- a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyResponseEventAdapter.java +++ b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyResponseEventAdapter.java @@ -72,7 +72,7 @@ public MutableConvertibleValues getAttributes() { @Override public Optional getBody() { if (event.getIsBase64Encoded() != null && event.getIsBase64Encoded()) { - return (Optional) Optional.ofNullable(Base64.getMimeDecoder().decode(event.getBody())); + return (Optional) Optional.ofNullable(Base64.getDecoder().decode(event.getBody())); } return (Optional) Optional.ofNullable(event.getBody()); } diff --git a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyServletResponse.java b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyServletResponse.java index 07b0a1d2ef..e16a089606 100644 --- a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyServletResponse.java +++ b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload1/ApiGatewayProxyServletResponse.java @@ -50,7 +50,7 @@ public APIGatewayProxyResponseEvent getNativeResponse() { if (binaryTypeConfiguration.isMediaTypeBinary(getHeaders().getContentType().orElse(null))) { apiGatewayProxyResponseEvent .withIsBase64Encoded(true) - .withBody(Base64.getMimeEncoder().encodeToString(body.toByteArray())); + .withBody(Base64.getEncoder().encodeToString(body.toByteArray())); } else { apiGatewayProxyResponseEvent .withIsBase64Encoded(false); diff --git a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/APIGatewayV2HTTPResponseServletResponse.java b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/APIGatewayV2HTTPResponseServletResponse.java index 08370447ea..7ca1481e04 100644 --- a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/APIGatewayV2HTTPResponseServletResponse.java +++ b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/APIGatewayV2HTTPResponseServletResponse.java @@ -50,7 +50,7 @@ public APIGatewayV2HTTPResponse getNativeResponse() { if (binaryTypeConfiguration.isMediaTypeBinary(getHeaders().getContentType().orElse(null))) { apiGatewayV2HTTPResponseBuilder .withIsBase64Encoded(true) - .withBody(Base64.getMimeEncoder().encodeToString(body.toByteArray())); + .withBody(Base64.getEncoder().encodeToString(body.toByteArray())); } else { String bodyStr = body.toString(getCharacterEncoding()); if (StringUtils.isNotEmpty(bodyStr)) { diff --git a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/ApiGatewayProxyResponseEventAdapter.java b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/ApiGatewayProxyResponseEventAdapter.java index f680791d26..e10835e8dd 100644 --- a/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/ApiGatewayProxyResponseEventAdapter.java +++ b/function-aws-api-proxy/src/main/java/io/micronaut/function/aws/proxy/payload2/ApiGatewayProxyResponseEventAdapter.java @@ -72,7 +72,7 @@ public MutableConvertibleValues getAttributes() { @Override public Optional getBody() { if (event.getIsBase64Encoded()) { - return (Optional) Optional.ofNullable(Base64.getMimeDecoder().decode(event.getBody())); + return (Optional) Optional.ofNullable(Base64.getDecoder().decode(event.getBody())); } return (Optional) Optional.ofNullable(event.getBody()); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1b018a8733..a3b9a37190 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,24 +1,23 @@ [versions] -micronaut = "4.8.3" +micronaut = "4.8.8" micronaut-docs = "2.0.0" micronaut-test = "4.7.0" -groovy = "4.0.22" +groovy = "4.0.26" spock = "2.3-groovy-4.0" -jetty = '11.0.25' bouncycastle = '1.70' fileupload = '0.0.6' logback-json-classic = '0.1.5' -micronaut-discovery = "4.5.2" +micronaut-discovery = "4.6.0" micronaut-groovy = "4.6.0" micronaut-logging = "1.6.1" -micronaut-mongodb = "5.5.0" +micronaut-mongodb = "5.6.0" micronaut-reactor = "3.7.0" micronaut-security = "4.12.0" micronaut-serde = "2.14.0" -micronaut-servlet = "5.1.1" -micronaut-test-resources="2.7.3" +micronaut-servlet = "5.2.1" +micronaut-test-resources = "2.8.0" micronaut-views = "5.7.0" micronaut-validation = "4.9.0" @@ -91,7 +90,6 @@ bouncycastle-provider = { module = 'org.bouncycastle:bcprov-jdk15on', version.re fileupload = { module = 'org.javadelight:delight-fileupload', version.ref = 'fileupload' } graal-sdk = { module = 'org.graalvm.sdk:graal-sdk', version.ref = 'graal' } jackson-afterburner = { module = 'com.fasterxml.jackson.module:jackson-module-afterburner' } -jetty-server = { module = 'org.eclipse.jetty:jetty-server', version.ref = 'jetty' } jcl-over-slf4j = { module = 'org.slf4j:jcl-over-slf4j', version.ref = 'slf4j' } junit-jupiter-engine = { module = 'org.junit.jupiter:junit-jupiter-engine' } junit-jupiter-api = { module = 'org.junit.jupiter:junit-jupiter-api' } diff --git a/test-suite-http-server-tck-function-aws-api-proxy-test/build.gradle.kts b/test-suite-http-server-tck-function-aws-api-proxy-test/build.gradle.kts index a755d9021c..b61f62c5ef 100644 --- a/test-suite-http-server-tck-function-aws-api-proxy-test/build.gradle.kts +++ b/test-suite-http-server-tck-function-aws-api-proxy-test/build.gradle.kts @@ -1,7 +1,6 @@ plugins { id("io.micronaut.build.internal.aws.http-server-tck-module") } - dependencies { testImplementation(projects.micronautFunctionAwsApiProxyTest) testImplementation(projects.micronautFunctionAwsApiProxy) diff --git a/test-suite-http-server-tck-function-aws-api-proxy-test/src/test/java/io/micronaut/http/server/tck/lambda/tests/MicronautLambdaHandlerHttpServerTestSuite.java b/test-suite-http-server-tck-function-aws-api-proxy-test/src/test/java/io/micronaut/http/server/tck/lambda/tests/MicronautLambdaHandlerHttpServerTestSuite.java index 186fe07762..b0bd0c2de9 100644 --- a/test-suite-http-server-tck-function-aws-api-proxy-test/src/test/java/io/micronaut/http/server/tck/lambda/tests/MicronautLambdaHandlerHttpServerTestSuite.java +++ b/test-suite-http-server-tck-function-aws-api-proxy-test/src/test/java/io/micronaut/http/server/tck/lambda/tests/MicronautLambdaHandlerHttpServerTestSuite.java @@ -1,9 +1,6 @@ package io.micronaut.http.server.tck.lambda.tests; -import org.junit.platform.suite.api.ExcludeClassNamePatterns; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.platform.suite.api.*; @Suite @SelectPackages({ @@ -11,9 +8,7 @@ "io.micronaut.http.server.tck.lambda.tests" }) @ExcludeClassNamePatterns({ - "io.micronaut.http.server.tck.tests.forms.FormUrlEncodedBodyInRequestFilterTest", - "io.micronaut.http.server.tck.tests.forms.FormsSubmissionsWithListsTest", - "io.micronaut.http.server.tck.tests.filter.options.OptionsFilterTest", + "io.micronaut.http.server.tck.tests.cors.CrossOriginTest", // CrossOriginTest#httHeaderValueAccessControlExposeHeaderValueCanBeSetViaCrossOriginAnnotation fails "io.micronaut.http.server.tck.tests.FilterProxyTest", // Immmutable request }) @SuiteDisplayName("HTTP Server TCK for Function AWS API Proxy Test")