簡體   English   中英

兩次讀取JAX-RS正文InputStream

[英]Read JAX-RS body InputStream twice

我有一個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讀取正文內容,但是我需要做一些技巧,因為無法重置此流。 沒有這個小技巧,我的rest方法總是收到空的請求正文內容:

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 ,可以讀取緩沖區中的整個流。 這還可以確保在第一次關閉時就正確關閉了源,以釋放OS資源。


舊答案

您可能不確定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服務器相同的邊界。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM