我有一个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服务器相同的边界.