Skip to content

Commit 211224e

Browse files
authored
Merge pull request #121 from awslabs/core
Merging in preparation for 1.0 release
2 parents 7442007 + da248d9 commit 211224e

File tree

13 files changed

+280
-151
lines changed

13 files changed

+280
-151
lines changed

aws-serverless-java-container-core/pom.xml

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -65,42 +65,12 @@
6565
</dependency>
6666
</dependencies>
6767

68-
<reporting>
69-
<plugins>
70-
<plugin>
71-
<groupId>org.codehaus.mojo</groupId>
72-
<artifactId>findbugs-maven-plugin</artifactId>
73-
<version>3.0.5</version>
74-
<configuration>
75-
<!--
76-
Enables analysis which takes more memory but finds more bugs.
77-
If you run out of memory, changes the value of the effort element
78-
to 'low'.
79-
-->
80-
<effort>Max</effort>
81-
<!-- Reports all bugs (other values are medium and max) -->
82-
<threshold>Low</threshold>
83-
<!-- Produces XML report -->
84-
<xmlOutput>true</xmlOutput>
85-
86-
<plugins>
87-
<plugin>
88-
<groupId>com.h3xstream.findsecbugs</groupId>
89-
<artifactId>findsecbugs-plugin</artifactId>
90-
<version>1.7.1</version>
91-
</plugin>
92-
</plugins>
93-
</configuration>
94-
</plugin>
95-
</plugins>
96-
</reporting>
97-
9868
<build>
9969
<plugins>
10070
<plugin>
101-
<groupId>org.codehaus.mojo</groupId>
102-
<artifactId>findbugs-maven-plugin</artifactId>
103-
<version>3.0.5</version>
71+
<groupId>com.github.spotbugs</groupId>
72+
<artifactId>spotbugs-maven-plugin</artifactId>
73+
<version>3.1.1</version>
10474
<configuration>
10575
<!--
10676
Enables analysis which takes more memory but finds more bugs.
@@ -113,7 +83,7 @@
11383
<!-- Produces XML report -->
11484
<xmlOutput>true</xmlOutput>
11585
<!-- Configures the directory in which the XML report is created -->
116-
<findbugsXmlOutputDirectory>${project.build.directory}/findbugs</findbugsXmlOutputDirectory>
86+
<spotbugsXmlOutputDirectory>${project.build.directory}/spotbugs</spotbugsXmlOutputDirectory>
11787

11888
<plugins>
11989
<plugin>
@@ -125,7 +95,7 @@
12595
</configuration>
12696
<executions>
12797
<!--
128-
Ensures that FindBugs inspects source code when project is compiled.
98+
Ensures that SpotBug inspects source code when project is compiled.
12999
-->
130100
<execution>
131101
<id>analyze-compile</id>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.amazonaws.serverless.proxy;
2+
3+
4+
import javax.ws.rs.core.SecurityContext;
5+
6+
7+
/**
8+
* Implementations of the log formatter interface are used by {@link com.amazonaws.serverless.proxy.internal.LambdaContainerHandler} class to log each request
9+
* processed in the container. You can set the log formatter using the {@link com.amazonaws.serverless.proxy.internal.LambdaContainerHandler#setLogFormatter(LogFormatter)}
10+
* method. The servlet implementation of the container ({@link com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler} includes a
11+
* default log formatter that produces Apache combined logs. {@link com.amazonaws.serverless.proxy.internal.servlet.ApacheCombinedServletLogFormatter}.
12+
* @param <ContainerRequestType> The request type used by the underlying framework
13+
* @param <ContainerResponseType> The response type produced by the underlying framework
14+
*/
15+
public interface LogFormatter<ContainerRequestType, ContainerResponseType> {
16+
/**
17+
* The format method is called by the container handler to produce the log line that should be written to the logs.
18+
* @param req The incoming request
19+
* @param res The completed response
20+
* @param ctx The security context produced based on the request
21+
* @return The log line
22+
*/
23+
String format(ContainerRequestType req, ContainerResponseType res, SecurityContext ctx);
24+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
package com.amazonaws.serverless.proxy.internal;
1414

1515

16+
import com.amazonaws.serverless.proxy.LogFormatter;
17+
import com.amazonaws.serverless.proxy.internal.servlet.ApacheCombinedServletLogFormatter;
1618
import com.amazonaws.serverless.proxy.model.ContainerConfig;
1719
import com.amazonaws.serverless.proxy.ExceptionHandler;
1820
import com.amazonaws.serverless.proxy.RequestReader;
@@ -56,10 +58,12 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
5658
private ExceptionHandler<ResponseType> exceptionHandler;
5759

5860
protected Context lambdaContext;
61+
protected LogFormatter<ContainerRequestType, ContainerResponseType> logFormatter;
5962

6063
private Logger log = LoggerFactory.getLogger(LambdaContainerHandler.class);
6164

6265

66+
6367
//-------------------------------------------------------------
6468
// Variables - Private - Static
6569
//-------------------------------------------------------------
@@ -119,6 +123,15 @@ public void stripBasePath(String basePath) {
119123
config.setServiceBasePath(basePath);
120124
}
121125

126+
/**
127+
* Sets the formatter used to log request data in CloudWatch. By default this is set to use an Apache
128+
* combined log format based on the servlet request and response object {@link ApacheCombinedServletLogFormatter}.
129+
* @param formatter The log formatter object
130+
*/
131+
public void setLogFormatter(LogFormatter<ContainerRequestType, ContainerResponseType> formatter) {
132+
this.logFormatter = formatter;
133+
}
134+
122135

123136
/**
124137
* Proxies requests to the underlying container given the incoming Lambda request. This method returns a populated
@@ -140,6 +153,10 @@ public ResponseType proxy(RequestType request, Context context) {
140153

141154
latch.await();
142155

156+
if (logFormatter != null) {
157+
log.info(SecurityUtils.crlf(logFormatter.format(containerRequest, containerResponse, securityContext)));
158+
}
159+
143160
return responseWriter.writeResponse(containerResponse, context);
144161
} catch (Exception e) {
145162
log.error("Error while handling request", e);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.amazonaws.serverless.proxy.internal.servlet;
2+
3+
4+
import com.amazonaws.serverless.proxy.LogFormatter;
5+
import com.amazonaws.serverless.proxy.model.ApiGatewayRequestContext;
6+
7+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8+
9+
import javax.servlet.http.HttpServletRequest;
10+
import javax.servlet.http.HttpServletResponse;
11+
import javax.ws.rs.core.SecurityContext;
12+
13+
import java.text.SimpleDateFormat;
14+
import java.time.Instant;
15+
import java.util.Calendar;
16+
import java.util.Date;
17+
import java.util.Locale;
18+
19+
import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_CONTEXT_PROPERTY;
20+
21+
22+
/**
23+
* Default implementation of the log formatter. Based on an <code>HttpServletRequest</code> and <code>HttpServletResponse</code> implementations produced
24+
* a log line in the Apache combined log format: https://httpd.apache.org/docs/2.4/logs.html
25+
* @param <ContainerRequestType> An implementation of <code>HttpServletRequest</code>
26+
* @param <ContainerResponseType> An implementation of <code>HttpServletResponse</code>
27+
*/
28+
public class ApacheCombinedServletLogFormatter<ContainerRequestType extends HttpServletRequest, ContainerResponseType extends HttpServletResponse>
29+
implements LogFormatter<ContainerRequestType, ContainerResponseType> {
30+
SimpleDateFormat dateFormat;
31+
32+
public ApacheCombinedServletLogFormatter() {
33+
dateFormat = new SimpleDateFormat("[dd/MM/yyyy:hh:mm:ss Z]");
34+
}
35+
36+
@Override
37+
@SuppressFBWarnings({ "SERVLET_HEADER_REFERER", "SERVLET_HEADER_USER_AGENT" })
38+
public String format(ContainerRequestType servletRequest, ContainerResponseType servletResponse, SecurityContext ctx) {
39+
//LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined
40+
StringBuilder logLineBuilder = new StringBuilder();
41+
ApiGatewayRequestContext gatewayContext = (ApiGatewayRequestContext)servletRequest.getAttribute(API_GATEWAY_CONTEXT_PROPERTY);
42+
43+
// %h
44+
logLineBuilder.append(servletRequest.getRemoteAddr());
45+
logLineBuilder.append(" ");
46+
47+
// %l
48+
if (gatewayContext != null) {
49+
if (gatewayContext.getIdentity().getUserArn() != null) {
50+
logLineBuilder.append(gatewayContext.getIdentity().getUserArn());
51+
} else {
52+
logLineBuilder.append("-");
53+
}
54+
} else {
55+
logLineBuilder.append("-");
56+
}
57+
logLineBuilder.append(" ");
58+
59+
// %u
60+
if (ctx != null && ctx.getUserPrincipal().getName() != null) {
61+
logLineBuilder.append(ctx.getUserPrincipal().getName());
62+
logLineBuilder.append(" ");
63+
}
64+
65+
66+
// %t
67+
if (gatewayContext != null) {
68+
logLineBuilder.append(dateFormat.format(Date.from(Instant.ofEpochMilli(gatewayContext.getRequestTimeEpoch()))));
69+
} else {
70+
logLineBuilder.append(dateFormat.format(Calendar.getInstance().getTime()));
71+
}
72+
logLineBuilder.append(" ");
73+
74+
// %r
75+
logLineBuilder.append("\"");
76+
logLineBuilder.append(servletRequest.getMethod().toUpperCase(Locale.ENGLISH));
77+
logLineBuilder.append(" ");
78+
logLineBuilder.append(servletRequest.getPathInfo());
79+
logLineBuilder.append(" ");
80+
logLineBuilder.append(servletRequest.getProtocol());
81+
logLineBuilder.append("\" ");
82+
83+
// %>s
84+
logLineBuilder.append(servletResponse.getStatus());
85+
logLineBuilder.append(" ");
86+
87+
// %b
88+
if (servletResponse instanceof AwsHttpServletResponse) {
89+
AwsHttpServletResponse awsResponse = (AwsHttpServletResponse)servletResponse;
90+
if (awsResponse.getAwsResponseBodyBytes().length > 0) {
91+
logLineBuilder.append(awsResponse.getAwsResponseBodyBytes().length);
92+
} else {
93+
logLineBuilder.append("-");
94+
}
95+
} else {
96+
logLineBuilder.append("-");
97+
}
98+
logLineBuilder.append(" ");
99+
100+
// \"%{Referer}i\"
101+
logLineBuilder.append("\"");
102+
if (servletRequest.getHeader("referer") != null) {
103+
logLineBuilder.append(servletRequest.getHeader("referer"));
104+
} else {
105+
logLineBuilder.append("-");
106+
}
107+
logLineBuilder.append("\" ");
108+
109+
// \"%{User-agent}i\"
110+
logLineBuilder.append("\"");
111+
if (servletRequest.getHeader("user-agent") != null) {
112+
logLineBuilder.append(servletRequest.getHeader("user-agent"));
113+
} else {
114+
logLineBuilder.append("-");
115+
}
116+
logLineBuilder.append("\" ");
117+
118+
logLineBuilder.append("combined");
119+
120+
121+
return logLineBuilder.toString();
122+
}
123+
}

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public String getRequestedSessionId() {
107107

108108
@Override
109109
public HttpSession getSession(boolean b) {
110-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
110+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
111111
if (b && null == this.session) {
112112
ApiGatewayRequestContext requestContext = (ApiGatewayRequestContext) getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY);
113113
this.session = new AwsHttpSession(requestContext.getRequestId());
@@ -118,43 +118,43 @@ public HttpSession getSession(boolean b) {
118118

119119
@Override
120120
public HttpSession getSession() {
121-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
121+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
122122
return this.session;
123123
}
124124

125125

126126
@Override
127127
public String changeSessionId() {
128-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
128+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
129129
return null;
130130
}
131131

132132

133133
@Override
134134
public boolean isRequestedSessionIdValid() {
135-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
135+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
136136
return false;
137137
}
138138

139139

140140
@Override
141141
public boolean isRequestedSessionIdFromCookie() {
142-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
142+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
143143
return false;
144144
}
145145

146146

147147
@Override
148148
public boolean isRequestedSessionIdFromURL() {
149-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
149+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
150150
return false;
151151
}
152152

153153

154154
@Override
155155
@Deprecated
156156
public boolean isRequestedSessionIdFromUrl() {
157-
log.warn("Trying to access session. Lambda functions are stateless and should not rely on the session");
157+
log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session");
158158
return false;
159159
}
160160

@@ -275,7 +275,7 @@ protected Cookie[] parseCookieHeaderValue(String headerValue) {
275275

276276
return parsedHeaders.stream()
277277
.filter(e -> e.getKey() != null)
278-
.map(e -> new Cookie(e.getKey(), e.getValue()))
278+
.map(e -> new Cookie(SecurityUtils.crlf(e.getKey()), SecurityUtils.crlf(e.getValue())))
279279
.toArray(Cookie[]::new);
280280
}
281281

@@ -304,7 +304,7 @@ protected String generateQueryString(Map<String, String> parameters) {
304304
newValue = URLEncoder.encode(newValue, StandardCharsets.UTF_8.name());
305305
}
306306
} catch (UnsupportedEncodingException e) {
307-
log.error("Could not URLEncode: " + newKey, e);
307+
log.error(SecurityUtils.crlf("Could not URLEncode: " + newKey), e);
308308

309309
}
310310
return newKey + "=" + newValue;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import java.io.IOException;
3636

37+
3738
/**
3839
* Abstract extension of the code <code>LambdaContainerHandler</code> object that adds protected variables for the
3940
* <code>ServletContext</code> and <code>FilterChainManager</code>. This object should be extended by the framework-specific
@@ -73,6 +74,8 @@ protected AwsLambdaServletContainerHandler(RequestReader<RequestType, ContainerR
7374
SecurityContextWriter<RequestType> securityContextWriter,
7475
ExceptionHandler<ResponseType> exceptionHandler) {
7576
super(requestReader, responseWriter, securityContextWriter, exceptionHandler);
77+
// set the default log formatter for servlet implementations
78+
setLogFormatter(new ApacheCombinedServletLogFormatter<>());
7679
}
7780

7881
//-------------------------------------------------------------

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,7 @@ public Map<String, String[]> getParameterMap() {
501501

502502
@Override
503503
public String getProtocol() {
504-
// TODO: We should have a cloudfront protocol header
505-
return null;
504+
return request.getRequestContext().getProtocol();
506505
}
507506

508507

@@ -621,6 +620,14 @@ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se
621620

622621

623622
private String getHeaderCaseInsensitive(String key) {
623+
// special cases for referer and user agent headers
624+
if ("referer".equals(key.toLowerCase(Locale.ENGLISH))) {
625+
return request.getRequestContext().getIdentity().getCaller();
626+
}
627+
if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
628+
return request.getRequestContext().getIdentity().getUserAgent();
629+
}
630+
624631
if (request.getHeaders() == null) {
625632
return null;
626633
}

0 commit comments

Comments
 (0)