记录一个我认为是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. Python基础 | 字符串格式化输出及print()函数介绍

    在写代码时,我们会经常与字符串打交道,Python中控制字符串格式通常有三种形式,分别是使用str%,str.format(),f-str,用法都差不多,但又有一些细微之差. 一起来看看吧~~~ 一. ...

  2. Data Management Tools(数据管理工具)《二》

    (数据管理工具)<二> 点击跳转(数据管理工具)<一> 16.打包 # Process: 共享包 arcpy.SharePackage_management("&qu ...

  3. SpringBoot-Thymeleaf模板引擎

    模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想 ...

  4. Java(23)常用API二

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228415.html 博客主页:https://www.cnblogs.com/testero ...

  5. docsify + Gitee Pages服务搭建开源项目网站

    前言 base-admin从开源至今,已经收获了2k Stat,而我们一直都没有一份像样的在线文档,最近写了一个博客园随笔备份Java脚本,将博客随笔备份到本地,格式是md文档格式,就有意去找将md文 ...

  6. Less-(38~41) 堆叠注入

    首先申明,Less-(38~41)可以采取和Less-(1~4)相同的解法:(一一对应) 然而,他们的漏洞其实更大,我们可以做更多具有破坏性的事情. 代码审计: Less-(38~41): 41的$s ...

  7. kiyv Button参数属性

    from kivy.uix.button import Button from kivy.uix.floatlayout import FloatLayout from kivy.app import ...

  8. Java:LinkedHashMap类小记

    Java:LinkedHashMap类小记 对 Java 中的 LinkedHashMap类,做一个微不足道的小小小小记 概述 public class LinkedHashMap<K,V> ...

  9. 【好好编程-技术博客】微信小程序开发中前后端的交互

    微信小程序开发中前后端的交互 微信小程序的开发有点类似与普通网页的开发,但是也不尽然相同.小程序的主要开发语言是JavaScript,开发同普通的网页开发有很大的相似性,对于前端开发者而言,从网页开发 ...

  10. Charles的简单用法

    Charles的简单用法 一.抓电脑上 http 包 二.显示请求的 Request 和 Response 三.抓取电脑上 https 包 1.安装根证书 2.在钥匙串中启用根证书 3.配置哪些需要抓 ...