Java – read the jax-rs body InputStream twice

I have a jax-rs logging filter to record request and response details, as follows:

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);
        }
    }
}

The getbody () method reads the body content from the InputStream, but I need to do some tricks because I can't reset the stream Without this tip, my rest method always receives empty request body content:

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;
    }
}

Is there a better way to read body content?

Solution

Edit this is an improved version that looks more powerful and uses JDK classes Just call close () before reuse.

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();
        }
    }
}

This allows you to adjust the tag to integer Maxvalue to read the entire stream in the buffer This also ensures that the source is shut down correctly the first time it is shut down to free OS resources

Old answer

Because you cannot determine the actual implementation of the InputStream support tag (marksupported()) You'd better cache the input stream itself in the first append

For example, in 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());
    }
}

Caching input streams is a simple input stream caching method similar to yours:

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();
    }

}

This implementation method is naive in all aspects, and can be improved in the following aspects, possibly more:

>Check the marksupported on the original stream > do not use the heap to store the cached input stream, which can avoid exerting pressure on the GC > the cache is infinite. At present, this may be a good improvement, at least using the same boundary as the HTTP server

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>