2017-10-05 108 views
6

我有一個JAX-RS日誌過濾器記錄的請求和響應的細節,這樣的事情:閱讀JAX-RS身上的InputStream兩次

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

有沒有更好的方式來閱讀的主體內容?

+0

請參閱[link]上的攔截器和過濾器部分(https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-2rd-edition/en/part1/chapter12 /reader_and_writer_interceptors.html) – Gautam

回答

1

編輯這是一個改進版本,它看起來更加健壯並且使用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(); 
     } 
    } 
} 

這允許在緩衝器讀取整個流,通過調整markInteger.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(); 
    } 

} 

這個實現是幼稚各種方式,它可以在以下方面進行改進,可能更多:

  • Chec在原始流
  • ķmarkSupported不要使用堆存儲緩存輸入流,這將避免壓力對GC
  • Cache是​​無界目前這可能是一個不錯的改進,至少是使用相同的綁定爲你的http服務器。