2017-10-05 17 views
6

Tôi có một JAX-RS đăng lọc để đăng nhập request và response chi tiết, một cái gì đó như thế này:đọc JAX-RS cơ thể InputStream hai lần

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

Phương pháp getBody() đọc nội dung cơ thể từ InputStream nhưng tôi cần phải làm một số mẹo vì tôi không thể đặt lại luồng này. Nếu không có mẹo nhỏ này, phương pháp nghỉ ngơi của tôi luôn nhận được nội dung cơ thể yêu cầu trống:

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

Có cách nào tốt hơn để đọc nội dung cơ thể không?

+0

Tham chiếu phần Bộ chặn và Bộ lọc tại [link] (https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-2rd-edition/en/part1/chapter12 /reader_and_writer_interceptors.html) – Gautam

Trả lời

1

EDIT Đây là phiên bản cải tiến có vẻ mạnh mẽ hơn và sử dụng các lớp JDK. Chỉ cần gọi close() trước khi sử dụng lại.

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

này cho phép để đọc toàn bộ dòng trong bộ đệm, bằng cách tinh chỉnh các mark-Integer.MAXVALUE. Điều này cũng đảm bảo rằng nguồn được đóng đúng vào lần đóng đầu tiên để giải phóng tài nguyên hệ điều hành.


Old trả lời

Như bạn không thể chắc chắn việc thực hiện thực tế của nhãn InputStream hỗ trợ (markSupported()). Bạn tốt hơn trong việc lưu bộ nhớ đệm luồng đầu vào trong một apprach đầu tiên.

Đối dụ trong một 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()); 
    } 
} 

Các dòng bộ nhớ đệm đầu vào là một cách tiếp cận ngây thơ để input stream bộ nhớ đệm, nó là cách tương tự như cách tiếp cận của bạn:

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

} 

thực hiện Đây là ngây thơ trong nhiều cách khác nhau, nó có thể được cải thiện trong khu vực sau và có thể nhiều hơn nữa:

  • Chec k markSupported trên luồng gốc
  • Không sử dụng vùng lưu trữ luồng đầu vào được lưu trong bộ nhớ cache, điều này sẽ tránh áp lực lên GC
  • Bộ nhớ cache không bị chặn. giới hạn như máy chủ http của bạn.
Các vấn đề liên quan