diff --git a/jersey-servlet-example-annotations/pom.xml b/jersey-servlet-example-annotations/pom.xml index 2c96779..680c602 100644 --- a/jersey-servlet-example-annotations/pom.xml +++ b/jersey-servlet-example-annotations/pom.xml @@ -103,7 +103,7 @@ com.moesif.servlet moesif-servlet - 1.8.1 + 1.8.3 diff --git a/jersey-servlet-example-annotations/src/main/java/com/moesif/servlet/jersey/example/MoesifServletFilter.java b/jersey-servlet-example-annotations/src/main/java/com/moesif/servlet/jersey/example/MoesifServletFilter.java index 559b887..1bd2756 100644 --- a/jersey-servlet-example-annotations/src/main/java/com/moesif/servlet/jersey/example/MoesifServletFilter.java +++ b/jersey-servlet-example-annotations/src/main/java/com/moesif/servlet/jersey/example/MoesifServletFilter.java @@ -15,7 +15,7 @@ urlPatterns ="/*", initParams = { @WebInitParam(name = "application-id", - value = "Your Moesif Application Id"), + value = "eyJhcHAiOiIxOTg6NzQ0IiwidmVyIjoiMi4xIiwib3JnIjoiNjQwOjEyOCIsImlhdCI6MTczMDQxOTIwMH0.duHFw2GFPGinL-RL_Bv2QyvxNzwO2oqtxnieO22P2B0"), @WebInitParam(name = "logBody", value = "true") } ) diff --git a/moesif-servlet/pom.xml b/moesif-servlet/pom.xml index 6d1a794..5a86f93 100644 --- a/moesif-servlet/pom.xml +++ b/moesif-servlet/pom.xml @@ -43,8 +43,6 @@ 1.8 2.17.0 3.4 - 1.1.3 - 1.8 @@ -69,16 +67,6 @@ commons-io ${commons-io.version} - - org.apache.logging.log4j - log4j-api - 2.17.2 - - - org.apache.logging.log4j - log4j-core - 2.17.2 - @@ -87,12 +75,6 @@ 4.13.2 test - - org.assertj - assertj-core - 3.23.1 - test - org.mockito mockito-core @@ -115,8 +97,8 @@ maven-compiler-plugin 3.10.1 - ${java.version} - ${java.version} + ${maven.compiler.source} + ${maven.compiler.target} @@ -161,7 +143,7 @@ sign-artifacts verify - sign + diff --git a/moesif-servlet/src/main/java/com/moesif/servlet/MoesifConfiguration.java b/moesif-servlet/src/main/java/com/moesif/servlet/MoesifConfiguration.java index 3c9f66c..a989915 100644 --- a/moesif-servlet/src/main/java/com/moesif/servlet/MoesifConfiguration.java +++ b/moesif-servlet/src/main/java/com/moesif/servlet/MoesifConfiguration.java @@ -51,8 +51,6 @@ public EventModel maskContent(EventModel eventModel) { public int queueSize = 1000000; // maximum queue capacity to hold events. public int retry = 0; // how many times to retry, if fails to post events.ß public int updateConfigTime = 5*60; // in seconds - time to update app config periodically. - public int maxBodySize = 450 * 1024; // in bytes - max body size to capture. - public boolean logBody = true; @Deprecated public String getTags(HttpServletRequest request, HttpServletResponse response) { diff --git a/moesif-servlet/src/main/java/com/moesif/servlet/MoesifFilter.java b/moesif-servlet/src/main/java/com/moesif/servlet/MoesifFilter.java index 469e869..01f5175 100644 --- a/moesif-servlet/src/main/java/com/moesif/servlet/MoesifFilter.java +++ b/moesif-servlet/src/main/java/com/moesif/servlet/MoesifFilter.java @@ -1,10 +1,12 @@ package com.moesif.servlet; +import com.mashape.unirest.request.body.Body; import com.moesif.api.BodyParser; import com.moesif.api.IpAddress; import com.moesif.api.MoesifAPIClient; import com.moesif.api.controllers.APIController; import com.moesif.api.models.*; +import com.moesif.servlet.wrappers.BodyHandler; import com.moesif.servlet.wrappers.LoggingHttpServletRequestWrapper; import com.moesif.servlet.wrappers.LoggingHttpServletResponseWrapper; import org.apache.commons.lang3.StringUtils; @@ -25,6 +27,7 @@ public class MoesifFilter implements Filter { private MoesifConfiguration config; private MoesifAPIClient moesifApi; private boolean debug; + private boolean logBody; private BatchProcessor batchProcessor = null; // Manages queue & provides a taskRunner to send events in batches. private int sendBatchJobAliveCounter = 0; // counter to check scheduled job is alive or not. @@ -126,7 +129,8 @@ public void setDebug(boolean debug) { * @param logBody boolean */ public void setLogBody(boolean logBody) { - this.config.logBody = logBody; + this.logBody = logBody; + BodyHandler.logBody = logBody; } /** @@ -365,8 +369,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha return; } - LoggingHttpServletRequestWrapper requestWrapper = new LoggingHttpServletRequestWrapper(httpRequest, config); - LoggingHttpServletResponseWrapper responseWrapper = new LoggingHttpServletResponseWrapper(httpResponse, config); + LoggingHttpServletRequestWrapper requestWrapper = new LoggingHttpServletRequestWrapper(httpRequest); + LoggingHttpServletResponseWrapper responseWrapper = new LoggingHttpServletResponseWrapper(httpResponse); // Initialize transactionId @@ -465,13 +469,16 @@ private EventRequestModel getEventRequestModel(LoggingHttpServletRequestWrapper } - if (this.config.logBody) { + if (this.logBody) { String content = requestWrapper.getContent(); if (content != null && !content.isEmpty()) { BodyParser.BodyWrapper bodyWrapper = BodyParser.parseBody(requestWrapper.getHeaders(), content); eventRequestBuilder.body(bodyWrapper.body); eventRequestBuilder.transferEncoding(bodyWrapper.transferEncoding); } + if (requestWrapper.bodySkipped) { + eventRequestBuilder.body(BodyHandler.getLargeBodyError(requestWrapper.contentLength)); + } } return eventRequestBuilder.build(); @@ -485,13 +492,16 @@ private EventResponseModel getEventResponseModel(LoggingHttpServletResponseWrapp .headers(responseWrapper.getHeaders()); - if (this.config.logBody) { + if (this.logBody) { String content = responseWrapper.getContent(); if (content != null && !content.isEmpty()) { BodyParser.BodyWrapper bodyWrapper = BodyParser.parseBody(responseWrapper.getHeaders(), content); eventResponseBuilder.body(bodyWrapper.body); eventResponseBuilder.transferEncoding(bodyWrapper.transferEncoding); } + if (responseWrapper.bodySkipped) { + eventResponseBuilder.body(BodyHandler.getLargeBodyError(responseWrapper.contentLength)); + } } return eventResponseBuilder.build(); diff --git a/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/BodyHandler.java b/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/BodyHandler.java new file mode 100644 index 0000000..d93ad94 --- /dev/null +++ b/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/BodyHandler.java @@ -0,0 +1,32 @@ +package com.moesif.servlet.wrappers; +import org.apache.commons.io.output.ByteArrayOutputStream; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class BodyHandler { + + public static boolean logBody = true; + public static final int MAX_BODY_SIZE = 10; // Move to a common utility class + + public static String encodeContent(byte[] content, String encoding) { + try { + return new String(content, encoding != null ? encoding : StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + return "[UNSUPPORTED ENCODING]"; + } + } + + // a method that returns a simple java map representing an error message for large body meant to be serialized into json + public static Map getLargeBodyError(long contentLength) { + Map error = new HashMap<>(); + error.put("msg", "request.body.length " + contentLength + " exceeded requestMaxBodySize of " + MAX_BODY_SIZE + " bytes"); + return error; + } + +} diff --git a/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletRequestWrapper.java b/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletRequestWrapper.java index 248800f..9491cea 100644 --- a/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletRequestWrapper.java +++ b/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletRequestWrapper.java @@ -1,6 +1,7 @@ package com.moesif.servlet.wrappers; -import org.apache.commons.io.IOUtils; +import com.moesif.servlet.MoesifConfiguration; +import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -19,15 +20,14 @@ public class LoggingHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final List FORM_CONTENT_TYPE = Arrays.asList("application/x-www-form-urlencoded", "multipart/form-data"); - private static final String METHOD_POST = "POST"; - - private byte[] content; - private final Map parameterMap; - private final HttpServletRequest delegate; + private byte[] content; + public boolean bodySkipped = false; + public long contentLength = 0; + public LoggingHttpServletRequestWrapper(HttpServletRequest request) { super(request); this.delegate = request; @@ -38,14 +38,88 @@ public LoggingHttpServletRequestWrapper(HttpServletRequest request) { } } + private void readContentLength() { + try { + this.contentLength = Long.parseLong(getHeader("Content-Length")); + } catch (NumberFormatException e) { + // ignore malformed content length + } + } + + private void updateContentLength(long length) { + if (contentLength == 0) { + contentLength = length; + } + } + + private boolean shouldSkipBody() { + readContentLength(); + // should skip if we are not logging body by config or content length is greater than max body size + return !BodyHandler.logBody || contentLength > BodyHandler.MAX_BODY_SIZE; + } + @Override public ServletInputStream getInputStream() throws IOException { - if (ArrayUtils.isEmpty(content)) { + if (bodySkipped || ArrayUtils.isEmpty(content)) { return delegate.getInputStream(); } return new LoggingServletInputStream(content); } + public String getContent() { + try { + if (shouldSkipBody()) { + bodySkipped = true; + return null; + } + + if (this.parameterMap.isEmpty()) { + content = boundedStreamToArray(delegate.getInputStream(), BodyHandler.MAX_BODY_SIZE); + if (content == null) { + bodySkipped = true; + return null; + } + } else { + content = getContentFromParameterMap(this.parameterMap); + if (content.length > BodyHandler.MAX_BODY_SIZE) { + bodySkipped = true; + return null; + } + } + + String normalizedContent = BodyHandler.encodeContent(content, getCharacterEncoding()); + updateContentLength(normalizedContent.length()); + return normalizedContent; + } catch (IOException e) { + e.printStackTrace(); + throw new IllegalStateException(); + } + } + + private byte[] boundedStreamToArray(InputStream inputStream, long maxBytes) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int totalBytesRead = 0; + int bytesRead; + + while ((bytesRead = inputStream.read(buffer)) != -1) { + totalBytesRead += bytesRead; + if (totalBytesRead > maxBytes) { + updateContentLength(totalBytesRead); + return null; + } + baos.write(buffer, 0, bytesRead); + } + updateContentLength(totalBytesRead); + return baos.toByteArray(); + } catch (IOException e) { + // this is not expected but would typically represent a dropped connection which we should not handle here + e.printStackTrace(); + return null; + } + } + @Override public BufferedReader getReader() throws IOException { if (ArrayUtils.isEmpty(content)) { @@ -90,23 +164,6 @@ public String[] getParameterValues(String name) { return this.parameterMap.get(name); } - public String getContent() { - try { - if (this.parameterMap.isEmpty()) { - content = IOUtils.toByteArray(delegate.getInputStream()); - } else { - content = getContentFromParameterMap(this.parameterMap); - } - String requestEncoding = delegate.getCharacterEncoding(); - String normalizedContent = StringUtils.normalizeSpace(new String(content, requestEncoding != null ? requestEncoding : StandardCharsets.UTF_8.name())); - return normalizedContent; - // return StringUtils.isBlank(normalizedContent) ? "[EMPTY]" : normalizedContent; - } catch (IOException e) { - e.printStackTrace(); - throw new IllegalStateException(); - } - } - private byte[] getContentFromParameterMap(Map parameterMap) { List result = new ArrayList(); diff --git a/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletResponseWrapper.java b/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletResponseWrapper.java index e3b982d..937f98a 100644 --- a/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletResponseWrapper.java +++ b/moesif-servlet/src/main/java/com/moesif/servlet/wrappers/LoggingHttpServletResponseWrapper.java @@ -6,12 +6,17 @@ import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; -import java.io.*; -import java.util.*; - -import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; public class LoggingHttpServletResponseWrapper extends HttpServletResponseWrapper { + public long contentLength = 0; + public boolean bodySkipped = false; private ServletOutputStream outputStream; private LoggingServletOutputStream logStream; private PrintWriter writer; @@ -61,7 +66,7 @@ public Map getHeaders() { Map headers = new HashMap(0); Collection headerNames = getHeaderNames(); - for (String headerName: headerNames) { + for (String headerName : headerNames) { if (headerName != null) { if (headerName.equals("set-cookie")) { headers.put(headerName, getHeader(headerName)); @@ -73,31 +78,50 @@ public Map getHeaders() { return headers; } + private void readContentLength() { + try { + this.contentLength = Long.parseLong(getHeader("Content-Length")); + } catch (NumberFormatException e) { + // ignore malformed content length + } + } + + private boolean shouldSkipBody() { + readContentLength(); + // should skip if we are not logging body by config or content length is greater than max body size + return !BodyHandler.logBody || contentLength > BodyHandler.MAX_BODY_SIZE; + } + public String getContent() { try { flushBuffer(); - if (logStream == null) { + if (shouldSkipBody() || logStream == null || logStream.getBufferedStream() == null) { return null; } + updateContentLength(logStream.getBufferedStream().size()); String responseEncoding = getResponse().getCharacterEncoding(); - return logStream.baos.toString(responseEncoding != null ? responseEncoding : UTF_8.name()); - } catch (UnsupportedEncodingException e) { - return "[UNSUPPORTED ENCODING]"; + return BodyHandler.encodeContent(logStream.getBufferedStream().toByteArray(), responseEncoding); } catch (IOException e) { return "[IO EXCEPTION]"; } } - private class LoggingServletOutputStream extends ServletOutputStream { - public LoggingServletOutputStream(ServletOutputStream outputStream) { - - this.outputStream = outputStream; - this.baos = new ByteArrayOutputStream(1024); + private void updateContentLength(long length) { + if (contentLength == 0) { + contentLength = length; } + } + public class LoggingServletOutputStream extends ServletOutputStream { + public boolean bufferExceeded = false; private ServletOutputStream outputStream; private ByteArrayOutputStream baos; + public LoggingServletOutputStream(ServletOutputStream outputStream) { + this.outputStream = outputStream; + this.baos = new ByteArrayOutputStream(); + } + @Override public boolean isReady() { return outputStream.isReady(); @@ -111,30 +135,47 @@ public void setWriteListener(WriteListener writeListener) { @Override public void write(int b) throws IOException { outputStream.write(b); - baos.write(b); - } - - @Override - public void write(byte[] b) throws IOException { - outputStream.write(b); - baos.write(b); + if (!bufferExceeded) { + if (baos.size() < BodyHandler.MAX_BODY_SIZE) { + baos.write(b); + } else { + bufferExceeded = true; + baos.close(); + baos = null; + } + } } @Override public void write(byte[] b, int off, int len) throws IOException { outputStream.write(b, off, len); - baos.write(b, off, len); + if (!bufferExceeded) { + if (baos.size() + len <= BodyHandler.MAX_BODY_SIZE) { + baos.write(b, off, len); + } else { + bufferExceeded = true; + baos.close(); + baos = null; + } + } } @Override public void flush() throws IOException { outputStream.flush(); - baos.flush(); + if (!bufferExceeded) { + baos.flush(); + } } + @Override public void close() throws IOException { outputStream.close(); - baos.close(); + if (baos != null) baos.close(); + } + + public ByteArrayOutputStream getBufferedStream() { + return baos; } } } diff --git a/servlet-example/pom.xml b/servlet-example/pom.xml index aed3af4..f38fbff 100644 --- a/servlet-example/pom.xml +++ b/servlet-example/pom.xml @@ -28,7 +28,7 @@ com.moesif.servlet moesif-servlet - 1.8.1 + 1.8.3