Comment puis-je intercepter en toute sécurité le flux de réponse dans un middleware Owin personnalisé


Maxime Labelle

J'essaye d' écrire un middleware OWIN simple , afin d'intercepter le flux de réponse. Ce que j'essaie de faire est de remplacer le flux d'origine par une classe personnalisée basée sur le flux, où je pourrai intercepter les écritures dans le flux de réponse.

Cependant, je suis confronté à des problèmes car je ne peux pas savoir quand la réponse a été complètement écrite par les composants middleware internes de la chaîne. Le Disposeremplacement du Stream n'est jamais appelé. Je ne sais donc pas quand il est temps d'effectuer mon traitement, ce qui devrait se produire à la fin du flux de réponse.

Voici un exemple de code:

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

Comme je l'ai mentionné dans les commentaires du code ci-dessus, il y a deux stratégies auxquelles je peux penser pour détecter si la réponse est complète.

a) Je peux enregistrer le nombre d'octets écrits dans le flux de réponse et le corréler à la longueur de réponse attendue. Cependant, dans le cas des réponses qui utilisent le codage de transfert par blocs, la longueur n'est pas connue.

b) Je peux décider que le flux de réponse est complet lorsqu'il Disposeest appelé sur le flux de réponse. Cependant, l'infrastructure OWIN / Katana n'appelle jamais Dispose sur le flux remplacé.

J'ai étudié le streaming opaque afin de voir si la manipulation du protocole HTTP sous-jacent serait une approche faisable, mais je ne semble pas trouver si Katana prend en charge le streaming opaque ou non.

Existe-t-il un moyen de réaliser ce que je veux?

Badri

Je ne pense pas que vous aurez besoin d'un flux sous-classé, mais voici comment vous pouvez lire la réponse. Assurez-vous simplement que ce middleware est le premier du pipeline OWIN afin qu'il soit le dernier à inspecter la réponse.

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, pour autant que je sache, dériver de OwinMiddlewaren'est pas considéré comme une bonne pratique car il OwinMiddlewareest spécifique à Katana. Cela n'a cependant rien à voir avec votre problème.

Articles connexes