.net 从HTTP API流式传输JSON数据时报告错误

rnmwe5a2  于 2023-05-08  发布在  .NET
关注(0)|答案(2)|浏览(68)

这个问题特别与.NET 7+相关,但它也涉及HTTP基础知识,因此欢迎任何输入。还有:我倾向于在问一个新问题之前自己做很多谷歌搜索。如果我的google-fu再次离开了我,我很乐意接受我忽略的外部资源的链接。
我有一个返回IAsyncEnumerable<int>的MVC端点。让我们假设端点所做的是从数据库中的表中阅读行。数据库驱动程序支持使用IAsyncEnumerable从游标查询行,因此我基本上可以执行以下操作

[HttpGet]
public async IAsyncEnumerable<int> SomeEndpoint() {
  await foreach (var row in PerformSelectQuery())
    yield return int.Parse(row[0]);
}

到目前为止一切顺利:.NET和System.Text.Json处理得很好:它将Transfer-Encoding设置为chunked,并在从数据库返回第一行时立即开始写入响应。我喜欢这样,因为我不需要做任何缓冲,所以我不会浪费内存,并且客户端可以更快地开始解析结果(如果它支持的话)。
假设:我做了一个蹩脚的工作,列可能包含空值。这将导致int.Parse在90000行中的#24645行引发异常。
这是个问题
.NET只是杀死了连接。像Postman这样的客户表示,请求已被中止。我不能责怪他们:我们无法知道哪里出了问题。
我可以在foreach中捕获任何异常(或执行其他错误处理),如果需要,只需yield break,导致请求默默失败。同样,客户端不知道结果是不完整的,并且发生了错误。
我甚至不确定这是否比请求完全失败更好。至少,客户知道“出了什么问题”。
我目前的解决方案是将int(或任何我返回的值) Package 在一种Result/Option monad中。然后序列化到流而不是原始值。这样,我就可以捕获错误,从我的操作中得到它,然后决定是使用yield break中止,还是继续表。 Package 器的特定格式也很棘手(多态序列化,耶!)但不是这个问题的真正主题。
我还有其他选择吗?我读过一些关于Trailer头的文章,这似乎是正确的做法,但

  • 它显然很少或根本不支持现有客户端
  • 我只能想象(来自客户端的)用法有多怪异
  • 我不知道我将如何设置这些拖车在我的控制器的行动。“分块”都是在内部完成的,那么在我的foreach中,我如何将错误信息添加到预告片中呢?
  • 似乎没有报告错误的标准方法(gRPC有一点,但我没有实现gRPC服务)。
  • 最后,我仍然不能报告一个不同的HTTP状态代码,因为在HTTP中,这根本不是一个头。

编辑:我已经弄清楚了如何使用Response.AppendTrailer从我的函数中启用和编写trailers,但我需要支持HTTP/1.1,因为.NET根本不支持HTTP < 2的Trailers,这无论如何都不会起作用:(
有趣的是,我注意到这里:Clarification on how IAsyncEnumerable works with ASP.NET Web API两个人已经开始在评论中问同样的问题。没有任何回应。这是一个如此晦涩的东西吗?没有人将IAsyncEnumerable流式传输到客户端吗?还是每个人都在写完美的代码,永远不会失败?:D
有谁知道这种情况下的最佳实践吗?有没有人遇到过类似的问题?

gajydyqb

gajydyqb1#

我不认为这与HTTP有关。你的数据模型不能以你想要的清晰度来表示一个缺失的值;只要响应是语法上有效的JSON,HTTP就不在乎(在Content-Type: application/json意味着格式良好的响应的意义上)。我不认为寻找HTTP规范来表示中断响应的能力是正确的方法;这是一个数据建模问题。我也不同意这与响应流有任何关系;表示问题与同步序列化响应相同-C#中的int数组是JSON中的int数组。
所以,在回答:
我如何通知客户端,在流中可能有数千个结果,第832个有错误?
从我的Angular 来看,答案似乎是“无论你喜欢什么”。 有一个约束,即答案必须表示为语法上有效的JSON)

yyhrrdl8

yyhrrdl82#

你有几个选项,你似乎没有在问题中探索:
1.不要从PerformSelectQuery返回任何不是int的内容
1.如果您无法控制PerformSelectQuery的返回类型/值,请添加var row in PerformSelectQuery().Where(r => int.TryParse(r[0], out var _))
1.或者类似的东西:if (int.TryParse(row[0], out int i) yield return i;
1.如果PerformSelectQuery在出现错误时返回一个非int,请考虑将端点更改为返回IAsyncEnumerable<int?>,以便客户端能够在出现错误时理解为null。
如果你在这个问题中的伪代码没有告诉我们整个故事,考虑发布更多的代码。

相关问题