(多张图片打包为Zip返回前端下载) 记NetCore HttpClient.GetStreamAsync()返回只读流,Stream的Length属性不可用,报错的问题。
需求是做一个打包若干照片为.zip格式,返回给Vue前端以供下载。
照片地址存在于数据库中,文件存储在Minio中,地址是https的,我用httpClient下载,有个问题,SSL证书不正确,此处参考网上给出的答案已经解决。
报错如下:
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: PartialChain
at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
解决方案如下:
//绕过https证书
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true;
HttpClient client = new HttpClient(httpClientHandler);
参考https://www.cnblogs.com/GarsonZhang/p/13039342.html老哥的《.NetCore下多个文件流生成压缩文件》文章,把他的通过aws api下载流的内容替换为httpClient下载。
代码如下:
/// <summary>
/// 创建Zip压缩文件,网络图片
/// </summary>
/// <param name="urls"></param>
/// <returns></returns>
public static async Task<byte[]> CreateZipBytesAsync(List<string> urls)
{
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
int index = 1;
foreach (var url in urls)
{
//得到文件流
var picBytes = await DownloadImageAsync(url);
ZipArchiveEntry entry = zip.CreateEntry($"{index}.jpg");
#region 测试获取的文件流有没有问题
//FileStream fs = new FileStream("D:\\test\\" + index + ".jpg", FileMode.Create);
//fs.Write(picBytes, 0, picBytes.Length);
//fs.Dispose();
#endregion
using (Stream sw = entry.Open())
{
sw.Write(picBytes, 0, picBytes.Length);//将文件的字节写到$"{index}.jpg"中
}
index++;
}
InvokeWriteFile(zip);//重新计算压缩文件的大小
int nowPos = (int)ms.Position;
bytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(bytes, 0, bytes.Length);
ms.Position = nowPos;
}
return bytes;
}
}
private static void InvokeWriteFile(ZipArchive zipArchive)
{
foreach (MethodInfo method in zipArchive.GetType().GetRuntimeMethods())
{
if (method.Name == "WriteFile")
{
method.Invoke(zipArchive, new object[0]);
}
}
}
private static async Task<Stream> DownloadImageAsync(string url)
{
// 绕过https证书
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true;
//注意这里生成的只读流无法使用Length属性
HttpClient client = new HttpClient(httpClientHandler);
client.BaseAddress = new Uri(url);
var stream = await client.GetStreamAsync(url);
return stream;
}
/// 将 Stream 转成 byte[]
public byte[] StreamToBytes(Stream stream)
{
byte[] bytes = new byte[stream.Length];//这里会报错'((System.Net.Http.HttpBaseStream)stream).Length' threw an exception of type。。。
stream.Read(bytes, 0, bytes.Length);
// 设置当前流的位置为流的开始
stream.Seek(0, SeekOrigin.Begin);
return bytes;
}
然后便是各种百度,也没有找到正确的写法,最后bing,参考了https://stackoverflow.com/questions/41027999/how-to-use-httpclient-getstreamasync-method文章,修改DownloadImageAsync方法如下:
/// <summary>
/// 下载单张图片流
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static async Task<byte[]> DownloadImageAsync(string url)
{
#region 绕过https证书
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true;
#endregion
HttpClient client = new HttpClient(httpClientHandler);
using (var file = await client.GetStreamAsync(url).ConfigureAwait(false))
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
完整代码如下:
public class ZipHelper
{
/// <summary>
/// 创建Zip压缩文件,网络图片
/// </summary>
/// <param name="urls"></param>
/// <returns></returns>
public static async Task<byte[]> CreateZipBytesAsync(List<string> urls)
{
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
int index = 1;
foreach (var url in urls)
{
//得到文件流
var picBytes = await DownloadImageAsync(url);
ZipArchiveEntry entry = zip.CreateEntry($"{index}.jpg");//压缩文件内创建一个文件名为$"{index}.jpg",流是什么文件格式就用什么格式
#region 测试获取的文件流有没有问题
//FileStream fs = new FileStream("D:\\test\\" + index + ".jpg", FileMode.Create);
//fs.Write(picBytes, 0, picBytes.Length);
//fs.Dispose();
#endregion
using (Stream sw = entry.Open())
{
sw.Write(picBytes, 0, picBytes.Length);//将文件的字节写到$"{index}.jpg"中
}
index++;
}
InvokeWriteFile(zip);//重新计算压缩文件的大小
int nowPos = (int)ms.Position;
bytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(bytes, 0, bytes.Length);
ms.Position = nowPos;
}
return bytes;
}
}
private static void InvokeWriteFile(ZipArchive zipArchive)
{
foreach (MethodInfo method in zipArchive.GetType().GetRuntimeMethods())
{
if (method.Name == "WriteFile")
{
method.Invoke(zipArchive, new object[0]);
}
}
}
/// <summary>
/// 下载单张图片流
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static async Task<byte[]> DownloadImageAsync(string url)
{
#region 绕过https证书
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true;
#endregion
HttpClient client = new HttpClient(httpClientHandler);
using (var file = await client.GetStreamAsync(url).ConfigureAwait(false))
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
}
参考内容:
https://stackoverflow.com/questions/41027999/how-to-use-httpclient-getstreamasync-method (感谢!)
https://www.cnblogs.com/GarsonZhang/p/13039342.html (感谢!)