【问题标题】:Rewind request body stream倒带请求正文流
【发布时间】:2014-03-15 08:22:34
【问题描述】:

我正在将请求记录器重新实现为 Owin 中间件,它记录所有传入请求的请求 url 和正文。 我可以读取正文,但如果我这样做,我的控制器中的正文参数为空。

我猜它是空的,因为流位置在末尾,所以当它试图反序列化主体时,没有什么可读取的了。我在以前版本的 Web API 中遇到过类似的问题,但能够将 Stream 位置设置回 0。这个特定的流会引发 This stream does not support seek operations 异常。

在最新版本的 Web API 2.0 中,我可以在我的请求记录器中调用 Request.HttpContent.ReadAsStringAsync(),而正文仍会完整地到达控制器。

我如何在阅读后快退?

如何在不消费的情况下读取请求正文?

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

【问题讨论】:

    标签: asp.net .net asp.net-web-api owin owin-middleware


    【解决方案1】:

    刚刚找到一个解决方案。将原始流替换为包含数据的新流

        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);
            });
        }
    

    如果您要处理大量数据,我相信FileStream 也可以作为替代品。

    【讨论】:

    • 另一种选择:Stream anotherStream = new MemoryStream(); context.Request.Body.CopyToAsync(anotherStream);
    • 在 ASP.NET Core 中(invoke 方法将具有以下签名 async Task Invoke(HttpContext context)),您可以执行以下操作来获取允许您多次查找和读取的缓冲流; context.Request.EnableRewind()(在Microsoft.AspNetCore.Http.Internal.BufferingHelper中找到的HttpRequest扩展方法)。
    • @jfiskvik 您的输入对我非常有帮助,它帮助我在中间件中进行了一些处理后回退流
    • 使用.NET Core 3.1(及更高版本)时,您可以使用Microsoft.AspNetCore.Http 中的context.Request.EnableBuffering()(而不是@jfiskvik 的回答中的context.Request.EnableRewind())。这使我能够像这样重置身体:context.Request.Body.Seek(0, SeekOrigin.Begin);.
    • 警告:这个答案很危险: - 来自Next.Invoke()Task 结果未被使用(或等待) - Task.Run 将在线程池线程中运行延续 - 没有 @987654334 @ 等 HttpContext.Current 将无法正确设置。在负载下,这段代码会做坏事。
    【解决方案2】:

    我知道这是旧的,但只是为了帮助遇到此问题的任何人。 您需要在流上寻找:context.Request.Body.Seek(0, SeekOrigin.Begin);

    【讨论】:

    • Body.Position = 0 相同,这会引发NotSupportedException 和消息This stream does not support seek operationsBody.CanSeek 属性也返回 false。
    【解决方案3】:

    这是对 Despertar 的第一个答案的一个小改进,这对我帮助很大,但是在处理 二进制数据时遇到了一个问题。 将流提取为字符串的中间步骤和然后使用 Encoding.UTF8.GetBytes(body) 将其放回字节数组中会弄乱二进制内容(除非它是 UTF8 编码的字符串,否则内容会改变)。这是我使用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);
            }
        }
    

    另外,MemoryStream 也不错,因为您可以在记录整个正文之前检查流的长度(如果有人上传了一个大文件,我不想这样做)。

    【讨论】:

    • 我已经尝试过这段代码,但是当我第二次尝试读取时它抛出了一个异常。
    • 异常发生在哪一行?
    猜你喜欢
    • 2016-08-24
    • 1970-01-01
    • 2014-07-06
    • 2020-09-07
    • 1970-01-01
    • 1970-01-01
    • 2020-12-23
    • 2021-03-12
    • 2015-11-28
    相关资源
    最近更新 更多