前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Net 6 AspNetCore Bug] 解决返回IAsyncEnumerable<T>类型时抛出的OperationCanceledException会被AspNetCore 框架吞掉的Bug

[Net 6 AspNetCore Bug] 解决返回IAsyncEnumerable<T>类型时抛出的OperationCanceledException会被AspNetCore 框架吞掉的Bug

作者头像
旺财的城堡
发布2021-11-30 19:55:18
5690
发布2021-11-30 19:55:18
举报
文章被收录于专栏:calvincalvincalvin

记录一个我认为是Net6 Aspnetcore 框架的一个Bug

Bug描述

在 Net6 的apsnecore项目中, 如果我们(满足以下所有条件)

  • api的返回类型是IAsyncEnumerable<T>,
  • 且我们返回的是JsonResult对象, 或者返回的是ObjectResult且要求的返回协商数据类型是json,
  • 且我们用的是System.Text.Json来序列化(模式是它),
  • 且我们的响应用要求的编码是utf-8

那么在业务方法中抛出的任何OperationCanceledException或者继承自OperationCanceledException的任何子类异常都会被框架吃掉.

Bug重现

如果我们有这样一段代码, 然后结果就是客户端和服务端都不会收到或者记录任何错误和异常.

[HttpGet("/asyncEnumerable-cancel")]
public ActionResult<IAsyncEnumerable<int>> TestAsync()
{
    async IAsyncEnumerable<int> asyncEnumerable()
    {
        await Task.Delay(100);

        yield return 1;

        throw new OperationCanceledException(); 
        // 或者Client 主动取消请求后 用this.HttpContext.RequestAborted.ThrowIfCancellationRequested() 或者任何地方抛出的task或operation cancel exception.
    }
    return this.Ok(asyncEnumerable());
}

测试代码

curl --location --request GET 'http://localhost:5000/asyncEnumerable-cancel'
# response code is 200
curl --location --request GET 'http://localhost:5000/asyncEnumerable-cancel' --header 'Accept-Charset: utf-16'
# response code is 500

显然这不是一个合理的 Behavior.

  • 不同的编码响应结果不一样
  • 明明抛出异常了, 但是utf-8还能收到200 ok的response http code

产生这个Bug的代码

SystemTextJsonOutputFormatter 对应的是用 return this.Ok(object)返回的Case SystemTextJsonResultExecutor 对应的是用 return new JsonResult(object)返回的case

当然, 其他的实现方式或者关联代码是否也有这个Bug我就没有验证了. 以及产生这个Bug的原因就不多说了. 可以看看这2个文件的commit logs.

//核心代码就是这么点. try-catch吞掉了这个Exception

if (selectedEncoding.CodePage == Encoding.UTF8.CodePage)
{
    try
    {
        await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
        await responseStream.FlushAsync(httpContext.RequestAborted);
    }
    catch (OperationCanceledException) { }
}

目前状况

昨天在 dotnet/aspnetcore/issues提交了一个issues, 等待官方的跟进.

如何手动修复这个Bug

如果是return new JsonResult(object), 我们可以用一个自己修复的SystemTextJsonResultExecutor替换框架自身的. 框架自身的是这么注册的: services.TryAddSingleton<IActionResultExecutor<JsonResult>, SystemTextJsonResultExecutor>();

如果你用的是return this.Ok(object)方式, 那么可以照着下面的代码来, 第一步, 首先从SystemTextJsonOutputFormatter copy 代码到你的本地. 然后修改构造函数并吧导致这个Bug的try-catch结构删掉即可.

// 构造函数中改动代码
public HookSystemTextJsonOutputFormatter(JsonSerializerOptions jsonSerializerOptions)
{
    SerializerOptions = jsonSerializerOptions;

    SupportedEncodings.Add(Encoding.UTF8);
    SupportedEncodings.Add(Encoding.Unicode);
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly());
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly());
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly());
}

// WriteResponseBodyAsync 方法中改动代码
var responseStream = httpContext.Response.Body;
if (selectedEncoding.CodePage == Encoding.UTF8.CodePage)
{
    await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
    await responseStream.FlushAsync(httpContext.RequestAborted);
}

第二步, 用我们自己改造过的SystemTextJsonOutputFormatter替换系统自己的

//用IConfigureOptions方式替换我们的自带SystemTextJsonOutputFormatter.
public class MvcCoreMvcOptionsSetupWithFixedSystemTextJsonOutputFormatter : IConfigureOptions<MvcOptions>
{
    private readonly IOptions<JsonOptions> jsonOptions;

    public MvcCoreMvcOptionsSetupWithFixedSystemTextJsonOutputFormatter(IOptions<JsonOptions> jsonOptions)
    {
        this.jsonOptions = jsonOptions;
    }

    public void Configure(MvcOptions options)
    {
        options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();//删除系统自己的
        options.OutputFormatters.Add(HookSystemTextJsonOutputFormatter.CreateFormatter(this.jsonOptions.Value));//替换为我们自己的
    }
}

// 然后在Startup.ConfigureServices的最后应用我们的更改

services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetupWithFixedSystemTextJsonOutputFormatter>());

后记

Ok, 到这里就结束了, 如果后续官方修复了这个bug, 那我们只要删除上面增加的代码即可.

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-11-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 记录一个我认为是Net6 Aspnetcore 框架的一个Bug
    • Bug描述
      • Bug重现
        • 产生这个Bug的代码
          • 目前状况
            • 如何手动修复这个Bug
            • 后记
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档