Fork me on GitHub

(多张图片打包为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 (感谢!)

posted @ 2021-06-16 13:28  秋刀鱼de滋味  阅读(812)  评论(0编辑  收藏  举报