Skip to content

Commit 9ab50f7

Browse files
committed
Additional changes and unit tests for ALB support (aws#214). Turns out that the behavior between ALB and API Gateway differs: ALB does not decode encoded query parameters, API Gateway does.
1 parent 2c52bd0 commit 9ab50f7

File tree

6 files changed

+178
-50
lines changed

6 files changed

+178
-50
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,11 @@ public String getContextPath() {
234234
@Override
235235
public String getQueryString() {
236236
try {
237-
return this.generateQueryString(request.getMultiValueQueryStringParameters(), true, config.getUriEncoding());
237+
return this.generateQueryString(
238+
request.getMultiValueQueryStringParameters(),
239+
// ALB does not automatically decode parameters, so we don't want to re-encode them
240+
request.getRequestSource() != AwsProxyRequest.RequestSource.ALB,
241+
config.getUriEncoding());
238242
} catch (ServletException e) {
239243
log.error("Could not generate query string", e);
240244
return null;

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.amazonaws.serverless.proxy.model.ApiGatewayRequestIdentity;
2020
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
2121
import com.amazonaws.serverless.proxy.model.CognitoAuthorizerClaims;
22+
import com.amazonaws.serverless.proxy.model.ContainerConfig;
2223
import com.amazonaws.serverless.proxy.model.Headers;
2324
import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap;
2425

@@ -36,6 +37,9 @@
3637
import java.io.File;
3738
import java.io.IOException;
3839
import java.io.InputStream;
40+
import java.io.UnsupportedEncodingException;
41+
import java.net.URLDecoder;
42+
import java.net.URLEncoder;
3943
import java.nio.charset.Charset;
4044
import java.nio.charset.StandardCharsets;
4145
import java.util.Base64;
@@ -184,7 +188,24 @@ public AwsProxyRequestBuilder queryString(String key, String value) {
184188
this.request.setMultiValueQueryStringParameters(new MultiValuedTreeMap<>());
185189
}
186190

187-
this.request.getMultiValueQueryStringParameters().add(key, value);
191+
if (request.getRequestSource() == AwsProxyRequest.RequestSource.API_GATEWAY) {
192+
this.request.getMultiValueQueryStringParameters().add(key, value);
193+
}
194+
// ALB does not decode parameters automatically like API Gateway.
195+
if (request.getRequestSource() == AwsProxyRequest.RequestSource.ALB) {
196+
String albValue = value;
197+
try {
198+
//if (URLDecoder.decode(value, ContainerConfig.DEFAULT_CONTENT_CHARSET).equals(value)) {
199+
// TODO: Assume we are always given an unencoded value, smarter check here to encode
200+
// only if necessary
201+
albValue = URLEncoder.encode(value, ContainerConfig.DEFAULT_CONTENT_CHARSET);
202+
//}
203+
} catch (UnsupportedEncodingException e) {
204+
e.printStackTrace();
205+
throw new RuntimeException(e);
206+
}
207+
this.request.getMultiValueQueryStringParameters().add(key, albValue);
208+
}
188209
return this;
189210
}
190211

@@ -219,14 +240,20 @@ public AwsProxyRequestBuilder binaryBody(InputStream is)
219240

220241

221242
public AwsProxyRequestBuilder authorizerPrincipal(String principal) {
222-
if (this.request.getRequestContext().getAuthorizer() == null) {
223-
this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext());
243+
if (this.request.getRequestSource() == AwsProxyRequest.RequestSource.API_GATEWAY) {
244+
if (this.request.getRequestContext().getAuthorizer() == null) {
245+
this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext());
246+
}
247+
this.request.getRequestContext().getAuthorizer().setPrincipalId(principal);
248+
if (this.request.getRequestContext().getAuthorizer().getClaims() == null) {
249+
this.request.getRequestContext().getAuthorizer().setClaims(new CognitoAuthorizerClaims());
250+
}
251+
this.request.getRequestContext().getAuthorizer().getClaims().setSubject(principal);
224252
}
225-
this.request.getRequestContext().getAuthorizer().setPrincipalId(principal);
226-
if (this.request.getRequestContext().getAuthorizer().getClaims() == null) {
227-
this.request.getRequestContext().getAuthorizer().setClaims(new CognitoAuthorizerClaims());
253+
if (this.request.getRequestSource() == AwsProxyRequest.RequestSource.ALB) {
254+
header("x-amzn-oidc-identity", principal);
255+
header("x-amzn-oidc-accesstoken", Base64.getMimeEncoder().encodeToString("test-token".getBytes()));
228256
}
229-
this.request.getRequestContext().getAuthorizer().getClaims().setSubject(principal);
230257
return this;
231258
}
232259

aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,24 @@
3030
import org.glassfish.jersey.logging.LoggingFeature;
3131
import org.glassfish.jersey.server.ResourceConfig;
3232
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
import org.junit.runners.Parameterized;
3335

3436
import javax.ws.rs.core.HttpHeaders;
3537
import javax.ws.rs.core.MediaType;
3638
import javax.ws.rs.core.Response;
3739

3840
import java.io.IOException;
41+
import java.util.Arrays;
42+
import java.util.Collection;
3943
import java.util.UUID;
4044

4145
import static org.junit.Assert.*;
4246

4347
/**
4448
* Unit test class for the Jersey AWS_PROXY default implementation
4549
*/
50+
@RunWith(Parameterized.class)
4651
public class JerseyAwsProxyTest {
4752
private static final String CUSTOM_HEADER_KEY = "x-custom-header";
4853
private static final String CUSTOM_HEADER_VALUE = "my-custom-value";
@@ -59,9 +64,28 @@ public class JerseyAwsProxyTest {
5964

6065
private static Context lambdaContext = new MockLambdaContext();
6166

67+
private boolean isAlb;
68+
69+
public JerseyAwsProxyTest(boolean alb) {
70+
isAlb = alb;
71+
}
72+
73+
@Parameterized.Parameters
74+
public static Collection<Object> data() {
75+
return Arrays.asList(new Object[] { false, true });
76+
}
77+
78+
private AwsProxyRequestBuilder getRequestBuilder(String path, String method) {
79+
AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(path, method);
80+
if (isAlb) builder.alb();
81+
82+
return builder;
83+
}
84+
85+
6286
@Test
6387
public void alb_basicRequest_expectSuccess() {
64-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/headers", "GET")
88+
AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET")
6589
.json()
6690
.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE)
6791
.alb()
@@ -78,7 +102,7 @@ public void alb_basicRequest_expectSuccess() {
78102

79103
@Test
80104
public void headers_getHeaders_echo() {
81-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/headers", "GET")
105+
AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET")
82106
.json()
83107
.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE)
84108
.build();
@@ -92,7 +116,7 @@ public void headers_getHeaders_echo() {
92116

93117
@Test
94118
public void headers_servletRequest_echo() {
95-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-headers", "GET")
119+
AwsProxyRequest request = getRequestBuilder("/echo/servlet-headers", "GET")
96120
.json()
97121
.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE)
98122
.build();
@@ -106,7 +130,7 @@ public void headers_servletRequest_echo() {
106130

107131
@Test
108132
public void context_servletResponse_setCustomHeader() {
109-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-response", "GET")
133+
AwsProxyRequest request = getRequestBuilder("/echo/servlet-response", "GET")
110134
.json()
111135
.build();
112136

@@ -117,7 +141,7 @@ public void context_servletResponse_setCustomHeader() {
117141

118142
@Test
119143
public void context_serverInfo_correctContext() {
120-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-context", "GET").build();
144+
AwsProxyRequest request = getRequestBuilder("/echo/servlet-context", "GET").build();
121145
AwsProxyResponse output = handler.proxy(request, lambdaContext);
122146
for (String header : output.getMultiValueHeaders().keySet()) {
123147
System.out.println(header + ": " + output.getMultiValueHeaders().getFirst(header));
@@ -130,7 +154,7 @@ public void context_serverInfo_correctContext() {
130154

131155
@Test
132156
public void requestScheme_valid_expectHttps() {
133-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/scheme", "GET")
157+
AwsProxyRequest request = getRequestBuilder("/echo/scheme", "GET")
134158
.json()
135159
.build();
136160

@@ -143,7 +167,7 @@ public void requestScheme_valid_expectHttps() {
143167

144168
@Test
145169
public void requestFilter_injectsServletRequest_expectCustomAttribute() {
146-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/filter-attribute", "GET")
170+
AwsProxyRequest request = getRequestBuilder("/echo/filter-attribute", "GET")
147171
.json()
148172
.build();
149173

@@ -156,21 +180,24 @@ public void requestFilter_injectsServletRequest_expectCustomAttribute() {
156180

157181
@Test
158182
public void authorizer_securityContext_customPrincipalSuccess() {
159-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/authorizer-principal", "GET")
183+
AwsProxyRequest request = getRequestBuilder("/echo/authorizer-principal", "GET")
160184
.json()
161185
.authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID)
162186
.build();
163187

164188
AwsProxyResponse output = handler.proxy(request, lambdaContext);
165-
assertEquals(200, output.getStatusCode());
166-
assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type"));
189+
if (!isAlb) {
190+
assertEquals(200, output.getStatusCode());
191+
assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type"));
192+
validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID);
193+
}
194+
167195

168-
validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID);
169196
}
170197

171198
@Test
172199
public void authorizer_securityContext_customAuthorizerContextSuccess() {
173-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/authorizer-context", "GET")
200+
AwsProxyRequest request = getRequestBuilder("/echo/authorizer-context", "GET")
174201
.json()
175202
.authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID)
176203
.authorizerContextValue(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE)
@@ -186,15 +213,15 @@ public void authorizer_securityContext_customAuthorizerContextSuccess() {
186213

187214
@Test
188215
public void errors_unknownRoute_expect404() {
189-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/test33", "GET").build();
216+
AwsProxyRequest request = getRequestBuilder("/echo/test33", "GET").build();
190217

191218
AwsProxyResponse output = handler.proxy(request, lambdaContext);
192219
assertEquals(404, output.getStatusCode());
193220
}
194221

195222
@Test
196223
public void error_contentType_invalidContentType() {
197-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST")
224+
AwsProxyRequest request = getRequestBuilder("/echo/json-body", "POST")
198225
.header("Content-Type", "application/octet-stream")
199226
.body("asdasdasd")
200227
.build();
@@ -205,7 +232,7 @@ public void error_contentType_invalidContentType() {
205232

206233
@Test
207234
public void error_statusCode_methodNotAllowed() {
208-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "POST")
235+
AwsProxyRequest request = getRequestBuilder("/echo/status-code", "POST")
209236
.json()
210237
.queryString("status", "201")
211238
.build();
@@ -218,7 +245,7 @@ public void error_statusCode_methodNotAllowed() {
218245
public void responseBody_responseWriter_validBody() throws JsonProcessingException {
219246
SingleValueModel singleValueModel = new SingleValueModel();
220247
singleValueModel.setValue(CUSTOM_HEADER_VALUE);
221-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST")
248+
AwsProxyRequest request = getRequestBuilder("/echo/json-body", "POST")
222249
.json()
223250
.body(objectMapper.writeValueAsString(singleValueModel))
224251
.build();
@@ -232,7 +259,7 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti
232259

233260
@Test
234261
public void statusCode_responseStatusCode_customStatusCode() {
235-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "GET")
262+
AwsProxyRequest request = getRequestBuilder("/echo/status-code", "GET")
236263
.json()
237264
.queryString("status", "201")
238265
.build();
@@ -243,7 +270,7 @@ public void statusCode_responseStatusCode_customStatusCode() {
243270

244271
@Test
245272
public void base64_binaryResponse_base64Encoding() {
246-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/binary", "GET").build();
273+
AwsProxyRequest request = getRequestBuilder("/echo/binary", "GET").build();
247274

248275
AwsProxyResponse response = handler.proxy(request, lambdaContext);
249276
assertNotNull(response.getBody());
@@ -252,7 +279,7 @@ public void base64_binaryResponse_base64Encoding() {
252279

253280
@Test
254281
public void exception_mapException_mapToNotImplemented() {
255-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/exception", "GET").build();
282+
AwsProxyRequest request = getRequestBuilder("/echo/exception", "GET").build();
256283

257284
AwsProxyResponse response = handler.proxy(request, lambdaContext);
258285
assertNotNull(response.getBody());
@@ -262,7 +289,7 @@ public void exception_mapException_mapToNotImplemented() {
262289

263290
@Test
264291
public void stripBasePath_route_shouldRouteCorrectly() {
265-
AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET")
292+
AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET")
266293
.json()
267294
.queryString("status", "201")
268295
.build();
@@ -274,7 +301,7 @@ public void stripBasePath_route_shouldRouteCorrectly() {
274301

275302
@Test
276303
public void stripBasePath_route_shouldReturn404WithStageAsContext() {
277-
AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET")
304+
AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET")
278305
.stage("prod")
279306
.json()
280307
.queryString("status", "201")
@@ -289,7 +316,7 @@ public void stripBasePath_route_shouldReturn404WithStageAsContext() {
289316

290317
@Test
291318
public void stripBasePath_route_shouldReturn404() {
292-
AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET")
319+
AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET")
293320
.json()
294321
.queryString("status", "201")
295322
.build();
@@ -301,7 +328,7 @@ public void stripBasePath_route_shouldReturn404() {
301328

302329
@Test
303330
public void securityContext_injectPrincipal_expectPrincipalName() {
304-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/security-context", "GET")
331+
AwsProxyRequest request = getRequestBuilder("/echo/security-context", "GET")
305332
.authorizerPrincipal(USER_PRINCIPAL).build();
306333

307334
AwsProxyResponse resp = handler.proxy(request, lambdaContext);
@@ -311,7 +338,7 @@ public void securityContext_injectPrincipal_expectPrincipalName() {
311338

312339
@Test
313340
public void emptyStream_putNullBody_expectPutToSucceed() {
314-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/empty-stream/" + CUSTOM_HEADER_KEY + "/test/2", "PUT")
341+
AwsProxyRequest request = getRequestBuilder("/echo/empty-stream/" + CUSTOM_HEADER_KEY + "/test/2", "PUT")
315342
.nullBody()
316343
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
317344
.build();
@@ -323,7 +350,7 @@ public void emptyStream_putNullBody_expectPutToSucceed() {
323350
@Test
324351
public void refererHeader_headerParam_expectCorrectInjection() {
325352
String refererValue = "test-referer";
326-
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/referer-header", "GET")
353+
AwsProxyRequest request = getRequestBuilder("/echo/referer-header", "GET")
327354
.nullBody()
328355
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
329356
.header("Referer", refererValue)

0 commit comments

Comments
 (0)