2014-10-06 18 views
19

Sto provando a scrivere un semplice middleware OWIN, per intercettare il flusso di risposta. Quello che sto cercando di fare è sostituire lo stream originale con una classe personalizzata basata su Stream, dove potrò intercettare le scritture nel flusso di risposta.Come intercettare in modo sicuro lo stream di risposta in un Owin Middleware personalizzato

Tuttavia, sto affrontando alcuni problemi perché non posso sapere quando la risposta è stata completamente scritta da componenti middleware interni della catena. L'override Dispose del flusso non viene mai chiamato. Quindi non so quando è il momento di eseguire la mia elaborazione, che dovrebbe accadere alla fine della risposta Stream.

Ecco un esempio di codice:

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

Come ho accennato nei commenti del codice di cui sopra, ci sono due strategie che mi viene in mente al fine di rilevare se la risposta è completa.

a) È possibile registrare il numero di byte scritti nel flusso di risposta e correlarlo alla lunghezza di risposta prevista. Tuttavia, nel caso di risposte che utilizzano Chunked Transfer Encoding, la lunghezza non è nota.

b) Posso decidere che il flusso di risposta è completo quando Dispose viene richiamato nel flusso di risposta. Tuttavia, l'infrastruttura OWIN/Katana non chiama mai Dispose sullo stream sostituito.

Ho indagato su Opaque Streaming per vedere se la manipolazione del protocollo HTTP sottostante sarebbe un approccio fattibile, ma non mi sembra di capire se Katana supporti o meno Opaque Streaming.

C'è un modo per ottenere ciò che voglio?

risposta

34

Non penso che avrete bisogno di uno stream sottoclassato, ma ecco come potete leggere la risposta. Assicurati solo che questo middleware sia il primo nella pipeline OWIN in modo che sia l'ultimo a ispezionare la risposta.

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, per quanto ne so, derivante da OwinMiddleware non è considerato una buona pratica perché OwinMiddleware è specifico per Katana. Tuttavia non ha nulla a che fare con il tuo problema.

+1

Grazie per la risposta. Punto preso circa derivando da 'OwinMiddleware'. Tuttavia, nella tua risposta, stai usando 'OwinContext', che è anche specifico per Katana. Mi sto perdendo qualcosa ? –

+0

Sì, sei corretto. Ma sto usando 'OwinContext' internamente al middleware, il che significa che sto prendendo una dipendenza da Katana. Tuttavia, non utilizzandolo nel costruttore o nelle firme del metodo 'Invoke', non costringo altri assembly a fare affidamento su Katana. Chiunque costruisca la pipeline OWIN non ha bisogno di sapere nulla di 'OwinMiddleware'. Allo stesso modo, quando un middleware prima di questo nella pipeline chiama 'Invoke'. – Badri

+0

Sono riuscito a trovare un collegamento a una risposta SO di David Fowler relativa a questo. http://stackoverflow.com/a/19613529/1709870 – Badri

Problemi correlati