[Net 6 AspNetCore Bug] 解决返回IAsyncEnumerable<T>类型时抛出的OperationCanceledException会被AspNetCore 框架吞掉的Bug
记录一个我认为是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的更多相关文章
- 【VS调试】C#读写Windows 7注册表时抛出“不允许所请求的注册表访问权”的解决办法
原文:[VS调试]C#读写Windows 7注册表时抛出"不允许所请求的注册表访问权"的解决办法 项目 - 属性 - 安全性,"使用ClickOnce",修改a ...
- JMS之——ActiveMQ时抛出的错误Could not connect to broker URL-使用线程池解决高并发连接
转载请注明出处:http://blog.csdn.net/l1028386804/article/details/69046395 解决使用activemq时抛出的异常:javax.j ms.JMSE ...
- Android ADT插件更新后程序运行时抛出java.lang.VerifyError异常解决办法
当我把Eclipse中的 Android ADT插件从21.1.0更新到22.0.1之后,安装后运行程序抛出java.lang.VerifyError异常. 经过调查,终于找到了一个有效的解决办法: ...
- 执行Socket socket = new Socket(ip, port);时抛出个异常:android.os.NetworkOnMainThreadException解决办法
首先,确认你的android版本是4.0之后再用此方法解决,因为在4.0之后在主线程里面执行Http请求才会报这个错,也许是怕Http请求时间太长造成程序假死的情况吧.Android在4.0之前的版本 ...
- mixare的measureText方法在频繁调用时抛出“referencetable overflow max 1024”的解决方式
这几天在搞基于位置的AR应用,採用了github上两款开源项目: mixare android-argument-reality-framework 这两个项目实现机制大致同样.我选取的是androi ...
- C#解决关闭多线程的form主窗体时抛出ObjectDisposedException 异常
一.现象: 我在主窗体新建线程,使用子线程来处理接收到的数据,并且更新窗体显示内容,但关闭主窗体程序之后就程序就报错,如下所示: 二.分析问题: 由于新建线程的处理函数里边是一直死循环处理数据,虽然窗 ...
- django在启动时抛出Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 解决办法
1.适用场景 在启动某个服务的时候,比如python中django启动的时候8000端口被占用,导致无法启动服务. 2.解决办法 通过命令行找出端口对应的PID进程 C:\Users\micha> ...
- CAD调试时抛出“正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码”异常的解决方法
这些天重装了电脑Win10系统,安装了CAD2012和VS2012,准备进行软件开发.在调试程序的时候,CAD没有进入界面就抛出 “正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain ...
- 解决input为number类型时maxlength无效的问题
使用input数字number类型的时候maxlength无效,假设需要控制输入数量为18,可以用以下方式: 无效: <input type="text" maxlengt ...
随机推荐
- Python基础 | 字符串格式化输出及print()函数介绍
在写代码时,我们会经常与字符串打交道,Python中控制字符串格式通常有三种形式,分别是使用str%,str.format(),f-str,用法都差不多,但又有一些细微之差. 一起来看看吧~~~ 一. ...
- Data Management Tools(数据管理工具)《二》
(数据管理工具)<二> 点击跳转(数据管理工具)<一> 16.打包 # Process: 共享包 arcpy.SharePackage_management("&qu ...
- SpringBoot-Thymeleaf模板引擎
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想 ...
- Java(23)常用API二
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228415.html 博客主页:https://www.cnblogs.com/testero ...
- docsify + Gitee Pages服务搭建开源项目网站
前言 base-admin从开源至今,已经收获了2k Stat,而我们一直都没有一份像样的在线文档,最近写了一个博客园随笔备份Java脚本,将博客随笔备份到本地,格式是md文档格式,就有意去找将md文 ...
- Less-(38~41) 堆叠注入
首先申明,Less-(38~41)可以采取和Less-(1~4)相同的解法:(一一对应) 然而,他们的漏洞其实更大,我们可以做更多具有破坏性的事情. 代码审计: Less-(38~41): 41的$s ...
- kiyv Button参数属性
from kivy.uix.button import Button from kivy.uix.floatlayout import FloatLayout from kivy.app import ...
- Java:LinkedHashMap类小记
Java:LinkedHashMap类小记 对 Java 中的 LinkedHashMap类,做一个微不足道的小小小小记 概述 public class LinkedHashMap<K,V> ...
- 【好好编程-技术博客】微信小程序开发中前后端的交互
微信小程序开发中前后端的交互 微信小程序的开发有点类似与普通网页的开发,但是也不尽然相同.小程序的主要开发语言是JavaScript,开发同普通的网页开发有很大的相似性,对于前端开发者而言,从网页开发 ...
- Charles的简单用法
Charles的简单用法 一.抓电脑上 http 包 二.显示请求的 Request 和 Response 三.抓取电脑上 https 包 1.安装根证书 2.在钥匙串中启用根证书 3.配置哪些需要抓 ...