2017-10-05 2 views
6

Ich habe ein JAX-RS-Filter Protokollierung Anfrage- und Antwort Details zu protokollieren, so etwas wie dieses:lesen JAX-RS Körper Input zweimal

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

Die getBody() Methode liest der Körper Inhalt aus dem InputStream aber ich muss mach einen Trick, weil ich diesen Stream nicht zurücksetzen kann. Ohne diesen kleinen Trick erhalten meine Ruhemethoden immer einen leeren Inhalt:

Gibt es eine bessere Möglichkeit, den Inhalt des Körpers zu lesen?

+0

Verweis auf Interceptors und Filter unter [link] (https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-2rd-edition/en/part1/chapter12 /reader_and_writer_interceptors.html) – Gautam

Antwort

1

EDIT Hier ist eine verbesserte Version, die viel robuster erscheinen und JDK-Klassen verwenden. Rufen Sie einfach close() vor der Wiederverwendung auf.

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

Dies ermöglicht es den ganzen Strom in dem Puffer zu lesen, durch die mark-Integer.MAXVALUE zwicken. Dies stellt auch sicher, dass die Quelle bei der ersten nahe bei der Freigabe befindlichen OS-Ressource ordnungsgemäß geschlossen ist.


Alte Antwort

Wie Sie nicht sicher, dass die tatsächliche Umsetzung der InputStream Unterstützung Marke sein kann (markSupported()). Sie sollten den Eingabedatenstrom selbst in einer ersten Anwendung besser zwischenspeichern.

Für exemple in einem 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()); 
    } 
} 

Der Caching-Input-Stream ist ein naiver Ansatz zur Eingabe-Stream-Caching ist es ähnlich wie Ihr Ansatz:

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

} 

Diese Implementierung ist naiv verschiedene Art und Weise, kann es in dem folgenden Bereich verbessert werden, und wahrscheinlich mehr:

  • Chec k markSupported auf dem ursprünglichen Strom
  • Sie den Heap nicht verwenden, um den im Cache gespeicherten Eingabestrom zu speichern, würde dies den Druck auf dem GC
  • Cache unbegrenzt zu vermeiden, ist zur Zeit kann dies eine gute Besserung sein, zumindest das gleiche verwenden Grenzen als Ihr http-Server.