Flujo de cuerpo de solicitud de rebobinado


Estoy re-implementando un registrador de solicitudes como Middleware Owin que registra la url de la solicitud y el cuerpo de todas las solicitudes entrantes. Soy capaz de leer el cuerpo, pero si lo hago el parámetro body en mi controlador es null.

Supongo que es nulo porque la posición del flujo está al final, por lo que no queda nada que leer cuando intenta deserializar el cuerpo. Tuve un problema similar en una versión anterior de Web API, pero pude establecer la posición de transmisión de nuevo a 0. Este particular stream lanza una excepción This stream does not support seek operations.

En la versión más reciente de Web API 2.0 podría llamar a Request.HttpContent.ReadAsStringAsync()dentro de mi registrador de solicitudes, y el cuerpo aún llegaría al controlador en tacto.

¿Cómo puedo rebobinar la transmisión después de leerla?

O

¿Cómo puedo leer el cuerpo de la solicitud sin consumirlo?

public class RequestLoggerMiddleware : OwinMiddleware
{
    public RequestLoggerMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            context.Request.Body.Position = 0; // cannot set stream position back to 0
            Console.WriteLine(context.Request.Body.CanSeek); // prints false
            this.Next.Invoke(context);
        });
    }
}

public class SampleController : ApiController 
{
    public void Post(ModelClass body)
    {
        // body is now null if the middleware reads it
    }
}
Author: Despertar, 2014-02-16

3 answers

Acabo de encontrar una solución. Reemplazar el flujo original con un nuevo flujo que contenga los datos.

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            byte[] requestData = Encoding.UTF8.GetBytes(body);
            context.Request.Body = new MemoryStream(requestData);
            this.Next.Invoke(context);
        });
    }

Si está tratando con grandes cantidades de datos, estoy seguro de que un FileStream también funcionaría como reemplazo.

 35
Author: Despertar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-16 00:36:23

Aquí hay una pequeña mejora a la primera respuesta de Despertar, que me ayudó mucho, pero me encontré con un problema al trabajar con datos binarios. El paso intermedio de extraer el flujo en una cadena y luego ponerlo de nuevo en una matriz de bytes usando Encoding.UTF8.GetBytes(body) estropea el contenido binario (el contenido cambiará a menos que sea una cadena codificada en UTF8). Aquí está mi solución usando Stream.CopyTo():

    public override async Task Invoke(IOwinContext context)
    {
        // read out body (wait for all bytes)
        using (var streamCopy = new MemoryStream())
        {
            context.Request.Body.CopyTo(streamCopy);
            streamCopy.Position = 0; // rewind

            string body = new StreamReader(streamCopy).ReadToEnd();
            // log body

            streamCopy.Position = 0; // rewind again
            context.Request.Body = streamCopy; // put back in place for downstream handlers

            await this.Next.Invoke(context);
        }
    }

También, MemoryStream es bueno porque se puede comprobar la longitud de la secuencia antes de registrar el body (que es algo que no quiero hacer en caso de que alguien suba un archivo enorme).

 5
Author: Efrain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-11 15:35:48

Sé que esto es viejo, pero solo para ayudar a cualquiera que se encuentre con esto. Necesitas buscar en la corriente: context.Request.Body.Seek(0, SeekOrigin.Begin);

 -3
Author: Mitch Freed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-22 20:09:01