我正在使用HttpClient
将文件上传到需要Content-MD5
标头的API。如何在发送之前从HttpClient
获取完整的请求正文,以便我可以对内容运行MD5
并在请求标头中使用它?请注意,我还需要在多部分表单数据(即Content-Disposition
)和每个部分中的所有其他标头之间包含标头。
我正在使用以下代码,取自this answer。
private System.IO.Stream Upload(string url, string param1, Stream fileStream, byte [] fileBytes)
{
HttpContent stringContent = new StringContent(param1);
HttpContent fileStreamContent = new StreamContent(fileStream);
HttpContent bytesContent = new ByteArrayContent(fileBytes);
using (var client = new HttpClient())
using (var formData = new MultipartFormDataContent())
{
formData.Add(stringContent, "param1", "param1");
formData.Add(fileStreamContent, "file1", "file1");
formData.Add(bytesContent, "file2", "file2");
var response = client.PostAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
{
return null;
}
return response.Content.ReadAsStreamAsync().Result;
}
}
答案 0 :(得分:2)
现在,我提前承认我没有测试过这段代码,因为我没有设置测试站点。但是,我已经测试了在LINQPad中发布数据并且代码没有错误并且MD5哈希设置。以下内容适用于您想要做的事情:
private System.IO.Stream Upload(string url, string param1, Stream fileStream, byte[] fileBytes)
{
HttpContent stringContent = new StringContent(param1);
HttpContent fileStreamContent = new StreamContent(fileStream);
HttpContent bytesContent = new ByteArrayContent(fileBytes);
using (HttpClient client = new HttpClient())
{
using (MultipartFormDataContent formData = new MultipartFormDataContent())
{
formData.Add(stringContent, "param1", "param1");
formData.Add(fileStreamContent, "file1", "file1");
formData.Add(bytesContent, "file2", "file2");
using (MD5 md5Hash = MD5.Create())
{
formData.Headers.ContentMD5 = md5Hash.ComputeHash(formData.ReadAsByteArrayAsync().Result);
}
var response = client.PostAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
{
return null;
}
return response.Content.ReadAsStreamAsync().Result;
}
}
}
答案 1 :(得分:2)
你的文件有多大? Adam回答的问题是它将文件的全部内容缓冲在内存中。这可能会导致程序因大型文件而耗尽内存,或因磁盘交换过多而性能不佳。
事实上,我发现即使是MultipartFormDataContent.ReadAsStreamAsync()也会将整个内容缓冲到内存中(可能是通过调用MultipartFormDataContent.LoadIntoBufferAsync())。使用StreamContent.ReadAsStreamAsync()时似乎不存在这个问题,所以它似乎是唯一的解决方案,如果遇到内存问题就是编写自己的MultipartFormDataContent实现,它不会缓冲整个内容但是会使用StreamContent.ReadAsStreamAsync()。
注意,我发现MultipartFormDataContent.CopyToAsync()不会缓冲内存中的所有内容,前提是接收流没有。编写一个作为一种管道的Stream实现可能是值得的,其中写入它的任何字节都被md5Hash.ComputeHash(Stream)立即使用。
编辑:这些是我在.NET 4.0上的经验。我听说.NET 4.5在客户端缓冲方面的行为有所不同,所以我不确定MultipartFormDataContent.ReadAsStreamAsync()是如何对它执行的。答案 2 :(得分:1)
使用HttpClient处理这种类型的一般方法是使用HttpMessageHandler,就像您可能需要做的其他工作一样。添加授权标题,签署消息等。
我还使用基于任务的语法重写了这个,因为它更具有理想性的HttpClient - 调用者可以根据需要调用.Result。
private await Task<System.IO.Stream> Upload(string url, string param1, Stream fileStream, byte[] fileBytes)
{
HttpContent stringContent = new StringContent(param1);
HttpContent fileStreamContent = new StreamContent(fileStream);
HttpContent bytesContent = new ByteArrayContent(fileBytes);
var handler = new HttpClientHandler();
var md5Handler = new RequestContentMd5Handler();
md5Handler.InnerHandler = handler;
using (HttpClient client = new HttpClient(md5Handler))
{
using (MultipartFormDataContent formData = new MultipartFormDataContent())
{
formData.Add(stringContent, "param1", "param1");
formData.Add(fileStreamContent, "file1", "file1");
formData.Add(bytesContent, "file2", "file2");
using (var response = await client.PostAsync(url, formData))
{
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadAsStreamAsync();
}
}
}
}
此外,在每个请求上重新创建HttpClient通常都是不好的做法(请参阅What is the overhead of creating a new HttpClient per call in a WebAPI client?等),但我在此处留下了与问题风格一致的内容。
这是使用的处理程序......
/// <summary>
/// Handler to assign the MD5 hash value if content is present
/// </summary>
public class RequestContentMd5Handler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content == null)
{
return await base.SendAsync(request, cancellationToken);
}
await request.Content.AssignMd5Hash();
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
扩展方法......
/// <summary>
/// Compute and assign the MD5 hash of the content.
/// </summary>
/// <param name="httpContent"></param>
/// <returns></returns>
public static async Task AssignMd5Hash(this HttpContent httpContent)
{
var hash = await httpContent.ComputeMd5Hash();
httpContent.Headers.ContentMD5 = hash;
}
/// <summary>
/// Compute the MD5 hash of the content.
/// </summary>
/// <param name="httpContent"></param>
/// <returns></returns>
public static async Task<byte[]> ComputeMd5Hash(this HttpContent httpContent)
{
using (var md5 = MD5.Create())
{
var content = await httpContent.ReadAsStreamAsync();
var hash = md5.ComputeHash(content);
return hash;
}
}
可以轻松地对各个部件进行单元测试。