Skip to content

Commit d074fae

Browse files
authored
Merge pull request #77 from awslabs/servlet-improvements
Servlet improvements merge for 0.8 release
2 parents 55aae8d + a1b0798 commit d074fae

File tree

34 files changed

+913
-90
lines changed

34 files changed

+913
-90
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyRe
9393
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
9494
if (!initialized) {
9595
defineRoutes();
96+
// it's important to call the awaitInitialization method not to run into race
97+
// conditions as routes are loaded asynchronously
98+
Spark.awaitInitialization();
9699
initialized = true;
97100
}
98101
return handler.proxy(awsProxyRequest, context);

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/exceptions/InvalidRequestEventException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ public class InvalidRequestEventException extends Exception {
2222
public InvalidRequestEventException(String message, Exception e) {
2323
super(message, e);
2424
}
25+
26+
public InvalidRequestEventException(String message) {
27+
super(message);
28+
}
2529
}

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

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package com.amazonaws.serverless.proxy.internal.jaxrs;
1414

1515
import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest;
16+
import com.amazonaws.serverless.proxy.internal.model.CognitoAuthorizerClaims;
1617
import com.amazonaws.services.lambda.runtime.Context;
1718

1819
import javax.ws.rs.core.SecurityContext;
@@ -61,26 +62,33 @@ public AwsProxySecurityContext(final Context lambdaContext, final AwsProxyReques
6162
//-------------------------------------------------------------
6263

6364
public Principal getUserPrincipal() {
64-
return () -> {
65-
if (getAuthenticationScheme() == null) {
66-
return null;
67-
}
68-
69-
if (getAuthenticationScheme().equals(AUTH_SCHEME_CUSTOM)) {
70-
return event.getRequestContext().getAuthorizer().getPrincipalId();
71-
} else if (getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) {
72-
// if we received credentials from Cognito Federated Identities then we return the identity id
73-
if (event.getRequestContext().getIdentity().getCognitoIdentityId() != null) {
74-
return event.getRequestContext().getIdentity().getCognitoIdentityId();
75-
} else { // otherwise the user arn from the credentials
76-
return event.getRequestContext().getIdentity().getUserArn();
65+
if (getAuthenticationScheme() == null) {
66+
return () -> null;
67+
}
68+
69+
if (getAuthenticationScheme().equals(AUTH_SCHEME_CUSTOM) || getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) {
70+
return () -> {
71+
if (getAuthenticationScheme().equals(AUTH_SCHEME_CUSTOM)) {
72+
return event.getRequestContext().getAuthorizer().getPrincipalId();
73+
} else if (getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) {
74+
// if we received credentials from Cognito Federated Identities then we return the identity id
75+
if (event.getRequestContext().getIdentity().getCognitoIdentityId() != null) {
76+
return event.getRequestContext().getIdentity().getCognitoIdentityId();
77+
} else { // otherwise the user arn from the credentials
78+
return event.getRequestContext().getIdentity().getUserArn();
79+
}
7780
}
78-
} else if (getAuthenticationScheme().equals(AUTH_SCHEME_COGNITO_POOL)) {
79-
return event.getRequestContext().getAuthorizer().getClaims().getSubject();
80-
}
8181

82-
return null;
83-
};
82+
// return null if we couldn't find a valid scheme
83+
return null;
84+
};
85+
}
86+
87+
if (getAuthenticationScheme().equals(AUTH_SCHEME_COGNITO_POOL)) {
88+
return new CognitoUserPoolPrincipal(event.getRequestContext().getAuthorizer().getClaims());
89+
}
90+
91+
throw new RuntimeException("Cannot recognize authorization scheme in event");
8492
}
8593

8694

@@ -105,4 +113,27 @@ public String getAuthenticationScheme() {
105113
return null;
106114
}
107115
}
116+
117+
118+
/**
119+
* Custom object for request authorized with a Cognito User Pool authorizer. By casting the Principal
120+
* object to this you can extract the Claims object included in the token.
121+
*/
122+
public class CognitoUserPoolPrincipal implements Principal {
123+
124+
private CognitoAuthorizerClaims claims;
125+
126+
CognitoUserPoolPrincipal(CognitoAuthorizerClaims c) {
127+
claims = c;
128+
}
129+
130+
@Override
131+
public String getName() {
132+
return claims.getSubject();
133+
}
134+
135+
public CognitoAuthorizerClaims getClaims() {
136+
return claims;
137+
}
138+
}
108139
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.fasterxml.jackson.annotation.JsonIgnore;
1616
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
1717

18+
import java.util.HashMap;
1819
import java.util.Map;
1920

2021
/**
@@ -31,7 +32,7 @@ public class AwsProxyRequest {
3132
private String resource;
3233
private ApiGatewayRequestContext requestContext;
3334
private Map<String, String> queryStringParameters;
34-
private Map<String, String> headers;
35+
private Map<String, String> headers = new HashMap<>(); // avoid NPE
3536
private Map<String, String> pathParameters;
3637
private String httpMethod;
3738
private Map<String, String> stageVariables;
@@ -105,7 +106,11 @@ public Map<String, String> getHeaders() {
105106

106107

107108
public void setHeaders(Map<String, String> headers) {
108-
this.headers = headers;
109+
if (null != headers) {
110+
this.headers = headers;
111+
} else {
112+
this.headers.clear();
113+
}
109114
}
110115

111116

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@
1313
package com.amazonaws.serverless.proxy.internal.model;
1414

1515

16+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
17+
import com.fasterxml.jackson.annotation.JsonAnySetter;
1618
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
1719
import com.fasterxml.jackson.annotation.JsonProperty;
1820

1921
import java.time.format.DateTimeFormatter;
22+
import java.util.HashMap;
23+
import java.util.Map;
2024

2125

2226
/**
@@ -44,6 +48,8 @@ public class CognitoAuthorizerClaims {
4448
// Variables - Private
4549
//-------------------------------------------------------------
4650

51+
private Map<String, String> claims = new HashMap<>();
52+
4753
@JsonProperty(value = "sub")
4854
private String subject;
4955
@JsonProperty(value = "aud")
@@ -69,6 +75,16 @@ public class CognitoAuthorizerClaims {
6975
// Methods - Getter/Setter
7076
//-------------------------------------------------------------
7177

78+
@JsonAnyGetter
79+
public String getClaim(String claim) {
80+
return claims.get(claim);
81+
}
82+
83+
@JsonAnySetter
84+
public void setClaim(String claim, String value) {
85+
claims.put(claim, value);
86+
}
87+
7288
public String getSubject() { return this.subject; }
7389

7490

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

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

15+
import com.amazonaws.serverless.proxy.internal.RequestReader;
16+
import com.amazonaws.serverless.proxy.internal.model.ApiGatewayRequestContext;
1517
import com.amazonaws.serverless.proxy.internal.model.ContainerConfig;
1618
import com.amazonaws.services.lambda.runtime.Context;
1719

@@ -24,6 +26,7 @@
2426
import javax.servlet.http.Cookie;
2527
import javax.servlet.http.HttpServletRequest;
2628
import javax.servlet.http.HttpSession;
29+
import javax.servlet.http.HttpSessionContext;
2730
import java.io.UnsupportedEncodingException;
2831
import java.net.URLDecoder;
2932
import java.net.URLEncoder;
@@ -68,6 +71,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
6871
private Context lambdaContext;
6972
private Map<String, Object> attributes;
7073
private ServletContext servletContext;
74+
private AwsHttpSession session;
7175

7276
protected DispatcherType dispatcherType;
7377

@@ -101,13 +105,17 @@ public String getRequestedSessionId() {
101105

102106
@Override
103107
public HttpSession getSession(boolean b) {
104-
return null;
108+
if (b && null == this.session) {
109+
ApiGatewayRequestContext requestContext = (ApiGatewayRequestContext) getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY);
110+
this.session = new AwsHttpSession(requestContext.getRequestId());
111+
}
112+
return this.session;
105113
}
106114

107115

108116
@Override
109117
public HttpSession getSession() {
110-
return null;
118+
return this.session;
111119
}
112120

113121

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class AwsHttpServletResponse
5353
private int statusCode;
5454
private String statusMessage;
5555
private String responseBody;
56+
private PrintWriter writer;
5657
private ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
5758
private CountDownLatch writersCountDownLatch;
5859
private AwsHttpServletRequest request;
@@ -316,7 +317,10 @@ public void close()
316317

317318
@Override
318319
public PrintWriter getWriter() throws IOException {
319-
return new PrintWriter(bodyOutputStream);
320+
if (null == writer) {
321+
writer = new PrintWriter(bodyOutputStream);
322+
}
323+
return writer;
320324
}
321325

322326

@@ -358,7 +362,11 @@ public int getBufferSize() {
358362

359363
@Override
360364
public void flushBuffer() throws IOException {
365+
if (null != writer) {
366+
writer.flush();
367+
}
361368
responseBody = new String(bodyOutputStream.toByteArray());
369+
log.debug("Response buffer flushed with {} bytes, latch={}", responseBody.length(), writersCountDownLatch.getCount());
362370
isCommitted = true;
363371
writersCountDownLatch.countDown();
364372
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.amazonaws.serverless.proxy.internal.servlet;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import javax.servlet.ServletContext;
7+
import javax.servlet.http.HttpSession;
8+
import javax.servlet.http.HttpSessionContext;
9+
import java.util.Enumeration;
10+
11+
public class AwsHttpSession implements HttpSession {
12+
13+
private static final Logger log = LoggerFactory.getLogger(AwsHttpSession.class);
14+
private String id;
15+
16+
/**
17+
* @param id API gateway request ID.
18+
*/
19+
public AwsHttpSession(String id) {
20+
if (null == id) {
21+
throw new RuntimeException("HTTP session id (from request ID) cannot be null");
22+
}
23+
log.debug("Creating session " + id);
24+
this.id = id;
25+
}
26+
27+
@Override
28+
public long getCreationTime() {
29+
return 0;
30+
}
31+
32+
@Override
33+
public String getId() {
34+
return id;
35+
}
36+
37+
@Override
38+
public long getLastAccessedTime() {
39+
return 0;
40+
}
41+
42+
@Override
43+
public ServletContext getServletContext() {
44+
return null;
45+
}
46+
47+
@Override
48+
public void setMaxInactiveInterval(int interval) {
49+
50+
}
51+
52+
@Override
53+
public int getMaxInactiveInterval() {
54+
return 0;
55+
}
56+
57+
@Override
58+
public HttpSessionContext getSessionContext() {
59+
return null;
60+
}
61+
62+
@Override
63+
public Object getAttribute(String name) {
64+
return null;
65+
}
66+
67+
@Override
68+
public Object getValue(String name) {
69+
return null;
70+
}
71+
72+
@Override
73+
public Enumeration<String> getAttributeNames() {
74+
return null;
75+
}
76+
77+
@Override
78+
public String[] getValueNames() {
79+
return new String[0];
80+
}
81+
82+
@Override
83+
public void setAttribute(String name, Object value) {
84+
85+
}
86+
87+
@Override
88+
public void putValue(String name, Object value) {
89+
90+
}
91+
92+
@Override
93+
public void removeAttribute(String name) {
94+
95+
}
96+
97+
@Override
98+
public void removeValue(String name) {
99+
100+
}
101+
102+
@Override
103+
public void invalidate() {
104+
105+
}
106+
107+
@Override
108+
public boolean isNew() {
109+
return false;
110+
}
111+
}

0 commit comments

Comments
 (0)