Extracted ContentCachingRequestWrapper and ContentCachingResponseWrapper
Issue: SPR-12477
This commit is contained in:
parent
3b1584904f
commit
decc5cd1ae
|
@ -16,34 +16,32 @@
|
|||
|
||||
package org.springframework.web.filter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
/**
|
||||
* Base class for {@code Filter}s that perform logging operations before and after a request is processed.
|
||||
* Base class for {@code Filter}s that perform logging operations before and after a request
|
||||
* is processed.
|
||||
*
|
||||
* <p>Subclasses should override the {@code beforeRequest(HttpServletRequest, String)} and
|
||||
* {@code afterRequest(HttpServletRequest, String)} methods to perform the actual logging around the request.
|
||||
* {@code afterRequest(HttpServletRequest, String)} methods to perform the actual logging
|
||||
* around the request.
|
||||
*
|
||||
* <p>Subclasses are passed the message to write to the log in the {@code beforeRequest} and
|
||||
* {@code afterRequest} methods. By default, only the URI of the request is logged. However, setting the
|
||||
* {@code includeQueryString} property to {@code true} will cause the query string of the request to be
|
||||
* included also. The payload (body) of the request can be logged via the {@code includePayload} flag. Note that
|
||||
* this will only log that which is read, which might not be the entire payload.
|
||||
* {@code afterRequest} methods. By default, only the URI of the request is logged. However,
|
||||
* setting the {@code includeQueryString} property to {@code true} will cause the query string
|
||||
* of the request to be included also. The payload (body) of the request can be logged via the
|
||||
* {@code includePayload} flag. Note that this will only log that which is read, which might
|
||||
* not be the entire payload.
|
||||
*
|
||||
* <p>Prefixes and suffixes for the before and after messages can be configured using the
|
||||
* {@code beforeMessagePrefix}, {@code afterMessagePrefix}, {@code beforeMessageSuffix} and
|
||||
|
@ -87,41 +85,43 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
|
|||
|
||||
|
||||
/**
|
||||
* Set whether or not the query string should be included in the log message. <p>Should be configured using an
|
||||
* {@code <init-param>} for parameter name "includeQueryString" in the filter definition in
|
||||
* {@code web.xml}.
|
||||
* Set whether the query string should be included in the log message.
|
||||
* <p>Should be configured using an {@code <init-param>} for parameter name
|
||||
* "includeQueryString" in the filter definition in {@code web.xml}.
|
||||
*/
|
||||
public void setIncludeQueryString(boolean includeQueryString) {
|
||||
this.includeQueryString = includeQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the query string should be included in the log message.
|
||||
* Return whether the query string should be included in the log message.
|
||||
*/
|
||||
protected boolean isIncludeQueryString() {
|
||||
return this.includeQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the client address and session id should be included in the log message. <p>Should be configured
|
||||
* using an {@code <init-param>} for parameter name "includeClientInfo" in the filter definition in
|
||||
* {@code web.xml}.
|
||||
* Set whether the client address and session id should be included in the
|
||||
* log message.
|
||||
* <p>Should be configured using an {@code <init-param>} for parameter name
|
||||
* "includeClientInfo" in the filter definition in {@code web.xml}.
|
||||
*/
|
||||
public void setIncludeClientInfo(boolean includeClientInfo) {
|
||||
this.includeClientInfo = includeClientInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the client address and session id should be included in the log message.
|
||||
* Return whether the client address and session id should be included in the
|
||||
* log message.
|
||||
*/
|
||||
protected boolean isIncludeClientInfo() {
|
||||
return this.includeClientInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the request payload (body) should be included in the log message. <p>Should be configured using
|
||||
* an {@code <init-param>} for parameter name "includePayload" in the filter definition in
|
||||
* {@code web.xml}.
|
||||
* Set whether the request payload (body) should be included in the log message.
|
||||
* <p>Should be configured using an {@code <init-param>} for parameter name
|
||||
* "includePayload" in the filter definition in {@code web.xml}.
|
||||
*/
|
||||
|
||||
public void setIncludePayload(boolean includePayload) {
|
||||
|
@ -129,14 +129,15 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
|
|||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the request payload (body) should be included in the log message.
|
||||
* Return whether the request payload (body) should be included in the log message.
|
||||
*/
|
||||
protected boolean isIncludePayload() {
|
||||
return includePayload;
|
||||
return this.includePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum length of the payload body to be included in the log message. Default is 50 characters.
|
||||
* Sets the maximum length of the payload body to be included in the log message.
|
||||
* Default is 50 characters.
|
||||
*/
|
||||
public void setMaxPayloadLength(int maxPayloadLength) {
|
||||
Assert.isTrue(maxPayloadLength >= 0, "'maxPayloadLength' should be larger than or equal to 0");
|
||||
|
@ -147,32 +148,36 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
|
|||
* Return the maximum length of the payload body to be included in the log message.
|
||||
*/
|
||||
protected int getMaxPayloadLength() {
|
||||
return maxPayloadLength;
|
||||
return this.maxPayloadLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value that should be prepended to the log message written <i>before</i> a request is processed.
|
||||
* Set the value that should be prepended to the log message written
|
||||
* <i>before</i> a request is processed.
|
||||
*/
|
||||
public void setBeforeMessagePrefix(String beforeMessagePrefix) {
|
||||
this.beforeMessagePrefix = beforeMessagePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value that should be apppended to the log message written <i>before</i> a request is processed.
|
||||
* Set the value that should be appended to the log message written
|
||||
* <i>before</i> a request is processed.
|
||||
*/
|
||||
public void setBeforeMessageSuffix(String beforeMessageSuffix) {
|
||||
this.beforeMessageSuffix = beforeMessageSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value that should be prepended to the log message written <i>after</i> a request is processed.
|
||||
* Set the value that should be prepended to the log message written
|
||||
* <i>after</i> a request is processed.
|
||||
*/
|
||||
public void setAfterMessagePrefix(String afterMessagePrefix) {
|
||||
this.afterMessagePrefix = afterMessagePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value that should be appended to the log message written <i>after</i> a request is processed.
|
||||
* Set the value that should be appended to the log message written
|
||||
* <i>after</i> a request is processed.
|
||||
*/
|
||||
public void setAfterMessageSuffix(String afterMessageSuffix) {
|
||||
this.afterMessageSuffix = afterMessageSuffix;
|
||||
|
@ -200,22 +205,21 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
|
|||
throws ServletException, IOException {
|
||||
|
||||
boolean isFirstRequest = !isAsyncDispatch(request);
|
||||
HttpServletRequest requestToUse = request;
|
||||
|
||||
if (isIncludePayload()) {
|
||||
if (isFirstRequest) {
|
||||
request = new RequestCachingRequestWrapper(request);
|
||||
}
|
||||
if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
|
||||
requestToUse = new ContentCachingRequestWrapper(request);
|
||||
}
|
||||
|
||||
if (isFirstRequest) {
|
||||
beforeRequest(request, getBeforeMessage(request));
|
||||
beforeRequest(requestToUse, getBeforeMessage(requestToUse));
|
||||
}
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
filterChain.doFilter(requestToUse, response);
|
||||
}
|
||||
finally {
|
||||
if (!isAsyncStarted(request)) {
|
||||
afterRequest(request, getAfterMessage(request));
|
||||
if (!isAsyncStarted(requestToUse)) {
|
||||
afterRequest(requestToUse, getAfterMessage(requestToUse));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -265,8 +269,8 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
|
|||
msg.append(";user=").append(user);
|
||||
}
|
||||
}
|
||||
if (isIncludePayload() && request instanceof RequestCachingRequestWrapper) {
|
||||
RequestCachingRequestWrapper wrapper = (RequestCachingRequestWrapper) request;
|
||||
if (isIncludePayload() && request instanceof ContentCachingRequestWrapper) {
|
||||
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
|
||||
byte[] buf = wrapper.getContentAsByteArray();
|
||||
if (buf.length > 0) {
|
||||
int length = Math.min(buf.length, getMaxPayloadLength());
|
||||
|
@ -301,63 +305,4 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
|
|||
*/
|
||||
protected abstract void afterRequest(HttpServletRequest request, String message);
|
||||
|
||||
|
||||
private static class RequestCachingRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final ByteArrayOutputStream cachedContent = new ByteArrayOutputStream(1024);
|
||||
|
||||
private final ServletInputStream inputStream;
|
||||
|
||||
private BufferedReader reader;
|
||||
|
||||
private RequestCachingRequestWrapper(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
this.inputStream = new RequestCachingInputStream(request.getInputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
return this.inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCharacterEncoding() {
|
||||
String enc = super.getCharacterEncoding();
|
||||
return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
if (this.reader == null) {
|
||||
this.reader = new BufferedReader(new InputStreamReader(this.inputStream, getCharacterEncoding()));
|
||||
}
|
||||
return this.reader;
|
||||
}
|
||||
|
||||
private byte[] getContentAsByteArray() {
|
||||
return this.cachedContent.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
private class RequestCachingInputStream extends ServletInputStream {
|
||||
|
||||
private final ServletInputStream is;
|
||||
|
||||
public RequestCachingInputStream(ServletInputStream is) {
|
||||
this.is = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int ch = this.is.read();
|
||||
if (ch != -1) {
|
||||
cachedContent.write(ch);
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,22 +17,17 @@
|
|||
package org.springframework.web.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.ResizableByteArrayOutputStream;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
|
@ -80,8 +75,8 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
|||
throws ServletException, IOException {
|
||||
|
||||
HttpServletResponse responseToUse = response;
|
||||
if (!isAsyncDispatch(request)) {
|
||||
responseToUse = new ShallowEtagResponseWrapper(response);
|
||||
if (!isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
|
||||
responseToUse = new ContentCachingResponseWrapper(response);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, responseToUse);
|
||||
|
@ -92,13 +87,13 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
|||
}
|
||||
|
||||
private void updateResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ShallowEtagResponseWrapper responseWrapper =
|
||||
WebUtils.getNativeResponse(response, ShallowEtagResponseWrapper.class);
|
||||
ContentCachingResponseWrapper responseWrapper =
|
||||
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
Assert.notNull(responseWrapper, "ShallowEtagResponseWrapper not found");
|
||||
|
||||
HttpServletResponse rawResponse = (HttpServletResponse) responseWrapper.getResponse();
|
||||
int statusCode = responseWrapper.getStatusCode();
|
||||
byte[] body = responseWrapper.toByteArray();
|
||||
byte[] body = responseWrapper.getContentAsByteArray();
|
||||
|
||||
if (rawResponse.isCommitted()) {
|
||||
if (body.length > 0) {
|
||||
|
@ -178,165 +173,4 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link HttpServletRequest} wrapper that buffers all content written to the
|
||||
* {@linkplain #getOutputStream() output stream} and {@linkplain #getWriter() writer},
|
||||
* and allows this content to be retrieved via a {@link #toByteArray() byte array}.
|
||||
*/
|
||||
private static class ShallowEtagResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
private final ResizableByteArrayOutputStream content = new ResizableByteArrayOutputStream(1024);
|
||||
|
||||
private final ServletOutputStream outputStream = new ResponseServletOutputStream();
|
||||
|
||||
private PrintWriter writer;
|
||||
|
||||
private int statusCode = HttpServletResponse.SC_OK;
|
||||
|
||||
public ShallowEtagResponseWrapper(HttpServletResponse response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int sc) {
|
||||
super.setStatus(sc);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setStatus(int sc, String sm) {
|
||||
super.setStatus(sc, sm);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc) throws IOException {
|
||||
copyBodyToResponse();
|
||||
super.sendError(sc);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc, String msg) throws IOException {
|
||||
copyBodyToResponse();
|
||||
super.sendError(sc, msg);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRedirect(String location) throws IOException {
|
||||
copyBodyToResponse();
|
||||
super.sendRedirect(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() {
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
if (this.writer == null) {
|
||||
String characterEncoding = getCharacterEncoding();
|
||||
this.writer = (characterEncoding != null ? new ResponsePrintWriter(characterEncoding) :
|
||||
new ResponsePrintWriter(WebUtils.DEFAULT_CHARACTER_ENCODING));
|
||||
}
|
||||
return this.writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLength(int len) {
|
||||
if (len > this.content.capacity()) {
|
||||
this.content.resize(len);
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides Servlet 3.1 setContentLengthLong(long) at runtime
|
||||
public void setContentLengthLong(long len) {
|
||||
if (len > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Content-Length exceeds ShallowEtagHeaderFilter's maximum (" +
|
||||
Integer.MAX_VALUE + "): " + len);
|
||||
}
|
||||
if (len > this.content.capacity()) {
|
||||
this.content.resize((int) len);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferSize(int size) {
|
||||
if (size > this.content.capacity()) {
|
||||
this.content.resize(size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBuffer() {
|
||||
this.content.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
this.content.reset();
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return this.statusCode;
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
return this.content.toByteArray();
|
||||
}
|
||||
|
||||
private void copyBodyToResponse() throws IOException {
|
||||
if (this.content.size() > 0) {
|
||||
getResponse().setContentLength(this.content.size());
|
||||
StreamUtils.copy(this.content.toByteArray(), getResponse().getOutputStream());
|
||||
this.content.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ResponseServletOutputStream extends ServletOutputStream {
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
content.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
content.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ResponsePrintWriter extends PrintWriter {
|
||||
|
||||
public ResponsePrintWriter(String characterEncoding) throws UnsupportedEncodingException {
|
||||
super(new OutputStreamWriter(content, characterEncoding));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char buf[], int off, int len) {
|
||||
super.write(buf, off, len);
|
||||
super.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s, int off, int len) {
|
||||
super.write(s, off, len);
|
||||
super.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) {
|
||||
super.write(c);
|
||||
super.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
/**
|
||||
* {@link javax.servlet.http-HttpServletRequest} wrapper that caches all content read from
|
||||
* the {@linkplain #getInputStream() input stream} and {@linkplain #getReader() reader},
|
||||
* and allows this content to be retrieved via a {@link #getContentAsByteArray() byte array}.
|
||||
*
|
||||
* <p>Used e.g. by {@link org.springframework.web.filter.AbstractRequestLoggingFilter}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.1.3
|
||||
*/
|
||||
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final ByteArrayOutputStream cachedContent;
|
||||
|
||||
private ServletInputStream inputStream;
|
||||
|
||||
private BufferedReader reader;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ContentCachingRequestWrapper for the given servlet request.
|
||||
* @param request the original servlet request
|
||||
*/
|
||||
public ContentCachingRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
int contentLength = request.getContentLength();
|
||||
this.cachedContent = new ByteArrayOutputStream(contentLength >= 0 ? contentLength : 1024);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
if (this.inputStream == null) {
|
||||
this.inputStream = new ContentCachingInputStream(getRequest().getInputStream());
|
||||
}
|
||||
return this.inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCharacterEncoding() {
|
||||
String enc = super.getCharacterEncoding();
|
||||
return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
if (this.reader == null) {
|
||||
this.reader = new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
|
||||
}
|
||||
return this.reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cached request content as a byte array.
|
||||
*/
|
||||
public byte[] getContentAsByteArray() {
|
||||
return this.cachedContent.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
private class ContentCachingInputStream extends ServletInputStream {
|
||||
|
||||
private final ServletInputStream is;
|
||||
|
||||
public ContentCachingInputStream(ServletInputStream is) {
|
||||
this.is = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int ch = this.is.read();
|
||||
if (ch != -1) {
|
||||
cachedContent.write(ch);
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
import org.springframework.util.ResizableByteArrayOutputStream;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* {@link javax.servlet.http-HttpServletResponse} wrapper that caches all content written to
|
||||
* the {@linkplain #getOutputStream() output stream} and {@linkplain #getWriter() writer},
|
||||
* and allows this content to be retrieved via a {@link #getContentAsByteArray() byte array}.
|
||||
*
|
||||
* <p>Used e.g. by {@link org.springframework.web.filter.ShallowEtagHeaderFilter}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.1.3
|
||||
*/
|
||||
public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
private final ResizableByteArrayOutputStream content = new ResizableByteArrayOutputStream(1024);
|
||||
|
||||
private final ServletOutputStream outputStream = new ResponseServletOutputStream();
|
||||
|
||||
private PrintWriter writer;
|
||||
|
||||
private int statusCode = HttpServletResponse.SC_OK;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ContentCachingResponseWrapper for the given servlet response.
|
||||
* @param response the original servlet response
|
||||
*/
|
||||
public ContentCachingResponseWrapper(HttpServletResponse response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setStatus(int sc) {
|
||||
super.setStatus(sc);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setStatus(int sc, String sm) {
|
||||
super.setStatus(sc, sm);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc) throws IOException {
|
||||
copyBodyToResponse();
|
||||
super.sendError(sc);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc, String msg) throws IOException {
|
||||
copyBodyToResponse();
|
||||
super.sendError(sc, msg);
|
||||
this.statusCode = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRedirect(String location) throws IOException {
|
||||
copyBodyToResponse();
|
||||
super.sendRedirect(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() {
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
if (this.writer == null) {
|
||||
String characterEncoding = getCharacterEncoding();
|
||||
this.writer = (characterEncoding != null ? new ResponsePrintWriter(characterEncoding) :
|
||||
new ResponsePrintWriter(WebUtils.DEFAULT_CHARACTER_ENCODING));
|
||||
}
|
||||
return this.writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLength(int len) {
|
||||
if (len > this.content.capacity()) {
|
||||
this.content.resize(len);
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides Servlet 3.1 setContentLengthLong(long) at runtime
|
||||
public void setContentLengthLong(long len) {
|
||||
if (len > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Content-Length exceeds ShallowEtagHeaderFilter's maximum (" +
|
||||
Integer.MAX_VALUE + "): " + len);
|
||||
}
|
||||
if (len > this.content.capacity()) {
|
||||
this.content.resize((int) len);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferSize(int size) {
|
||||
if (size > this.content.capacity()) {
|
||||
this.content.resize(size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBuffer() {
|
||||
this.content.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
this.content.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status code as specifed on the response.
|
||||
*/
|
||||
public int getStatusCode() {
|
||||
return this.statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cached response content as a byte array.
|
||||
*/
|
||||
public byte[] getContentAsByteArray() {
|
||||
return this.content.toByteArray();
|
||||
}
|
||||
|
||||
private void copyBodyToResponse() throws IOException {
|
||||
if (this.content.size() > 0) {
|
||||
getResponse().setContentLength(this.content.size());
|
||||
StreamUtils.copy(this.content.toByteArray(), getResponse().getOutputStream());
|
||||
this.content.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ResponseServletOutputStream extends ServletOutputStream {
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
content.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
content.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ResponsePrintWriter extends PrintWriter {
|
||||
|
||||
public ResponsePrintWriter(String characterEncoding) throws UnsupportedEncodingException {
|
||||
super(new OutputStreamWriter(content, characterEncoding));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char buf[], int off, int len) {
|
||||
super.write(buf, off, len);
|
||||
super.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s, int off, int len) {
|
||||
super.write(s, off, len);
|
||||
super.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) {
|
||||
super.write(c);
|
||||
super.flush();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue