读取请求正文两次

Posted

技术标签:

【中文标题】读取请求正文两次【英文标题】:Read request body twice 【发布时间】:2015-10-02 02:18:11 【问题描述】:

我正在尝试读取中间件中的正文以进行身份​​验证,但是当请求到达 api 控制器时,对象为空,因为正文已被读取。有没有办法解决。我正在中间件中读取这样的正文。

var buffer = new byte[ Convert.ToInt32( context.Request.ContentLength ) ];
await context.Request.Body.ReadAsync( buffer, 0, buffer.Length );
var body = Encoding.UTF8.GetString( buffer );

【问题讨论】:

只是好奇:为什么在执行身份验证时读取请求的正文? 它是一个 api,在构建响应时还需要请求例如 ApiKey,并且还需要一个哈希来保护请求。在标题中没有意义。 An example 使用 PinPoint 的 EnableRewind 从正文中读取 json。 一个example 使用 PinPoint 的 EnableRewind 从正文读取 json 【参考方案1】:

如果您使用application/x-www-form-urlencodedmultipart/form-data,您可以安全地多次调用context.Request.ReadFormAsync(),因为它会在后续调用中返回一个缓存实例。

如果您使用不同的内容类型,则必须手动缓冲请求并将请求正文替换为 MemoryStream 之类的可回退流。以下是使用内联中间件的方法(您需要尽快在管道中注册它):

app.Use(next => async context =>

    // Keep the original stream in a separate
    // variable to restore it later if necessary.
    var stream = context.Request.Body;

    // Optimization: don't buffer the request if
    // there was no stream or if it is rewindable.
    if (stream == Stream.Null || stream.CanSeek)
    
        await next(context);

        return;
    

    try
    
        using (var buffer = new MemoryStream())
        
            // Copy the request stream to the memory stream.
            await stream.CopyToAsync(buffer);

            // Rewind the memory stream.
            buffer.Position = 0L;

            // Replace the request stream by the memory stream.
            context.Request.Body = buffer;

            // Invoke the rest of the pipeline.
            await next(context);
        
    

    finally
    
        // Restore the original stream.
        context.Request.Body = stream;
    
);

您还可以使用 BufferingHelper.EnableRewind() 扩展,它是 Microsoft.AspNet.Http 包的一部分:它基于类似的方法,但依赖于一个特殊的流,该流开始在内存中缓冲数据并将所有内容假脱机到磁盘上的临时文件达到阈值时:

app.Use(next => context =>

    context.Request.EnableRewind();

    return next(context);
);

仅供参考:未来可能会在 vNext 中添加缓冲中间件。

【讨论】:

我对缓存很好奇。合同或实施细节的那一部分是否会在未来发生变化? @SimonBelanger 在不久的将来,主机和中间件可能会共享一个共同的合同来表明它们是否支持缓冲。您可以在此 wiki 页面上找到更多详细信息以获取更多信息:github.com/aspnet/HttpAbstractions/wiki/Rolling-Notes @JakeRote 仅供参考,我刚刚更新了我的答案,提到了使用context.Request.EnableRewind() 的另一种方法。 @Pinpoint 谢谢我今晚回家后会尝试这两个谢谢 在 .Net Core 3.0 中 context.Request.EnableRewind() 应该改为 context.Request.EnableBuffering()【参考方案2】:

PinPoint 提及 EnableRewind 的用法

Startup.cs
using Microsoft.AspNetCore.Http.Internal;

Startup.Configure(...)
...
//Its important the rewind us added before UseMvc
app.Use(next => context =>  context.Request.EnableRewind(); return next(context); );
app.UseMvc()
...

然后在你的中间件中你只需倒带并重新阅读

private async Task GenerateToken(HttpContext context)
    
     context.Request.EnableRewind();
     string jsonData = new StreamReader(context.Request.Body).ReadToEnd();
    ...
    

【讨论】:

同样在 .Net Core 2.1 中需要在 StreamReader 之前添加 context.Request.Body.Position = 0 否则你会得到 "" (empty) 这个,context.Request.Body.Seek(0, SeekOrigin.Begin); 不起作用。【参考方案3】:

这适用于 .Net Core 2.1 及更高版本。

今天我遇到了类似的问题。长话短说,过去可以使用什么

Body.Seek(0, SeekOrigin.Begin);

导致今天出现异常,至少在我的情况下。这发生在代码迁移到最新版本的 .NET Core 之后。

我的解决方法是添加这个:

app.Use(next => context =>  context.Request.EnableBuffering(); return next(context);

在设置控制器或 MVC 之前添加它。这似乎是作为 .NET Core 2.1 版本的一部分添加的。

希望这对某人有所帮助!

干杯,编码愉快。

【讨论】:

以上是关于读取请求正文两次的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring 'HandlerMethodArgumentResolver' 中多次读取请求正文?

如何使用 spring webflux 读取请求正文

如何使用 GCDWebServer 请求读取 POST 请求的正文

在 webflux 中读取请求正文

读取请求消息正文时出错

如何使用 WEnviroment 读取 Wt 中的原始请求正文?