Skip to content

Commit 7dbb3f2

Browse files
committed
Performance improvements all around. This addresses #99.
1 parent 498e989 commit 7dbb3f2

File tree

12 files changed

+132
-78
lines changed

12 files changed

+132
-78
lines changed

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.fasterxml.jackson.core.JsonParseException;
2626
import com.fasterxml.jackson.databind.JsonMappingException;
2727
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import com.fasterxml.jackson.databind.ObjectReader;
29+
import com.fasterxml.jackson.databind.ObjectWriter;
2830
import org.slf4j.Logger;
2931
import org.slf4j.LoggerFactory;
3032

@@ -62,20 +64,22 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
6264
private SecurityContextWriter<RequestType> securityContextWriter;
6365
private ExceptionHandler<ResponseType> exceptionHandler;
6466
private Class<RequestType> requestTypeClass;
67+
private Class<ResponseType> responseTypeClass;
6568

6669
protected Context lambdaContext;
6770
private LogFormatter<ContainerRequestType, ContainerResponseType> logFormatter;
6871

6972
private Logger log = LoggerFactory.getLogger(LambdaContainerHandler.class);
7073

71-
74+
private ObjectReader objectReader;
75+
private ObjectWriter objectWriter;
7276

7377
//-------------------------------------------------------------
7478
// Variables - Private - Static
7579
//-------------------------------------------------------------
7680

7781
private static ContainerConfig config = ContainerConfig.defaultConfig();
78-
private static volatile ObjectMapper objectMapper;
82+
private static ObjectMapper objectMapper = new ObjectMapper();
7983

8084

8185

@@ -84,12 +88,14 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
8488
//-------------------------------------------------------------
8589

8690
protected LambdaContainerHandler(Class<RequestType> requestClass,
91+
Class<ResponseType> responseClass,
8792
RequestReader<RequestType, ContainerRequestType> requestReader,
8893
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
8994
SecurityContextWriter<RequestType> securityContextWriter,
9095
ExceptionHandler<ResponseType> exceptionHandler) {
9196
log.info("Starting Lambda Container Handler");
9297
requestTypeClass = requestClass;
98+
responseTypeClass = responseClass;
9399
this.requestReader = requestReader;
94100
this.responseWriter = responseWriter;
95101
this.securityContextWriter = securityContextWriter;
@@ -113,9 +119,6 @@ protected abstract void handleRequest(ContainerRequestType containerRequest, Con
113119
//-------------------------------------------------------------
114120

115121
public static ObjectMapper getObjectMapper() {
116-
if (objectMapper == null) {
117-
objectMapper = new ObjectMapper();
118-
}
119122
return objectMapper;
120123
}
121124

@@ -188,10 +191,17 @@ public void proxyStream(InputStream input, OutputStream output, Context context)
188191
throws IOException {
189192

190193
try {
191-
RequestType request = getObjectMapper().readValue(input, requestTypeClass);
194+
if (objectReader == null) {
195+
objectReader = getObjectMapper().readerFor(requestTypeClass);
196+
}
197+
RequestType request = objectReader.readValue(input);
192198
ResponseType resp = proxy(request, context);
193199

194-
getObjectMapper().writeValue(output, resp);
200+
if (objectWriter == null) {
201+
objectWriter = getObjectMapper().writerFor(responseTypeClass);
202+
}
203+
204+
objectWriter.writeValue(output, resp);
195205
} catch (JsonParseException e) {
196206
log.error("Error while parsing request object stream", e);
197207
getObjectMapper().writeValue(output, exceptionHandler.handle(e));

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

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010
import javax.servlet.http.HttpServletResponse;
1111
import javax.ws.rs.core.SecurityContext;
1212

13-
import java.text.SimpleDateFormat;
14-
import java.time.Instant;
15-
import java.util.Calendar;
16-
import java.util.Date;
13+
import java.time.LocalDateTime;
14+
import java.time.ZoneOffset;
15+
import java.time.format.DateTimeFormatter;
16+
import java.time.format.DateTimeFormatterBuilder;
1717
import java.util.Locale;
1818

1919
import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_CONTEXT_PROPERTY;
20+
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
21+
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
22+
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
23+
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
24+
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
25+
import static java.time.temporal.ChronoField.YEAR;
2026

2127

2228
/**
@@ -27,10 +33,30 @@
2733
*/
2834
public class ApacheCombinedServletLogFormatter<ContainerRequestType extends HttpServletRequest, ContainerResponseType extends HttpServletResponse>
2935
implements LogFormatter<ContainerRequestType, ContainerResponseType> {
30-
SimpleDateFormat dateFormat;
36+
DateTimeFormatter dateFormat;
3137

3238
public ApacheCombinedServletLogFormatter() {
33-
dateFormat = new SimpleDateFormat("[dd/MM/yyyy:hh:mm:ss Z]");
39+
dateFormat = new DateTimeFormatterBuilder()
40+
.parseCaseInsensitive()
41+
.appendLiteral("[")
42+
.appendValue(DAY_OF_MONTH, 2)
43+
.appendLiteral("/")
44+
.appendValue(MONTH_OF_YEAR, 2)
45+
.appendLiteral("/")
46+
.appendValue(YEAR, 4)
47+
.appendLiteral(":")
48+
.appendValue(HOUR_OF_DAY, 2)
49+
.appendLiteral(":")
50+
.appendValue(MINUTE_OF_HOUR, 2)
51+
.appendLiteral(":")
52+
.appendValue(SECOND_OF_MINUTE, 2)
53+
.optionalStart()
54+
.appendOffset("+HHMM", "Z")
55+
.optionalEnd()
56+
.appendLiteral("]")
57+
.toFormatter();
58+
//DateTimeFormatter.ofPattern("dd/MM/yyyy:hh:mm:ss Z");
59+
//DateTimeFormatter.BASIC_ISO_DATE
3460
}
3561

3662
@Override
@@ -65,9 +91,9 @@ public String format(ContainerRequestType servletRequest, ContainerResponseType
6591

6692
// %t
6793
if (gatewayContext != null) {
68-
logLineBuilder.append(dateFormat.format(Date.from(Instant.ofEpochMilli(gatewayContext.getRequestTimeEpoch()))));
94+
logLineBuilder.append(dateFormat.format(LocalDateTime.ofEpochSecond(gatewayContext.getRequestTimeEpoch() / 1000, 0, ZoneOffset.UTC)));
6995
} else {
70-
logLineBuilder.append(dateFormat.format(Calendar.getInstance().getTime()));
96+
logLineBuilder.append(dateFormat.format(LocalDateTime.now()));
7197
}
7298
logLineBuilder.append(" ");
7399

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
7474
private Map<String, Object> attributes;
7575
private ServletContext servletContext;
7676
private AwsHttpSession session;
77+
private String queryString;
7778

7879
protected DispatcherType dispatcherType;
7980

@@ -290,8 +291,11 @@ protected String generateQueryString(Map<String, String> parameters) {
290291
if (parameters == null || parameters.size() == 0) {
291292
return null;
292293
}
294+
if (queryString != null) {
295+
return queryString;
296+
}
293297

294-
return parameters.keySet().stream()
298+
queryString = parameters.keySet().stream()
295299
.map(key -> {
296300
String newKey = key;
297301
String newValue = parameters.get(key);
@@ -310,6 +314,7 @@ protected String generateQueryString(Map<String, String> parameters) {
310314
return newKey + "=" + newValue;
311315
})
312316
.collect(Collectors.joining("&"));
317+
return queryString;
313318
}
314319

315320

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ public abstract class AwsLambdaServletContainerHandler<RequestType, ResponseType
7070
//-------------------------------------------------------------
7171

7272
protected AwsLambdaServletContainerHandler(Class<RequestType> requestTypeClass,
73+
Class<ResponseType> responseTypeClass,
7374
RequestReader<RequestType, ContainerRequestType> requestReader,
7475
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
7576
SecurityContextWriter<RequestType> securityContextWriter,
7677
ExceptionHandler<ResponseType> exceptionHandler) {
77-
super(requestTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
78+
super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
7879
// set the default log formatter for servlet implementations
7980
setLogFormatter(new ApacheCombinedServletLogFormatter<>());
8081
}

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

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd
102102
this.request = awsProxyRequest;
103103
this.securityContext = awsSecurityContext;
104104
this.config = config;
105-
106-
this.urlEncodedFormParameters = getFormUrlEncodedParametersMap();
107-
this.multipartFormParameters = getMultipartFormParametersMap();
108105
}
109106

110107

@@ -295,14 +292,14 @@ public void logout()
295292
@Override
296293
public Collection<Part> getParts()
297294
throws IOException, ServletException {
298-
return multipartFormParameters.values();
295+
return getMultipartFormParametersMap().values();
299296
}
300297

301298

302299
@Override
303300
public Part getPart(String s)
304301
throws IOException, ServletException {
305-
return multipartFormParameters.get(s);
302+
return getMultipartFormParametersMap().get(s);
306303
}
307304

308305

@@ -441,7 +438,7 @@ public Enumeration<String> getParameterNames() {
441438
if (request.getQueryStringParameters() != null) {
442439
paramNames.addAll(request.getQueryStringParameters().keySet());
443440
}
444-
paramNames.addAll(urlEncodedFormParameters.keySet());
441+
paramNames.addAll(getFormUrlEncodedParametersMap().keySet());
445442
return Collections.enumeration(paramNames);
446443
}
447444

@@ -473,10 +470,7 @@ public String[] getParameterValues(String s) {
473470
public Map<String, String[]> getParameterMap() {
474471
Map<String, String[]> output = new HashMap<>();
475472

476-
Map<String, List<String>> params = urlEncodedFormParameters;
477-
if (params == null) {
478-
params = new HashMap<>();
479-
}
473+
Map<String, List<String>> params = getFormUrlEncodedParametersMap();
480474

481475
if (request.getQueryStringParameters() != null) {
482476
for (Map.Entry<String, String> entry : request.getQueryStringParameters().entrySet()) {
@@ -655,7 +649,7 @@ private String getQueryStringParameterCaseInsensitive(String key) {
655649

656650

657651
private String[] getFormBodyParameterCaseInsensitive(String key) {
658-
List<String> values = urlEncodedFormParameters.get(key);
652+
List<String> values = getFormUrlEncodedParametersMap().get(key);
659653
if (values != null) {
660654
String[] valuesArray = new String[values.size()];
661655
valuesArray = values.toArray(valuesArray);
@@ -668,11 +662,15 @@ private String[] getFormBodyParameterCaseInsensitive(String key) {
668662

669663
@SuppressFBWarnings("FILE_UPLOAD_FILENAME")
670664
private Map<String, Part> getMultipartFormParametersMap() {
665+
if (multipartFormParameters != null) {
666+
return multipartFormParameters;
667+
}
671668
if (!ServletFileUpload.isMultipartContent(this)) { // isMultipartContent also checks the content type
672-
return new HashMap<>();
669+
multipartFormParameters = new HashMap<>();
670+
return multipartFormParameters;
673671
}
674672
Timer.start("SERVLET_REQUEST_GET_MULTIPART_PARAMS");
675-
Map<String, Part> output = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
673+
multipartFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
676674

677675
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
678676
try {
@@ -694,14 +692,14 @@ private Map<String, Part> getMultipartFormParametersMap() {
694692
}
695693
}
696694

697-
output.put(item.getFieldName(), newPart);
695+
multipartFormParameters.put(item.getFieldName(), newPart);
698696
}
699697
} catch (FileUploadException e) {
700698
Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS");
701699
log.error("Could not read multipart upload file", e);
702700
}
703701
Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS");
704-
return output;
702+
return multipartFormParameters;
705703
}
706704

707705

@@ -723,31 +721,36 @@ private String cleanUri(String uri) {
723721

724722

725723
private Map<String, List<String>> getFormUrlEncodedParametersMap() {
724+
if (urlEncodedFormParameters != null) {
725+
return urlEncodedFormParameters;
726+
}
726727
String contentType = getContentType();
727728
if (contentType == null) {
728-
return new HashMap<>();
729+
urlEncodedFormParameters = new HashMap<>();
730+
return urlEncodedFormParameters;
729731
}
730732
if (!contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED) || !getMethod().toLowerCase(Locale.ENGLISH).equals("post")) {
731-
return new HashMap<>();
733+
urlEncodedFormParameters = new HashMap<>();
734+
return urlEncodedFormParameters;
732735
}
733736
Timer.start("SERVLET_REQUEST_GET_FORM_PARAMS");
734737
String rawBodyContent = request.getBody();
735738

736-
Map<String, List<String>> output = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
739+
urlEncodedFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
737740
for (String parameter : rawBodyContent.split(FORM_DATA_SEPARATOR)) {
738741
String[] parameterKeyValue = parameter.split(HEADER_KEY_VALUE_SEPARATOR);
739742
if (parameterKeyValue.length < 2) {
740743
continue;
741744
}
742745
List<String> values = new ArrayList<>();
743-
if (output.containsKey(parameterKeyValue[0])) {
744-
values = output.get(parameterKeyValue[0]);
746+
if (urlEncodedFormParameters.containsKey(parameterKeyValue[0])) {
747+
values = urlEncodedFormParameters.get(parameterKeyValue[0]);
745748
}
746749
values.add(decodeValueIfEncoded(parameterKeyValue[1]));
747-
output.put(decodeValueIfEncoded(parameterKeyValue[0]), values);
750+
urlEncodedFormParameters.put(decodeValueIfEncoded(parameterKeyValue[0]), values);
748751
}
749752
Timer.stop("SERVLET_REQUEST_GET_FORM_PARAMS");
750-
return output;
753+
return urlEncodedFormParameters;
751754
}
752755

753756

aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,6 @@ public void postForm_getParts_parsing() {
8282
}
8383
}
8484

85-
@Test
86-
public void getForm_getParts_noPartsInGet() {
87-
try {
88-
AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "GET")
89-
.header(MULTIPART_FORM_DATA.getContentType().getName(), MULTIPART_FORM_DATA.getContentType().getValue())
90-
.body(IOUtils.toString(MULTIPART_FORM_DATA.getContent()))
91-
.build();
92-
93-
HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null);
94-
assertNotNull(request.getParts());
95-
assertEquals(0, request.getParts().size());
96-
} catch (IOException | ServletException e) {
97-
fail(e.getMessage());
98-
}
99-
}
100-
10185
@Test
10286
public void multipart_getParts_binary() {
10387
try {

0 commit comments

Comments
 (0)