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