记录一个我认为是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, 那我们只要删除上面增加的代码即可.

开始写的时候本想多介绍一些关于ActionResult(JsonResult, ObjectResult), ObjectResult的内容格式协商, 以及在ObjectResult上的一些设计. 临到头了打不动字了, 也不想翻源代码了, 最重要的还是懒. 哈哈.

所以这个任务就交给搜索引擎吧... 搜索了一下有不少讲这个的, 啊哈哈.

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

  1. 【VS调试】C#读写Windows 7注册表时抛出“不允许所请求的注册表访问权”的解决办法

    原文:[VS调试]C#读写Windows 7注册表时抛出"不允许所请求的注册表访问权"的解决办法 项目 - 属性 - 安全性,"使用ClickOnce",修改a ...

  2. JMS之——ActiveMQ时抛出的错误Could not connect to broker URL-使用线程池解决高并发连接

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/69046395 解决使用activemq时抛出的异常:javax.j ms.JMSE ...

  3. Android ADT插件更新后程序运行时抛出java.lang.VerifyError异常解决办法

    当我把Eclipse中的 Android ADT插件从21.1.0更新到22.0.1之后,安装后运行程序抛出java.lang.VerifyError异常. 经过调查,终于找到了一个有效的解决办法: ...

  4. 执行Socket socket = new Socket(ip, port);时抛出个异常:android.os.NetworkOnMainThreadException解决办法

    首先,确认你的android版本是4.0之后再用此方法解决,因为在4.0之后在主线程里面执行Http请求才会报这个错,也许是怕Http请求时间太长造成程序假死的情况吧.Android在4.0之前的版本 ...

  5. mixare的measureText方法在频繁调用时抛出“referencetable overflow max 1024”的解决方式

    这几天在搞基于位置的AR应用,採用了github上两款开源项目: mixare android-argument-reality-framework 这两个项目实现机制大致同样.我选取的是androi ...

  6. C#解决关闭多线程的form主窗体时抛出ObjectDisposedException 异常

    一.现象: 我在主窗体新建线程,使用子线程来处理接收到的数据,并且更新窗体显示内容,但关闭主窗体程序之后就程序就报错,如下所示: 二.分析问题: 由于新建线程的处理函数里边是一直死循环处理数据,虽然窗 ...

  7. django在启动时抛出Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 解决办法

    1.适用场景 在启动某个服务的时候,比如python中django启动的时候8000端口被占用,导致无法启动服务. 2.解决办法 通过命令行找出端口对应的PID进程 C:\Users\micha> ...

  8. CAD调试时抛出“正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码”异常的解决方法

    这些天重装了电脑Win10系统,安装了CAD2012和VS2012,准备进行软件开发.在调试程序的时候,CAD没有进入界面就抛出 “正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain ...

  9. 解决input为number类型时maxlength无效的问题

    使用input数字number类型的时候maxlength无效,假设需要控制输入数量为18,可以用以下方式: 无效: <input type="text"  maxlengt ...

随机推荐

  1. spark性能优化(一)

    本文内容说明 初始化配置给rdd和dataframe带来的影响 repartition的相关说明 cache&persist的相关说明 性能优化的说明建议以及实例 配置说明 spark:2.4 ...

  2. 初次认识指针:C语言*p、p以及&p的区别,*p和**p的区别?

    https://blog.csdn.net/weixin_43115440/article/details/93475460 先要理解地址和数据,你可以想象有很多盒子,每个盒子有对应的号码,那个号码叫 ...

  3. 乘风破浪,遇见最美Windows 11之新微软商店(Microsoft Store)生态 - 安卓(Android™)开发体验指南

    什么是Windows 11的安卓(Android)应用 2021年6月25日,微软召开线上发布会,对外宣告下一代Windows操作系统Windows 11,Windows 11为用户重新打造的Micr ...

  4. 【UE4 C++】Slate 初探: Editor UI 与 Game UI

    概述 名词区分 Slate Slate 是完全自定义.与平台无关的UI框架 应用 可用于编辑器UI,编辑器的大部分界面都是使用 Slate 构建的 可做为游戏UI 可作为独立应用开发 只能 C++ 开 ...

  5. vue3.x相对于vue2.x生命周期改动

    vue3.x已经正式发布了,部分小伙伴已经用了vue3.x开发,部分小伙伴还在观望中,下面是两个影响比较大的改动 1.beforeDestroy和destroyed不能用了. 这个应该是vue2.x项 ...

  6. 基于RequestBodyAdvice和ResponseBodyAdvice来实现spring中参数的加密和解密

    在日常开发中,有时候我们经常需要和第三方接口打交道,有时候是我们调用别人的第三方接口,有时候是别人在调用我们的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作,如果每个接口都由我 ...

  7. Python环境配置详细步骤以及第一个程序

    打开python官网:https://www.python.org/ 在官网找与自己电脑系统匹配的版本路径  这里以python3.7.2版本为例: 下载完成后,使用管理员身份进行安装:  打开命令提 ...

  8. IdentityServer4 负载均衡配置

    在不用到负载之前,一切都很好,但是部署多个实例之后,问题挺多的:session问题.令牌签发后的校验问题. 在此之前,先自查官方文档:Deployment - IdentityServer4 1.0. ...

  9. sort-list leetcode C++

    Sort a linked list in O(n log n) time using constant space complexity. C++ /** * Definition for sing ...

  10. hdu 2586 How far away? (LCA模板)

    题意: N个点,形成一棵树,边有长度. M个询问,每个询问(a,b),询问a和b的距离 思路: 模板题,看代码.DFS预处理算出每个结点离根结点的距离. 注意: qhead[maxn],而不是qhea ...