我有一个JAX-RS日志记录过滤器来记录请求和响应详细信息,如下所示:
public class LoggingFilter implements ContainerRequestFilter,ContainerResponseFilter { @Override public void filter(final ContainerRequestContext requestContext) throws IOException { ... String body = getBody(request); ... if (LOGGER.isDebugEnabled()) { LOGGER.debug("request: {}",httpRequest); } } }
getBody()方法从InputStream中读取正文内容,但我需要做一些技巧,因为我无法重置此流.没有这个小技巧我的休息方法总是收到空的请求正文内容:
private String getBody(final ContainerRequestContext requestContext) { try { byte[] body = IoUtils.toByteArray(requestContext.getEntityStream()); InputStream stream = new ByteArrayInputStream(body); requestContext.setEntityStream(stream); return new String(body); } catch (IOException e) { return null; } }
解决方法
编辑这是一个改进的版本,看起来更强大,并使用JDK类.只需在重用之前调用close().
public class CachingInputStream extends BufferedInputStream { public CachingInputStream(InputStream source) { super(new PostCloseProtection(source)); super.mark(Integer.MAX_VALUE); } @Override public synchronized void close() throws IOException { if (!((PostCloseProtection) in).decoratedClosed) { in.close(); } super.reset(); } private static class PostCloseProtection extends InputStream { private volatile boolean decoratedClosed = false; private final InputStream source; public PostCloseProtection(InputStream source) { this.source = source; } @Override public int read() throws IOException { return decoratedClosed ? -1 : source.read(); } @Override public int read(byte[] b) throws IOException { return decoratedClosed ? -1 : source.read(b); } @Override public int read(byte[] b,int off,int len) throws IOException { return decoratedClosed ? -1 : source.read(b,off,len); } @Override public long skip(long n) throws IOException { return decoratedClosed ? 0 : source.skip(n); } @Override public int available() throws IOException { return source.available(); } @Override public void close() throws IOException { decoratedClosed = true; source.close(); } @Override public void mark(int readLimit) { source.mark(readLimit); } @Override public void reset() throws IOException { source.reset(); } @Override public boolean markSupported() { return source.markSupported(); } } }
这允许通过将标记调整为Integer.MAXVALUE来读取缓冲区中的整个流.这也确保在第一次关闭以释放OS资源时正确关闭源.
老答案
因为您无法确定InputStream支持标记的实际实现(markSupported()).你最好在第一个apprach中缓存输入流本身.
例如,在ContainerRequestFilter中:
@Component @Provider @PreMatching @Priority(1) public class ReadSomethingInPayloadFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext request) throws IOException { CachingInputStream entityStream = new CachingInputStream(request.getEntityStream()); readPayload(entityStream); request.setEntityStream(entityStream.getCachedInputStream()); } }
class CachingInputStream extends InputStream { public static final int END_STREAM = -1; private final InputStream is; private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); public CachingInputStream(InputStream is) { this.is = is; } public InputStream getCachedInputStream() { return new ByteArrayInputStream(baos.toByteArray()); } @Override public int read() throws IOException { int result = is.read(); // Avoid rewriting the end char (-1) otherwise it will be considered as a real char. if (result != END_STREAM) baos.write(result); return result; } @Override public int available() throws IOException { return is.available(); } @Override public void close() throws IOException { is.close(); } }
这种实现方式在各方面都很幼稚,可以在以下方面进行改进,可能更多:
>检查原始流上的markSupported>不要使用堆来存储缓存的输入流,这样可以避免对GC施加压力>缓存是无限的,目前这可能是一个很好的改进,至少使用与http服务器相同的边界.