我正在开发Google Cloud Storage .NET客户端库。有三个功能(在.NET、我的客户端库和存储服务之间)以令人不愉快的方式组合在一起:
下载文件(在Google Cloud Storage术语中为对象)时,服务器会包含存储数据的哈希值。然后,我的客户端代码会根据已下载的数据验证该哈希值。
Google Cloud Storage的另一个功能是用户可以设置对象的Content-Encoding,并且当请求包含匹配的Accept-Encoding时,该功能包含在下载时的标头中。(暂时忽略请求不包含它时的行为...)
HttpClientHandler
可以自动透明地解压缩gzip(或deflate)内容。
当这三者结合在一起时,我们就会遇到麻烦。以下是一个简短但完整的程序,演示了这一点,但没有使用我的客户端库(并访问了一个公共可访问的文件):
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string url = "https://www.googleapis.com/download/storage/v1/b/"
+ "storage-library-test-bucket/o/gzipped-text.txt?alt=media";
var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip
};
var client = new HttpClient(handler);
var response = await client.GetAsync(url);
byte[] content = await response.Content.ReadAsByteArrayAsync();
string text = Encoding.UTF8.GetString(content);
Console.WriteLine($"Content: {text}");
var hashHeader = response.Headers.GetValues("X-Goog-Hash").FirstOrDefault();
Console.WriteLine($"Hash header: {hashHeader}");
using (var md5 = MD5.Create())
{
var md5Hash = md5.ComputeHash(content);
var md5HashBase64 = Convert.ToBase64String(md5Hash);
Console.WriteLine($"MD5 of content: {md5HashBase64}");
}
}
}
.NET Core 项目文件:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
</Project>
输出:
Content: hello world
Hash header: crc32c=T1s5RQ==,md5=xhF4M6pNFRDQnvaRRNVnkA==
MD5 of content: XrY7u+Ae7tCTyyK7j1rNww==
正如您所看到的,内容的MD5与X-Goog-Hash
头部分的MD5不同。(在我的客户端库中,我使用crc32c哈希,但显示相同的行为。)
这不是HttpClientHandler
中的错误 - 这是预期的,但验证哈希时很麻烦。基本上,我需要在解压缩之前和之后检查内容。我找不到任何方法来做到这一点。
为了澄清我的要求,我知道如何在HttpClient
中防止解压缩,并在从流中读取时进行解压缩,但我需要能够在不更改使用HttpClient
的代码的情况下执行此操作的能力。(有许多处理响应的代码,我只想在一个中心位置进行更改。)
我有一个计划,我已经完成了原型,至少目前为止它是有效的,但有点丑陋。它涉及创建一个三层处理程序:
HttpClientHandler
,禁用自动解压缩。- 一个新的处理程序,它将内容流替换为一个新的
Stream
子类,该子类委托给原始内容流,但在读取数据时对其进行哈希。 - 一个仅基于Microsoft
DecompressionHandler
代码的解压缩处理程序。
虽然这有效,但是有以下缺点:
- 开源许可证:检查我需要做什么才能基于MIT许可的Microsoft代码在我的存储库中创建新文件
- 实际上分叉了MS代码,这意味着我可能应该定期检查其中是否发现了任何错误
- Microsoft代码使用程序集的内部成员,因此它不能像预期的那样轻松移植。
如果Microsoft公开DecompressionHandler
,那将有很大帮助-但这可能比我需要的时间范围更长。
如果可能,我正在寻找替代方法 - 我错过了某些东西,使我可以在解压缩之前获取内容。我不想重新发明HttpClient
- 例如,响应通常是分块的,我不想涉及那方面的事情。我正在寻找一个非常特定的拦截点。
HttpClientHandler
在执行此操作,而是GCS。如果您请求一个Content-Encoding为gzip的文件,但没有指定Accept-Encoding: gzip,则它会为您解压缩,使用无Content-Encoding标头提供解压缩后的内容。(并仍包括压缩文件的哈希值。我知道,这很有问题...我不想涉及所有可能的怪癖在这个问题中,但如果您认为我应该提到,请告诉我。) - Jon Skeet