2014-10-06 36 views
19

Próbuję napisać proste oprogramowanie pośrednie OWIN, aby przechwycić strumień odpowiedzi. Próbuję zastąpić oryginalny strumień niestandardową klasą strumieniową, w której będę mógł przechwytywać zapisy do strumienia odpowiedzi.Jak bezpiecznie przechwycić strumień odpowiedzi w niestandardowym oprogramowaniu Owin

Jednak napotykam na pewne problemy, ponieważ nie wiem, kiedy odpowiedź została w całości napisana przez wewnętrzne komponenty oprogramowania pośredniego w łańcuchu. Przesłonięcie strumienia przez Dispose nigdy nie jest wywoływane. Nie wiem, kiedy nadszedł czas, aby wykonać moje przetwarzanie, co powinno nastąpić pod koniec strumienia odpowiedzi.

Oto przykładowy kod:

public sealed class CustomMiddleware: OwinMiddleware 
{ 
    public CustomMiddleware(OwinMiddleware next) 
     : base(next) 
    { 
    } 

    public override async Task Invoke(IOwinContext context) 
    { 
     var request = context.Request; 
     var response = context.Response; 

     // capture response stream 

     var vr = new MemoryStream(); 
     var responseStream = new ResponseStream(vr, response.Body); 

     response.OnSendingHeaders(state => 
     { 
      var resp = (state as IOwinContext).Response; 
      var contentLength = resp.Headers.ContentLength; 

      // contentLength == null for Chunked responses 

     }, context); 

     // invoke the next middleware in the pipeline 

     await Next.Invoke(context); 
    } 
} 

public sealed class ResponseStream : Stream 
{ 
    private readonly Stream stream_; // MemoryStream 
    private readonly Stream output_; // Owin response 
    private long writtenBytes_ = 0L; 

    public ResponseStream(Stream stream, Stream output) 
    { 
     stream_ = stream; 
     output_ = output; 
    } 

    ... // System.IO.Stream implementation 

    public override void Write(byte[] buffer, int offset, int count) 
    { 
     // capture writes to the response stream in our local stream 
     stream_.Write(buffer, offset, count); 

     // write to the real output stream 
     output_.Write(buffer, offset, count); 

     // update the number of bytes written 

     writtenBytes_ += count; 

     // how do we know the response is complete ? 
     // we could check that the number of bytes written 
     // is equal to the content length, but content length 
     // is not available for Chunked responses. 
    } 

    protected override void Dispose(bool disposing) 
    { 
     // we could perform our processing 
     // when the stream is disposed of. 
     // however, this method is never called by 
     // the OWIN/Katana infrastructure. 
    } 
} 

Jak już wspomniałem w komentarzach z kodem powyżej, istnieją dwie strategie, które mogę myśleć w celu wykrycia, czy odpowiedź jest kompletna.

a) Mogę zarejestrować liczbę bajtów zapisanych w strumieniu odpowiedzi i skorelować ją z oczekiwaną długością odpowiedzi. Jednak w przypadku odpowiedzi wykorzystujących kodowane kodowanie transferowe długość nie jest znana.

b) Mogę zdecydować, że strumień odpowiedzi jest kompletny, gdy wywoływana jest Dispose w strumieniu odpowiedzi. Jednak infrastruktura OWIN/Katana nigdy nie wywołuje Dispose w zastępowanym strumieniu.

Badałem Opaque Streaming w celu sprawdzenia, czy manipulowanie bazowym protokołem HTTP byłoby wykonalnym podejściem, ale wydaje mi się, że nie widzę, czy Katana obsługuje Opaque Streaming czy nie.

Czy istnieje sposób na osiągnięcie tego, czego chcę?

Odpowiedz

34

Nie sądzę, że będziesz potrzebował strumienia podzielonego na klasy, ale tutaj możesz odczytać odpowiedź. Po prostu upewnij się, że to oprogramowanie pośrednie jest pierwszym w potoku OWIN, więc będzie ono ostatnim sprawdzać odpowiedź.

using AppFunc = Func<IDictionary<string, object>, Task>; 

public class CustomMiddleware 
{ 
    private readonly AppFunc next; 

    public CustomMiddleware(AppFunc next) 
    { 
     this.next = next; 
    } 

    public async Task Invoke(IDictionary<string, object> env) 
    { 
     IOwinContext context = new OwinContext(env); 

     // Buffer the response 
     var stream = context.Response.Body; 
     var buffer = new MemoryStream(); 
     context.Response.Body = buffer; 

     await this.next(env); 

     buffer.Seek(0, SeekOrigin.Begin); 
     var reader = new StreamReader(buffer); 
     string responseBody = await reader.ReadToEndAsync(); 

     // Now, you can access response body. 
     Debug.WriteLine(responseBody); 

     // You need to do this so that the response we buffered 
     // is flushed out to the client application. 
     buffer.Seek(0, SeekOrigin.Begin); 
     await buffer.CopyToAsync(stream); 
    } 
} 

BTW, o ile mi wiadomo, wynikające z OwinMiddleware nie jest uważane za dobrą praktykę, ponieważ OwinMiddleware jest specyficzna Katana. Nie ma to jednak nic wspólnego z twoim problemem.

+1

Dziękuję za odpowiedź. Punkt zaczerpnięty z 'OwinMiddleware'. Jednak w twojej odpowiedzi używasz 'OwinContext', który jest również specyficzny dla Katany. Czy czegoś brakuje? –

+0

Tak, masz rację. Ale używam 'OwinContext' wewnętrznie do middleware, co oznacza, że ​​biorę zależność od Katany. Jednak nie używając go w konstruktorze lub sygnaturach metody "Invoke", nie zmuszam innych zespołów do uzależnienia się od Katany. Ktokolwiek buduje rurociąg OWIN nie musi wiedzieć nic o 'OwinMiddleware'. Podobnie, gdy oprogramowanie pośrednie przed tym w potoku wywołuje 'Invoke'. – Badri

+0

Udało mi się znaleźć link do odpowiedzi od Davida Fowlera na SO. http://stackoverflow.com/a/19613529/1709870 – Badri