Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,16 @@ public Enumeration<String> getParameterNames() {
@Override
@SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS") // suppressing this as according to the specs we should be returning null here if we can't find params
public String[] getParameterValues(String s) {
List<String> values = new ArrayList<>(Arrays.asList(getQueryParamValues(queryString, s, config.isQueryStringCaseSensitive())));

List<String> values = getQueryParamValuesAsList(queryString, s, config.isQueryStringCaseSensitive());

// copy list so we don't modifying the underlying multi-value query params
if (values != null) {
values = new ArrayList<>(values);
} else {
values = new ArrayList<>();
}

values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s)));

if (values.size() == 0) {
Expand Down Expand Up @@ -483,10 +491,10 @@ private MultiValuedTreeMap<String, String> parseRawQueryString(String qs) {

String[] kv = value.split(QUERY_STRING_KEY_VALUE_SEPARATOR);
String key = URLDecoder.decode(kv[0], LambdaContainerHandler.getContainerConfig().getUriEncoding());
String val = kv.length == 2 ? kv[1] : "";
String val = kv.length == 2 ? AwsHttpServletRequest.decodeValueIfEncoded(kv[1]) : "";
qsMap.add(key, val);
} catch (UnsupportedEncodingException e) {
log.error("Unsupported encoding in query string key: " + SecurityUtils.crlf(value), e);
log.error("Unsupported encoding in query string key-value pair: " + SecurityUtils.crlf(value), e);
}
}
return qsMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ private void addPart(Map<String, List<Part>> params, String fieldName, Part newP

protected String[] getQueryParamValues(MultiValuedTreeMap<String, String> qs, String key, boolean isCaseSensitive) {
List<String> value = getQueryParamValuesAsList(qs, key, isCaseSensitive);
if (value == null){
if (value == null) {
return null;
}
return value.toArray(new String[0]);
Expand All @@ -607,38 +607,56 @@ protected List<String> getQueryParamValuesAsList(MultiValuedTreeMap<String, Stri
}

protected Map<String, String[]> generateParameterMap(MultiValuedTreeMap<String, String> qs, ContainerConfig config) {
return generateParameterMap(qs, config, false);
}

protected Map<String, String[]> generateParameterMap(MultiValuedTreeMap<String, String> qs, ContainerConfig config, boolean decodeQueryParams) {
Map<String, String[]> output;

Map<String, List<String>> formEncodedParams = getFormUrlEncodedParametersMap();

if (qs == null) {
// Just transform the List<String> values to String[]
output = formEncodedParams.entrySet().stream()
return formEncodedParams.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, (e) -> e.getValue().toArray(new String[0])));
} else {
Map<String, List<String>> queryStringParams;
if (config.isQueryStringCaseSensitive()) {
queryStringParams = qs;
} else {
// If it's case insensitive, we check the entire map on every parameter
queryStringParams = qs.entrySet().stream().parallel().collect(
Collectors.toMap(
Map.Entry::getKey,
e -> getQueryParamValuesAsList(qs, e.getKey(), false)
));
}

// Merge formEncodedParams and queryStringParams Maps
output = Stream.of(formEncodedParams, queryStringParams).flatMap(m -> m.entrySet().stream())
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().toArray(new String[0]),
// If a parameter is in both Maps, we merge the list of values (and ultimately transform to String[])
(formParam, queryParam) -> Stream.of(formParam, queryParam).flatMap(Stream::of).toArray(String[]::new)
));
}

// decode all keys and values in map
final MultiValuedTreeMap<String, String> decodedQs = new MultiValuedTreeMap<String, String>();
if (decodeQueryParams) {
for (Map.Entry<String, List<String>> entry : qs.entrySet()) {
String k = decodeValueIfEncoded(entry.getKey());
List<String> v = getQueryParamValuesAsList(qs, entry.getKey(), false).stream()
.map(AwsHttpServletRequest::decodeValueIfEncoded)
.collect(Collectors.toList());
// addAll in case map has 2 keys that are identical once decoded
decodedQs.addAll(k, v);
}
} else {
decodedQs.putAll(qs);
}

Map<String, List<String>> queryStringParams;
if (config.isQueryStringCaseSensitive()) {
queryStringParams = decodedQs;
} else {
// If it's case insensitive, we check the entire map on every parameter
queryStringParams = decodedQs.entrySet().stream().collect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to remove this parallel here?

I'm not sure how much it actually helps, but this has been used since the first versions of this, so I'm curious if you had a specific reason to remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reintroduced it for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parallel streams actually perform worse than a sequential stream for small stream data sets due to the work involved in splitting up the data set, farming them out to threads, then recombining the results. Here's an article: https://blogs.oracle.com/javamagazine/post/java-parallel-streams-performance-benchmark.

And because I come from the old days where we had to get every last drop out of available memory and CPU because there was no magical cloud scaling :) So it's an old habit to be searching for these types of efficiency gains. I also reworked some usage in getQueryParameters() where arrays and lists were being created and immediately disposed of.

It's not a big deal in the end, but it's a good one to know. Parallel is not always the silver bullet it seems to be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sharing - makes sense! If @valerena agrees, we can address that for 2.1.1, I just didn't want to delay the 2.1.0 release any longer.

Collectors.toMap(
Map.Entry::getKey,
e -> getQueryParamValuesAsList(decodedQs, e.getKey(), false)
));
}

// Merge formEncodedParams and queryStringParams Maps
output = Stream.of(formEncodedParams, queryStringParams).flatMap(m -> m.entrySet().stream())
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().toArray(new String[0]),
// If a parameter is in both Maps, we merge the list of values (and ultimately transform to String[])
(formParam, queryParam) -> Stream.of(formParam, queryParam).flatMap(Stream::of).toArray(String[]::new)
));

return output;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,17 +325,26 @@ public String getContentType() {

@Override
public String getParameter(String s) {
String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive());
if (queryStringParameter != null) {
return queryStringParameter;
}

String[] bodyParams = getFormBodyParameterCaseInsensitive(s);
if (bodyParams.length == 0) {
return null;
} else {
return bodyParams[0];
// decode key if ALB
if (request.getRequestSource() == RequestSource.ALB) {
s = decodeValueIfEncoded(s);
}

String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive());
if (queryStringParameter != null) {
if (request.getRequestSource() == RequestSource.ALB) {
queryStringParameter = decodeValueIfEncoded(queryStringParameter);
}
return queryStringParameter;
}

String[] bodyParams = getFormBodyParameterCaseInsensitive(s);
if (bodyParams.length == 0) {
return null;
} else {
return bodyParams[0];
}
}


Expand All @@ -345,18 +354,43 @@ public Enumeration<String> getParameterNames() {
if (request.getMultiValueQueryStringParameters() == null) {
return Collections.enumeration(formParameterNames);
}
return Collections.enumeration(Stream.concat(formParameterNames.stream(),
request.getMultiValueQueryStringParameters().keySet().stream()).collect(Collectors.toSet()));

Set<String> paramNames = request.getMultiValueQueryStringParameters().keySet();
if (request.getRequestSource() == RequestSource.ALB) {
paramNames = paramNames.stream().map(AwsProxyHttpServletRequest::decodeValueIfEncoded).collect(Collectors.toSet());
}

return Collections.enumeration(
Stream.concat(formParameterNames.stream(), paramNames.stream())
.collect(Collectors.toSet()));
}


@Override
@SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS") // suppressing this as according to the specs we should be returning null here if we can't find params
public String[] getParameterValues(String s) {
List<String> values = new ArrayList<>(Arrays.asList(getQueryParamValues(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive())));

// decode key if ALB
if (request.getRequestSource() == RequestSource.ALB) {
s = decodeValueIfEncoded(s);
}

values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s)));
List<String> values = getQueryParamValuesAsList(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive());

// copy list so we don't modifying the underlying multi-value query params
if (values != null) {
values = new ArrayList<>(values);
} else {
values = new ArrayList<>();
}

// decode values if ALB
if (values != null && request.getRequestSource() == RequestSource.ALB) {
values = values.stream().map(AwsHttpServletRequest::decodeValueIfEncoded).collect(Collectors.toList());
}

values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s)));

if (values.size() == 0) {
return null;
} else {
Expand All @@ -367,7 +401,7 @@ public String[] getParameterValues(String s) {

@Override
public Map<String, String[]> getParameterMap() {
return generateParameterMap(request.getMultiValueQueryStringParameters(), config);
return generateParameterMap(request.getMultiValueQueryStringParameters(), config, request.getRequestSource() == RequestSource.ALB);
}


Expand Down
Loading
Loading