Skip to content

Commit be7ca2a

Browse files
committed
Changes to servlet context to address #84. Created a singleton ObjectMapper to speed up initialization and address #91. Added unit tests to make sure #90 is resolved and stays resolved.
1 parent e8e41cc commit be7ca2a

File tree

13 files changed

+176
-44
lines changed

13 files changed

+176
-44
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
package com.amazonaws.serverless.proxy;
1414

1515
import com.amazonaws.serverless.exceptions.InvalidRequestEventException;
16+
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
1617
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
1718
import com.amazonaws.serverless.proxy.model.ErrorModel;
1819

1920
import com.fasterxml.jackson.core.JsonProcessingException;
2021
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
23+
import com.fasterxml.jackson.databind.ser.std.JsonValueSerializer;
2124
import org.slf4j.Logger;
2225
import org.slf4j.LoggerFactory;
2326

@@ -55,8 +58,6 @@ public class AwsProxyExceptionHandler
5558
//-------------------------------------------------------------
5659

5760
private static Map<String, String> headers = new HashMap<>();
58-
private static ObjectMapper objectMapper = new ObjectMapper();
59-
6061

6162
//-------------------------------------------------------------
6263
// Constructors
@@ -87,7 +88,7 @@ public AwsProxyResponse handle(Throwable ex) {
8788
public void handle(Throwable ex, OutputStream stream) throws IOException {
8889
AwsProxyResponse response = handle(ex);
8990

90-
objectMapper.writeValue(stream, response);
91+
LambdaContainerHandler.getObjectMapper().writeValue(stream, response);
9192
}
9293

9394

@@ -96,8 +97,9 @@ public void handle(Throwable ex, OutputStream stream) throws IOException {
9697
//-------------------------------------------------------------
9798

9899
String getErrorJson(String message) {
100+
99101
try {
100-
return objectMapper.writeValueAsString(new ErrorModel(message));
102+
return LambdaContainerHandler.getObjectMapper().writeValueAsString(new ErrorModel(message));
101103
} catch (JsonProcessingException e) {
102104
log.error("Could not produce error JSON", e);
103105
return "{ \"message\": \"" + message + "\" }";

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.amazonaws.serverless.proxy.SecurityContextWriter;
2121
import com.amazonaws.services.lambda.runtime.Context;
2222

23+
import com.fasterxml.jackson.databind.ObjectMapper;
2324
import org.slf4j.Logger;
2425
import org.slf4j.LoggerFactory;
2526

@@ -64,6 +65,8 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
6465
//-------------------------------------------------------------
6566

6667
private static ContainerConfig config = ContainerConfig.defaultConfig();
68+
private static ObjectMapper objectMapper;
69+
6770

6871

6972
//-------------------------------------------------------------
@@ -97,6 +100,13 @@ protected abstract void handleRequest(ContainerRequestType containerRequest, Con
97100
// Methods - Public
98101
//-------------------------------------------------------------
99102

103+
public static ObjectMapper getObjectMapper() {
104+
if (objectMapper == null) {
105+
objectMapper = new ObjectMapper();
106+
}
107+
return objectMapper;
108+
}
109+
100110
/**
101111
* Configures the library to strip a base path from incoming requests before passing them on to the wrapped
102112
* framework. This was added in response to issue #34 (https://github.com/awslabs/aws-serverless-java-container/issues/34).

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
1717
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
18+
import com.amazonaws.serverless.proxy.model.ContainerConfig;
1819
import com.amazonaws.services.lambda.runtime.Context;
1920

2021
import org.apache.commons.fileupload.FileItem;
@@ -79,6 +80,7 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest {
7980
private Map<String, List<String>> urlEncodedFormParameters;
8081
private Map<String, Part> multipartFormParameters;
8182
private Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class);
83+
private ContainerConfig config;
8284

8385

8486
//-------------------------------------------------------------
@@ -89,6 +91,17 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd
8991
super(lambdaContext);
9092
this.request = awsProxyRequest;
9193
this.securityContext = awsSecurityContext;
94+
this.config = ContainerConfig.defaultConfig();
95+
96+
this.urlEncodedFormParameters = getFormUrlEncodedParametersMap();
97+
this.multipartFormParameters = getMultipartFormParametersMap();
98+
}
99+
100+
public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambdaContext, SecurityContext awsSecurityContext, ContainerConfig config) {
101+
super(lambdaContext);
102+
this.request = awsProxyRequest;
103+
this.securityContext = awsSecurityContext;
104+
this.config = config;
92105

93106
this.urlEncodedFormParameters = getFormUrlEncodedParametersMap();
94107
this.multipartFormParameters = getMultipartFormParametersMap();
@@ -181,10 +194,7 @@ public String getMethod() {
181194

182195
@Override
183196
public String getPathInfo() {
184-
String pathInfo = getServletPath().replace(getContextPath(), "");
185-
if (!pathInfo.startsWith("/")) {
186-
pathInfo = "/" + pathInfo;
187-
}
197+
String pathInfo = cleanUri(request.getPath()); //getServletPath().replace(getContextPath(), "");
188198
return decodeRequestPath(pathInfo, LambdaContainerHandler.getContainerConfig());
189199
}
190200

@@ -196,13 +206,18 @@ public String getPathTranslated() {
196206
}
197207

198208

199-
/**
200-
* In AWS API Gateway, stage is never given as part of the path.
201-
* @return
202-
*/
203209
@Override
204210
public String getContextPath() {
205-
return "";
211+
if (config.isUseStageAsServletContext()) {
212+
String contextPath = cleanUri(request.getRequestContext().getStage());
213+
if (config.getServiceBasePath() != null) {
214+
contextPath += cleanUri(config.getServiceBasePath());
215+
}
216+
217+
return contextPath;
218+
} else {
219+
return "" + (config.getServiceBasePath() != null ? cleanUri(config.getServiceBasePath()) : "");
220+
}
206221
}
207222

208223

@@ -232,28 +247,25 @@ public Principal getUserPrincipal() {
232247

233248
@Override
234249
public String getRequestURI() {
235-
return (getContextPath().isEmpty() ? "" : "/" + getContextPath()) + request.getPath();
250+
return cleanUri(getContextPath()) + cleanUri(request.getPath());
236251
}
237252

238253

239254
@Override
240255
public StringBuffer getRequestURL() {
241256
String url = "";
242257
url += getServerName();
243-
url += "/";
244-
url += getContextPath();
245-
url += "/";
246-
url += request.getPath();
247-
248-
url = url.replaceAll("/+", "/");
258+
url += cleanUri(getContextPath());
259+
url += cleanUri(request.getPath());
249260

250261
return new StringBuffer(getScheme() + "://" + url);
251262
}
252263

253264

254265
@Override
255266
public String getServletPath() {
256-
return decodeRequestPath(request.getPath(), LambdaContainerHandler.getContainerConfig());
267+
// we always work on the root path
268+
return "";
257269
}
258270

259271
@Override
@@ -653,15 +665,15 @@ private String getQueryStringParameterCaseInsensitive(String key) {
653665

654666

655667
private String[] getFormBodyParameterCaseInsensitive(String key) {
656-
List<String> values = urlEncodedFormParameters.get(key);
657-
if (values != null) {
658-
String[] valuesArray = new String[values.size()];
659-
valuesArray = values.toArray(valuesArray);
660-
return valuesArray;
661-
} else {
662-
return null;
663-
}
668+
List<String> values = urlEncodedFormParameters.get(key);
669+
if (values != null) {
670+
String[] valuesArray = new String[values.size()];
671+
valuesArray = values.toArray(valuesArray);
672+
return valuesArray;
673+
} else {
674+
return null;
664675
}
676+
}
665677

666678

667679
private Map<String, Part> getMultipartFormParametersMap() {
@@ -698,6 +710,22 @@ private Map<String, Part> getMultipartFormParametersMap() {
698710
return output;
699711
}
700712

713+
private String cleanUri(String uri) {
714+
String finalUri = uri;
715+
716+
if (!finalUri.startsWith("/")) {
717+
finalUri = "/" + finalUri;
718+
}
719+
720+
if (finalUri.endsWith(("/"))) {
721+
finalUri = finalUri.substring(0, finalUri.length() - 1);
722+
}
723+
724+
finalUri = finalUri.replaceAll("/+", "/");
725+
726+
return finalUri;
727+
}
728+
701729

702730
private Map<String, List<String>> getFormUrlEncodedParametersMap() {
703731
String contentType = getContentType();

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class AwsProxyHttpServletRequestReader extends RequestReader<AwsProxyRequ
3434
public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config)
3535
throws InvalidRequestEventException {
3636
request.setPath(stripBasePath(request.getPath(), config));
37-
AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(request, lambdaContext, securityContext);
37+
AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(request, lambdaContext, securityContext, config);
3838
servletRequest.setAttribute(API_GATEWAY_CONTEXT_PROPERTY, request.getRequestContext());
3939
servletRequest.setAttribute(API_GATEWAY_STAGE_VARS_PROPERTY, request.getStageVariables());
4040
servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext);

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public static void clearServletContextCache() {
104104
@Override
105105
public String getContextPath() {
106106
// servlets are always at the root.
107-
return "/";
107+
return "";
108108
}
109109

110110

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public abstract class FilterChainManager<ServletContextType extends ServletConte
9393
* @return A <code>FilterChainHolder</code> object that can be used to apply the filters to the request
9494
*/
9595
FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servlet) {
96-
String targetPath = request.getServletPath();
96+
String targetPath = request.getRequestURI();
9797
DispatcherType type = request.getDispatcherType();
9898

9999
// only return the cached result if the filter list hasn't changed in the meanwhile

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package com.amazonaws.serverless.proxy.internal.testutils;
1414

15+
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
1516
import com.amazonaws.serverless.proxy.model.ApiGatewayAuthorizerContext;
1617
import com.amazonaws.serverless.proxy.model.ApiGatewayRequestContext;
1718
import com.amazonaws.serverless.proxy.model.ApiGatewayRequestIdentity;
@@ -41,8 +42,6 @@ public class AwsProxyRequestBuilder {
4142
//-------------------------------------------------------------
4243

4344
private AwsProxyRequest request;
44-
private ObjectMapper mapper;
45-
4645

4746
//-------------------------------------------------------------
4847
// Constructors
@@ -59,7 +58,6 @@ public AwsProxyRequestBuilder(String path) {
5958

6059

6160
public AwsProxyRequestBuilder(String path, String httpMethod) {
62-
this.mapper = new ObjectMapper();
6361

6462
this.request = new AwsProxyRequest();
6563
this.request.setHeaders(new HashMap<>()); // avoid NPE
@@ -144,7 +142,7 @@ public AwsProxyRequestBuilder body(String body) {
144142
public AwsProxyRequestBuilder body(Object body) {
145143
if (request.getHeaders() != null && request.getHeaders().get(HttpHeaders.CONTENT_TYPE).equals(MediaType.APPLICATION_JSON)) {
146144
try {
147-
return body(mapper.writeValueAsString(body));
145+
return body(LambdaContainerHandler.getObjectMapper().writeValueAsString(body));
148146
} catch (JsonProcessingException e) {
149147
throw new UnsupportedOperationException("Could not serialize object: " + e.getMessage());
150148
}
@@ -239,13 +237,13 @@ public AwsProxyRequestBuilder serverName(String serverName) {
239237

240238
public AwsProxyRequestBuilder fromJsonString(String jsonContent)
241239
throws IOException {
242-
request = mapper.readValue(jsonContent, AwsProxyRequest.class);
240+
request = LambdaContainerHandler.getObjectMapper().readValue(jsonContent, AwsProxyRequest.class);
243241
return this;
244242
}
245243

246244
public AwsProxyRequestBuilder fromJsonPath(String filePath)
247245
throws IOException {
248-
request = mapper.readValue(new File(filePath), AwsProxyRequest.class);
246+
request = LambdaContainerHandler.getObjectMapper().readValue(new File(filePath), AwsProxyRequest.class);
249247
return this;
250248
}
251249

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.amazonaws.serverless.proxy.model;
22

33

4+
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest;
5+
6+
47
/**
58
* Configuration parameters for the framework
69
*/
@@ -12,6 +15,7 @@ public static ContainerConfig defaultConfig() {
1215
configuration.setStripBasePath(false);
1316
configuration.setUriEncoding(DEFAULT_URI_ENCODING);
1417
configuration.setConsolidateSetCookieHeaders(true);
18+
configuration.setUseStageAsServletContext(false);
1519

1620
return configuration;
1721
}
@@ -24,19 +28,29 @@ public static ContainerConfig defaultConfig() {
2428
private boolean stripBasePath;
2529
private String uriEncoding;
2630
private boolean consolidateSetCookieHeaders;
31+
private boolean useStageAsServletContext;
2732

2833

2934
//-------------------------------------------------------------
3035
// Methods - Getter/Setter
3136
//-------------------------------------------------------------
3237

38+
39+
/**
40+
* Returns the base path configured in the container. This configuration variable is used in conjuction with {@link #setStripBasePath(boolean)} to route
41+
* the request. When requesting the context path from an HttpServletRequest: {@link AwsProxyHttpServletRequest#getContextPath()} this base path is added
42+
* to the context even though it was initially stripped for the purpose of routing the request. We decided to add it to the context to address GitHub issue
43+
* #84 and allow framework's link builders to it.
44+
*
45+
* @return The base path configured for the container
46+
*/
3347
public String getServiceBasePath() {
3448
return serviceBasePath;
3549
}
3650

3751

3852
/**
39-
* Configures a base path that can be strippped from the request path before passing it to the frameowkr-specific implementation. This can be used to
53+
* Configures a base path that can be stripped from the request path before passing it to the framework-specific implementation. This can be used to
4054
* remove API Gateway's base path mappings from the request.
4155
* @param serviceBasePath The base path mapping to be removed.
4256
*/
@@ -99,4 +113,22 @@ public boolean isConsolidateSetCookieHeaders() {
99113
public void setConsolidateSetCookieHeaders(boolean consolidateSetCookieHeaders) {
100114
this.consolidateSetCookieHeaders = consolidateSetCookieHeaders;
101115
}
116+
117+
118+
/**
119+
* Tells whether the stage name passed in the request should be added to the context path: {@link AwsProxyHttpServletRequest#getContextPath()}.
120+
* @return true if the stage will be included in the context path, false otherwise.
121+
*/
122+
public boolean isUseStageAsServletContext() {
123+
return useStageAsServletContext;
124+
}
125+
126+
127+
/**
128+
* Sets whether the API Gateway stage name should be included in the servlet context path.
129+
* @param useStageAsServletContext true if you want the stage to appear as the root of the context path, false otherwise.
130+
*/
131+
public void setUseStageAsServletContext(boolean useStageAsServletContext) {
132+
this.useStageAsServletContext = useStageAsServletContext;
133+
}
102134
}

0 commit comments

Comments
 (0)