Skip to content

Commit 430dcd0

Browse files
author
Dennis Kieselhorst
authored
feat: new SpringDelegatingLambdaContainerHandler that leverages spring-cloud-function-serverless-web
Add implementation of delegating RequestStreamHandler
2 parents 7248749 + da5cb2b commit 430dcd0

File tree

19 files changed

+1170
-1
lines changed

19 files changed

+1170
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
*.jar
55
*.war
66
*.ear
7+
*.project
8+
*.classpath
9+
*.settings
710

811
# Idea project files
912
.idea/

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ public class AwsProxyRequest {
2929
//-------------------------------------------------------------
3030

3131
private String body;
32+
private String version;
3233
private String resource;
3334
private AwsProxyRequestContext requestContext;
3435
private MultiValuedTreeMap<String, String> multiValueQueryStringParameters;
35-
private Map<String, String> queryStringParameters;
36+
private Map<String, String> queryStringParameters;
3637
private Headers multiValueHeaders;
3738
private SingleValueHeaders headers;
3839
private Map<String, String> pathParameters;
@@ -95,6 +96,13 @@ public String getResource() {
9596
return resource;
9697
}
9798

99+
public String getVersion() {
100+
return version;
101+
}
102+
103+
public void setVersion(String version) {
104+
this.version = version;
105+
}
98106

99107
public void setResource(String resource) {
100108
this.resource = resource;

aws-serverless-java-container-springboot3/pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222

2323
<dependencies>
2424
<!-- Core interfaces for the aws-serverless-java-container project -->
25+
<dependency>
26+
<groupId>org.springframework.cloud</groupId>
27+
<artifactId>spring-cloud-function-serverless-web</artifactId>
28+
<version>4.0.4</version>
29+
</dependency>
2530
<dependency>
2631
<groupId>com.amazonaws.serverless</groupId>
2732
<artifactId>aws-serverless-java-container-core</artifactId>
@@ -281,4 +286,22 @@
281286
</plugin>
282287
</plugins>
283288
</build>
289+
<repositories>
290+
<repository>
291+
<id>spring-snapshots</id>
292+
<name>Spring Snapshots</name>
293+
<url>https://repo.spring.io/snapshot</url>
294+
<snapshots>
295+
<enabled>true</enabled>
296+
</snapshots>
297+
</repository>
298+
<repository>
299+
<id>spring-milestones</id>
300+
<name>Spring Milestones</name>
301+
<url>https://repo.spring.io/milestone</url>
302+
<snapshots>
303+
<enabled>false</enabled>
304+
</snapshots>
305+
</repository>
306+
</repositories>
284307
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.amazonaws.serverless.proxy.spring;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.OutputStream;
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.Map;
8+
import java.util.concurrent.CountDownLatch;
9+
import java.util.concurrent.TimeUnit;
10+
11+
import org.springframework.cloud.function.serverless.web.FunctionClassUtils;
12+
import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest;
13+
import org.springframework.cloud.function.serverless.web.ProxyMvc;
14+
import org.springframework.util.StringUtils;
15+
16+
import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter;
17+
import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter;
18+
import com.amazonaws.serverless.proxy.RequestReader;
19+
import com.amazonaws.serverless.proxy.SecurityContextWriter;
20+
import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse;
21+
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;
22+
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
23+
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
24+
import com.amazonaws.services.lambda.runtime.Context;
25+
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
26+
import com.fasterxml.jackson.databind.ObjectMapper;
27+
28+
import jakarta.servlet.http.HttpServletRequest;
29+
30+
/*
31+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
32+
*
33+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
34+
* with the License. A copy of the License is located at
35+
*
36+
* http://aws.amazon.com/apache2.0/
37+
*
38+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
39+
* OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
40+
* and limitations under the License.
41+
*/
42+
43+
/**
44+
* An implementation of {@link RequestStreamHandler} which delegates to
45+
* Spring Cloud Function serverless web module managed by Spring team.
46+
*
47+
* It requires no sub-classing from the user other then being identified as "Handler".
48+
* The configuration class(es) should be provided via MAIN_CLASS environment variable.
49+
*
50+
*/
51+
public class SpringDelegatingLambdaContainerHandler implements RequestStreamHandler {
52+
53+
private final Class<?>[] startupClasses;
54+
55+
private final ProxyMvc mvc;
56+
57+
private final ObjectMapper mapper;
58+
59+
private final AwsProxyHttpServletResponseWriter responseWriter;
60+
61+
public SpringDelegatingLambdaContainerHandler() {
62+
this(new Class[] {FunctionClassUtils.getStartClass()});
63+
}
64+
65+
public SpringDelegatingLambdaContainerHandler(Class<?>... startupClasses) {
66+
this.startupClasses = startupClasses;
67+
this.mvc = ProxyMvc.INSTANCE(this.startupClasses);
68+
this.mapper = new ObjectMapper();
69+
this.responseWriter = new AwsProxyHttpServletResponseWriter();
70+
}
71+
72+
@SuppressWarnings({"rawtypes" })
73+
@Override
74+
public void handleRequest(InputStream input, OutputStream output, Context lambdaContext) throws IOException {
75+
Map request = mapper.readValue(input, Map.class);
76+
SecurityContextWriter securityWriter = "2.0".equals(request.get("version"))
77+
? new AwsHttpApiV2SecurityContextWriter() : new AwsProxySecurityContextWriter();
78+
HttpServletRequest httpServletRequest = "2.0".equals(request.get("version"))
79+
? this.generateRequest2(request, lambdaContext, securityWriter) : this.generateRequest(request, lambdaContext, securityWriter);
80+
81+
CountDownLatch latch = new CountDownLatch(1);
82+
AwsHttpServletResponse httpServletResponse = new AwsHttpServletResponse(httpServletRequest, latch);
83+
try {
84+
mvc.service(httpServletRequest, httpServletResponse);
85+
latch.await(10, TimeUnit.SECONDS);
86+
mapper.writeValue(output, responseWriter.writeResponse(httpServletResponse, lambdaContext));
87+
}
88+
catch (Exception e) {
89+
throw new IllegalStateException(e);
90+
}
91+
}
92+
93+
@SuppressWarnings({ "unchecked", "rawtypes" })
94+
private HttpServletRequest generateRequest(Map request, Context lambdaContext, SecurityContextWriter securityWriter) {
95+
AwsProxyRequest v1Request = this.mapper.convertValue(request, AwsProxyRequest.class);
96+
97+
ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(),
98+
v1Request.getHttpMethod(), v1Request.getPath());
99+
100+
if (StringUtils.hasText(v1Request.getBody())) {
101+
httpRequest.setContentType("application/json");
102+
httpRequest.setContent(v1Request.getBody().getBytes(StandardCharsets.UTF_8));
103+
}
104+
httpRequest.setAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY, v1Request.getRequestContext());
105+
httpRequest.setAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY, v1Request.getStageVariables());
106+
httpRequest.setAttribute(RequestReader.API_GATEWAY_EVENT_PROPERTY, v1Request);
107+
httpRequest.setAttribute(RequestReader.ALB_CONTEXT_PROPERTY, v1Request.getRequestContext().getElb());
108+
httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext);
109+
httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, securityWriter.writeSecurityContext(v1Request, lambdaContext));
110+
return httpRequest;
111+
}
112+
113+
@SuppressWarnings({ "rawtypes", "unchecked" })
114+
public HttpServletRequest generateRequest2(Map request, Context lambdaContext, SecurityContextWriter securityWriter) {
115+
HttpApiV2ProxyRequest v2Request = this.mapper.convertValue(request, HttpApiV2ProxyRequest.class);
116+
ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(),
117+
v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath());
118+
119+
if (StringUtils.hasText(v2Request.getBody())) {
120+
httpRequest.setContentType("application/json");
121+
httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8));
122+
}
123+
httpRequest.setAttribute(RequestReader.HTTP_API_CONTEXT_PROPERTY, v2Request.getRequestContext());
124+
httpRequest.setAttribute(RequestReader.HTTP_API_STAGE_VARS_PROPERTY, v2Request.getStageVariables());
125+
httpRequest.setAttribute(RequestReader.HTTP_API_EVENT_PROPERTY, v2Request);
126+
httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext);
127+
httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, securityWriter.writeSecurityContext(v2Request, lambdaContext));
128+
return httpRequest;
129+
}
130+
}

0 commit comments

Comments
 (0)