java - 读取 JAX-RS body InputStream 两次

标签 java rest jersey jax-rs jersey-2.0

我有一个 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();
        }
    }
}

这允许通过将 mark 调整为 Integer.MAXVALUE 来读取缓冲区中的整个流。这也确保源在第一次关闭时正确关闭以释放操作系统资源。


旧答案

因为您不能确定 InputStream 支持标记 (markSupported()) 的实际实现。您最好首先缓存输入流本身。

例如在 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 服务器相同的限制。

关于java - 读取 JAX-RS body InputStream 两次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46593062/

相关文章:

java - 使用泛型实例化java数组

java - SSL套接字帮助-javax.net.ssl.SSLHandshakeException : Received fatal alert: certificate_unknown

java - 具有静态内部类的构建器模式与具有一个抽象和一个+具体实现的构建器设计模式有什么区别

rest - 我应该对公共(public) API 中的资源使用 UUID 吗?

java - Postman 中仅显示响应实体

java - Gson解析卡住

node.js - PayPal Rest API - 创建发票时出现 401

REST:如何处理更改多个资源的操作

rest - 使用 CORS 的跨域 REST/Jersey Web 服务

java - JAXB 和 Jersey 列表解析?