2014-10-06 4 views
19

Ich versuche, eine einfache OWIN Middleware zu schreiben, um den Antwort-Stream abzufangen. Was ich versuche, ist den ursprünglichen Stream durch eine benutzerdefinierte Stream-basierte Klasse zu ersetzen, in der ich Schreibvorgänge im Antwort-Stream abfangen kann.Wie kann ich den Antwort-Stream in einer benutzerdefinierten Owin Middleware sicher abfangen

Allerdings habe ich einige Probleme, weil ich nicht wissen kann, wann die innere Komponente der Kette vollständig auf die Antwort geschrieben wurde. Die Dispose Überschreibung des Streams wird nie aufgerufen. Also weiß ich nicht, wann es Zeit ist, meine Verarbeitung durchzuführen, was am Ende des Response-Streams passieren soll.

Hier ist ein Beispielcode:

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

Als ich aus dem Code in den Kommentaren oben erwähnt haben, gibt es zwei Strategien, die ich in der Prüfung, ob die Reaktion vollständig ist zu erkennen, denken kann.

a) Ich kann die Anzahl der in den Antwortdatenstrom geschriebenen Bytes aufzeichnen und diese mit der erwarteten Antwortlänge korrelieren. Im Fall von Antworten, die das Chunked Transfer Encoding verwenden, ist die Länge jedoch nicht bekannt.

b) Ich kann entscheiden, dass der Antwort-Stream abgeschlossen ist, wenn Dispose für den Antwort-Stream aufgerufen wird. Die OWIN/Katana-Infrastruktur ruft Dispose jedoch nie für den ersetzten Stream auf.

Ich habe untersucht, um zu sehen, ob die Manipulation des zugrunde liegenden HTTP-Protokolls ein praktikabler Ansatz wäre, aber ich finde nicht, ob Katana Opaque Streaming unterstützt oder nicht.

Gibt es eine Möglichkeit zu erreichen, was ich will?

Antwort

34

Ich glaube nicht, dass Sie einen unterklassierten Stream benötigen, aber hier ist, wie Sie die Antwort lesen können. Stellen Sie nur sicher, dass diese Middleware die erste in der OWIN-Pipeline ist, damit sie als letzte die Antwort prüft.

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, soweit ich weiß, von OwinMiddleware abzuleiten, ist keine gute Praxis angesehen, weil OwinMiddleware spezifisch für Katana ist. Es hat jedoch nichts mit Ihrem Problem zu tun.

+1

Vielen Dank für Ihre Antwort. Punkt, der von "Owin Middleware" abgeleitet wurde. In Ihrer Antwort verwenden Sie jedoch 'OwinContext', der auch für Katana spezifisch ist. Fehle ich etwas? –

+0

Ja, Sie haben Recht. Aber ich benutze 'OwinContext' intern zur Middleware, was bedeutet, dass ich von Katana abhängig bin. Indem ich es jedoch nicht in den Konstruktor- oder "Invoke" -Methodensignaturen verwende, erzwinge ich anderen Assemblies keine Abhängigkeit von Katana. Wer die OWIN-Pipeline erstellt, braucht nichts über 'OwinMiddleware' zu ​​wissen. Ähnlich, wenn eine Middleware davor in der Pipeline "Invoke" aufruft. – Badri

+0

Es ist mir gelungen, einen Link zu einer SO-Antwort von David Fowler zu finden. http://Stackoverflow.com/a/19613529/1709870 – Badri

Verwandte Themen