用于发布列表的 HttpContent<ArraySegment<byte>> 通过 HttpClient

问题描述 投票:0回答:1

我需要发布 HTTP API 请求,其正文为

List<ArraySegment<byte>>
(通常的固定标头 + 可变中间 + 固定页脚内容)。 .NET Socket 自古以来就可以发送
List<ArraySegment<byte>>
,但我在 .NET HttpClient 的上下文中找不到类似的功能。

为了发送正文,

HttpClient
需要一个HttpRequestMessage,其中要发送的正文在Content属性(类型为HttpContent)中给出。

HttpContent
及其任何后代都没有带有
List<ArraySegment<byte>>
或类似内容的构造函数;它们只处理单个数组切片。这同样适用于 MemoryStream,否则可以通过
HttpContent
后代之一使用。

此时前景似乎黯淡:我可以将正文片段合并到单个字节缓冲区中进行发送,或者在

HttpContent
之上实现
List<ArraySegment<byte>>

这两种选择似乎都不是特别有吸引力。消息正文可能有数十兆字节,因此不必要的复制会给内存子系统带来不必要的负载并降低性能(服务器位于同一物理主机上,并且通过环回设备或通过虚拟网卡到达)。

HttpContent
MemoryStream
拥有不必要的庞大且复杂的 API,实施起来似乎具有挑战性。

有没有更简单的方法来发布

List<ArraySegment<byte>>
?如果没有,有人根据经验知道哪条路线是最不痛苦的路线 - 实施
HttpContent
或实施
MemoryStream
? (后者的 API 稍大,但看起来更简单、更容易理解。)

P.S.:把事情放在上下文中:我正在重新设计一个我们用来测量 API 服务器性能以及功能和压力测试的工具;它目前基于 .NET

Socket
API,但使用普通 TCP 套接字实际上将其限制为 HTTP 1.1。这就是
HttpClient
发挥作用的地方。

c# .net dotnet-httpclient httpcontent
1个回答
0
投票

好消息是,实现HttpContent以在HttpRequestMessage中使用非常简单明了;只需要实现 TryComputeLength() 以及将内容序列化到 (TCP) 流的三个函数。

围绕

LoadIntoBuffer()
CreateReadStream()
和朋友的令人费解的 - 而且大多没有记录 - 功能系统并没有发挥作用,
HttpContent
文档中的半官方评论也说明了这一点。

这是一个最小的工作示例实现:

class ArraySegmentsContent : HttpContent
{
    private readonly List<ArraySegment<byte>> m_segments;

    public ArraySegmentsContent (List<ArraySegment<byte>> segments)
    {
        m_segments = segments;
    }

    protected override Task SerializeToStreamAsync (Stream stream, TransportContext? context)
    {
        return SerializeToStreamAsync(stream, context, default);
    }

    protected override async Task SerializeToStreamAsync (Stream stream, TransportContext? context, CancellationToken cancellationToken)
    {
        foreach (var segment in m_segments)
        {
            await stream.WriteAsync(segment, cancellationToken);
        }
    }

    protected override void SerializeToStream (Stream stream, TransportContext? context, CancellationToken cancellationToken)
    {
        foreach (var segment in m_segments)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return;
            }

            stream.Write(segment);
        }
    }

    protected override bool TryComputeLength (out long length)
    {
        length = m_segments.Sum(segment => (long) segment.Count);

        return true;
    }
}

我提到的奇怪之处在于,采用取消令牌的异步函数是由

HttpContent
实现的,而对于不采用此类令牌的函数来说;取消令牌掉到地板上。通常情况下,情况会相反:基本实现函数将是需要取消标记的函数,而没有标记的函数将使用
CancellationToken.None
来调用它。

缺少的花里胡哨的是覆盖剩余的虚拟来抛出

NotImplementedException
以及在没有任何同步/序列化逻辑的情况下围绕
List<>
的可变性的棘手问题。

我使用

List<ArraySegment<byte>>
是因为它是 Socked.SendAsync() 所采用的,但更现实的是它应该是
IReadOnlyCollection<ArraySegment<byte>>
或类似的东西,结合防止更改底层字节缓冲区的方法,直到发送 HTTP 请求.

通过这个小类,我可以通过引用单个处方 300 次,加上用于获取的微小间隙片段,来表示包含 300 个电子处方(相关 API 允许的最大数量)的 30 MB 请求,以进行性能/负载/压力测试。照顾框架并为每个处方提供唯一的 ID。总共约为 200 KB,而不是 30 MB,并且在这 200 KB 中,100 KB(规定)可以由所有并发发送者共享。这意味着即使模拟 100 个并发客户端,所有数据也能整齐地放入所涉及 CPU 的 L2 缓存中。

© www.soinside.com 2019 - 2024. All rights reserved.