Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rewind request body stream

I am re-implementing a request logger as Owin Middleware which logs the request url and body of all incoming requests. I am able to read the body, but if I do the body parameter in my controller is null.

I'm guessing it's null because the stream position is at the end so there is nothing left to read when it tries to deserialize the body. I had a similar issue in a previous version of Web API but was able to set the Stream position back to 0. This particular stream throws a This stream does not support seek operations exception.

In the most recent version of Web API 2.0 I could call Request.HttpContent.ReadAsStringAsync()inside my request logger, and the body would still arrive to the controller in tact.

How can I rewind the stream after reading it?

or

How can I read the request body without consuming it?

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     } } 
like image 828
Despertar Avatar asked Feb 15 '14 23:02

Despertar


People also ask

How do I get a request body in middleware?

Middleware { public class CreateSession { private readonly RequestDelegate _next; public CreateSession(RequestDelegate next) { this. _next = next; } public async Task Invoke(HttpContext httpContext) { //I want to get the request body here and if possible //map it to my user model and use the user model here. } }

What is EnableBuffering?

EnableBuffering(HttpRequest) Ensure the request Body can be read multiple times. Normally buffers request bodies in memory; writes requests larger than 30K bytes to disk. EnableBuffering(HttpRequest, Int32) Ensure the request Body can be read multiple times.


2 Answers

Just found one solution. Replacing the original stream with a new stream containing the data.

    public override async 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);             await this.Next.Invoke(context);         });     } 

If you are dealing with larger amounts of data, I'm sure a FileStream would also work as the replacement.

like image 90
Despertar Avatar answered Oct 13 '22 02:10

Despertar


Here's a small improvement to the first answer by Despertar, which helped me a lot, but I ran into an issue when working with binary data. The intermediate step of extracting the stream into a string and then putting it back into a byte array using Encoding.UTF8.GetBytes(body) messes up the binary content (the contents will change unless it is an UTF8 encoded string). Here's my fix using 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);         }     } 

Also, MemoryStream is nice because you can check the stream's length before logging the full body (which is something I don't want to do in case someone uploads a huge file).

like image 20
Efrain Avatar answered Oct 13 '22 02:10

Efrain