gpt4 book ai didi

c# - 是否可以在HttpClient中访问解压前的压缩数据?

转载 作者:IT王子 更新时间:2023-10-29 03:44:33 26 4
gpt4 key购买 nike

我正在研究 Google Cloud Storage .NET clientlibrary .具有三个功能(在 .NET 之间,我的客户端库和存储服务)结合在一个不愉快的方式:

  • 下载文件(Google Cloud Storage 中的对象术语),服务器包括存储数据的散列。我的客户端代码然后根据它的数据验证该散列下载。

  • Google Cloud Storage 的一个单独功能是用户可以设置对象的内容编码,并将其作为下载时的 header ,当请求包含匹配时接受编码。 (目前,让我们忽略请求不包括...)

  • 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 与 MD5 不同X-Goog-Hash header 的一部分。 (在我的客户端库中,我使用的是 crc32c哈希,但这显示了相同的行为。)

这不是 HttpClientHandler 中的错误 - 这是意料之中的,但很痛苦当我想验证哈希时。基本上,我需要在解压前后的内容。我找不到任何办法这样做。

为了稍微阐明我的要求,我知道如何在 HttpClient 中阻止解压缩,而是在从流中读取时解压缩 - 但我需要能够在不更改任何代码的情况下执行此操作使用来自 HttpClient 的结果 HttpResponseMessage。 (有很多处理响应的代码,我只想在一个中心位置进行更改。)

我有一个计划,我已经对其进行了原型(prototype)设计并且就我而言它是有效的到目前为止发现,但有点难看。它涉及创建一个三层处理程序:

  • HttpClientHandler 禁用自动解压缩。
  • 用新的 Stream 子类替换内容流的新处理程序它委托(delegate)给原始内容流,但在读取数据时对其进行哈希处理。
  • 仅解压缩处理程序,基于 Microsoft DecompressionHandler代码。

虽然这可行,但它有以下缺点:

  • 开源许可:准确检查我需要按顺序做什么根据麻省理工学院许可在我的仓库中创建一个新文件微软代码
  • 有效地 fork MS 代码,这意味着我可能应该定期检查是否发现任何错误
  • Microsoft 代码使用程序集的内部成员,因此它没有尽可能干净地移植。

如果 Microsoft 将 DecompressionHandler 公开,那将有助于很多 - 但这可能比我需要的时间更长。

如果可能的话,我正在寻找一种替代方法 -我错过的东西让我了解了之前的内容减压。我不想重新发明 HttpClient - 响应例如,经常被分 block ,我不想进入事情的那一面。这是一个非常具体的拦截点我在找。

最佳答案

看看@Michael 所做的事情给了我我所缺少的提示。获得压缩内容后,您可以使用 CryptoStreamGZipStreamStreamReader 读取响应,而无需将响应加载到内存中。 CryptoStream 将在解压缩和读取压缩内容时对其进行哈希处理。将 StreamReader 替换为 FileStream,您可以将数据写入文件,同时占用最少的内存 :)

using System;
using System.IO;
using System.IO.Compression;
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.None
};
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");

var response = await client.GetAsync(url);
var hashHeader = response.Headers.GetValues("X-Goog-Hash").FirstOrDefault();
Console.WriteLine($"Hash header: {hashHeader}");
string text = null;
using (var md5 = MD5.Create())
{
using (var cryptoStream = new CryptoStream(await response.Content.ReadAsStreamAsync(), md5, CryptoStreamMode.Read))
{
using (var gzipStream = new GZipStream(cryptoStream, CompressionMode.Decompress))
{
using (var streamReader = new StreamReader(gzipStream, Encoding.UTF8))
{
text = streamReader.ReadToEnd();
}
}
Console.WriteLine($"Content: {text}");
var md5HashBase64 = Convert.ToBase64String(md5.Hash);
Console.WriteLine($"MD5 of content: {md5HashBase64}");
}
}
}
}

输出:

Hash header: crc32c=T1s5RQ==,md5=xhF4M6pNFRDQnvaRRNVnkA==
Content: hello world
MD5 of content: xhF4M6pNFRDQnvaRRNVnkA==

答案的 V2

阅读 Jon 的回复和更新后的答案后,我有了以下版本。几乎相同的想法,但我将流媒体移动到我注入(inject)的特殊 HttpContent 中。不是很漂亮,但想法是存在的。

using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
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.None
};
var client = new HttpClient(new Intercepter(handler));
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");

var response = await client.GetAsync(url);
var hashHeader = response.Headers.GetValues("X-Goog-Hash").FirstOrDefault();
Console.WriteLine($"Hash header: {hashHeader}");
HttpContent content1 = response.Content;
byte[] content = await content1.ReadAsByteArrayAsync();
string text = Encoding.UTF8.GetString(content);
Console.WriteLine($"Content: {text}");
var md5Hash = ((HashingContent)content1).Hash;
var md5HashBase64 = Convert.ToBase64String(md5Hash);
Console.WriteLine($"MD5 of content: {md5HashBase64}");
}

public class Intercepter : DelegatingHandler
{
public Intercepter(HttpMessageHandler innerHandler) : base(innerHandler)
{
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
response.Content = new HashingContent(await response.Content.ReadAsStreamAsync());
return response;
}
}

public sealed class HashingContent : HttpContent
{
private readonly StreamContent streamContent;
private readonly MD5 mD5;
private readonly CryptoStream cryptoStream;
private readonly GZipStream gZipStream;

public HashingContent(Stream content)
{
mD5 = MD5.Create();
cryptoStream = new CryptoStream(content, mD5, CryptoStreamMode.Read);
gZipStream = new GZipStream(cryptoStream, CompressionMode.Decompress);
streamContent = new StreamContent(gZipStream);
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) => streamContent.CopyToAsync(stream, context);
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}

protected override Task<Stream> CreateContentReadStreamAsync() => streamContent.ReadAsStreamAsync();

protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
streamContent.Dispose();
gZipStream.Dispose();
cryptoStream.Dispose();
mD5.Dispose();
}
}
finally
{
base.Dispose(disposing);
}
}

public byte[] Hash => mD5.Hash;
}
}

关于c# - 是否可以在HttpClient中访问解压前的压缩数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47324282/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com