HttpClient:在运行时有条件地设置AcceptEncoding压缩

18
我们试图在客户端中实现由用户(在设置屏幕上)决定的可选gzip压缩,我们使用HttpClient,这样我们可以记录并比较一段时间内多个不同调用的性能。我们的第一次尝试是简单地有条件地添加标题,如下所示:
HttpRequestMessage request = new HttpRequestMessage(Method, Uri);
if (AcceptGzipEncoding)
{
     _client.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
}

//Send to the server
result = await _client.SendAsync(request);

//Read the content of the result response from the server
content = await result.Content.ReadAsStringAsync();

这样创建了正确的请求,但返回时未解压缩gzip响应,导致响应混乱。我发现在构建HttpClient时必须包括HttpClientHandler

HttpClient _client = new HttpClient(new HttpClientHandler
    { 
        AutomaticDecompression = DecompressionMethods.GZip
    });

这一切都很顺利,但我们希望在运行时更改客户端是否发送Accept-Encoding:gzip头信息,并且似乎没有任何方法可以访问或更改 HttpClient 构造函数之后传递给 HttpClientHandler 的值。此外,如果由 HttpClientHandler 定义,则修改 HttpRequestMessage 对象的标头对请求的标头没有任何影响。

有没有办法在不每次更改时重新创建 HttpClient 的情况下实现这一点?

编辑:我还尝试修改对 HttpClientHandler 的引用,以在运行时更改 AutomaticDecompression ,但会抛出以下异常:

此实例已启动一个或多个请求。 只能在发送第一个请求之前修改属性。


好奇一下,既然你已经看到自动解压有多方便了,那么为什么不在每次更改该设置时重新创建客户端呢?除非有非常好的理由,否则我会在这里这样做。 - Todd Menier
@ToddMenier 这是一个非常有意义的问题。我们正在考虑这个问题,但我认为这需要进行一些重组,因为同一个 HttpClient 被绑定到了多个区域中。由于这是一个从早期开发人员手中继承而来的中等规模应用程序,所以我们必须仔细管理更改。我们将在本周内对此进行研究。感谢您的评论和帮助。 - pcdev
4个回答

15

你已经接近完成第一个示例,只需要自己解压缩流。MS的GZipSteam可以帮助你完成这个任务:

HttpRequestMessage request = new HttpRequestMessage(Method, Uri);
if (AcceptGzipEncoding)
{
     _client.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
}

//Send to the server
result = await _client.SendAsync(request);

//Read the content of the result response from the server
using (Stream stream = await result.Content.ReadAsStreamAsync())
using (Stream decompressed = new GZipStream(stream, CompressionMode.Decompress))
using (StreamReader reader = new StreamReader(decompressed))
{
    content = reader.ReadToEnd();
}

1
嗯,看起来这个答案是有效的,除非服务器决定不压缩响应(或未配置),否则它会失败。不幸的是,没有简单的方法可以从HttpResponseMessage对象中获取“Content-Encoding”头信息请参阅此帖子,因此重新创建HttpClient似乎是唯一的方法。再次感谢您的建议。 - pcdev

9
如果您想使用相同的 HttpClient,并且只想为某些请求启用压缩,则无法使用自动解压缩。当启用自动解压缩时,框架还会重置响应的 Content-Encoding 标头。这意味着您无法确定响应是否真正被压缩。顺便说一下,如果您打开自动解压缩,响应的 Content-Length 标头也将与解压缩后的内容大小匹配。
因此,您需要手动解压缩内容。以下示例显示了 gzip 压缩内容的实现方法(如 @ToddMenier 的 response 中所示):
private async Task<string> ReadContentAsString(HttpResponseMessage response)
{
    // Check whether response is compressed
    if (response.Content.Headers.ContentEncoding.Any(x => x == "gzip")) 
    {
        // Decompress manually
        using (var s = await response.Content.ReadAsStreamAsync())
        {
            using (var decompressed = new GZipStream(s, CompressionMode.Decompress))
            {
                using (var rdr As New IO.StreamReader(decompressed))
                {
                    return await rdr.ReadToEndAsync();
                }
            }
        }
    else
        // Use standard implementation if not compressed
        return await response.Content.ReadAsStringAsync();
}

我们的代码使用NSCachedUrlResponse来节省带宽,但根据连接的服务器不同,有些脚本可能无法运行。在快速搜索了Stack Overflow之后,我找到了上述方法,这就是我代码所需要的全部。 - Code Knox

2

根据以上评论,重新创建HttpClient确实是唯一(健壮)的方法。手动解压缩可以实现,但似乎很难可靠/高效地确定内容是否已被编码以及确定是否应用解码。


0
我最近遇到了这个问题。我通过创建客户端并通知自动解压缩来解决它。
_client = new HttpClient(new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.Deflate })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接