当前位置: 首页 > news >正文

.NET WebClient 类下载部分文件会错误?可能是解压缩的锅

一直在使用 WebClient 下载文件,.NET 已经封装好,所以用起来代码非常简洁;但直到今天发现有一个文件一直不能正确下载下来。

本文介绍这个问题的原因和解决方法,更重要的是给出调查方法。


@TOC

本文所涉及到的域名已经过敏感信息处理,所以实际上你是无法访问到的;但这不影响本文对调查方法的描述。

问题

我原本是使用如下的代码去下载任意文件的(参数经过简化)。

private static async Task DownloadFileAsync()
{
    var url = "http://localhost:5000/walterlv-icon.svg";
    var fileName = @"C:\Users\lvyi\Desktop\TEST\walterlv-icon.svg";

    using var webClient = new WebClient();
    webClient.DownloadFile(new Uri(url), fileName);
}

现在,下载一个 svg 的时候,原本应该是如下的图片:

walterlv-icon.svg

然而实际上下载下来之后却是这样的:

walterlv-icon.svg

原本大小是 992 字节,实际下载下来后是 508 字节,而且固定是 508 字节。你可以通过右键复制图片地址,然后分别把两张图下载下来看。

调查

显然,WebClient 没有抛出任何异常,而且每次下载下来都是固定的 508 字节,说明肯定不是网络不通或程序提前退出导致的,也不是线程安全相关的问题。基本可以认定为问题出在服务器的配置,或者客户端的请求上。

使用其他“正常”下载器尝试

拿 Chrome 跑以上地址,拿专用下载工具跑以上地址,甚至是拿 Postman 跑以上地址,都可以成功显示或者下载到正确的图片。

这几乎可以肯定,问题出在 .NET 的 WebClient 上,可能是请求不对,或者对响应的后续处理不对。

使用 Postman 和 WebClient 对比测试

为了对比请求和响应,我使用的是 Fiddler 抓包。

WebClient 请求:

GET http://localhost:5000/walterlv-icon.svg HTTP/1.1
Host: localhost:5000
Connection: Keep-Alive

WebClient 响应(因为内含乱码,会让网页显示不正常,所以放截图):

WebClient 响应

Postman 请求:

GET http://localhost:5000/walterlv-icon.svg HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.22.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 05bb3d80-d7a7-4c0d-bdd1-9cd65d79ecab
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Postman 响应(因为内含乱码,会让网页显示不正常,所以放截图):

Postman 响应

请求和响应贴得很长,这可以让比较感兴趣的小伙伴仔细比较。但这里我直接给出我比较后的结论:

  1. Postman 的请求会发送比较多的头
  2. 两者的响应几乎相同(包括文件大小和内容)

由于响应几乎相同,所以实际上前面请求头的不同可以忽略了(至少说明返回的内容没有因为请求的不同而有所变化),我们能够拿到完整的整个文件。

那么问题基本确定就是在 WebClient 对这个响应的处理上了。

可以注意到 Postman 的请求中有 Accept-Encoding,两折的响应中都有 Content-Encoding,指定了 gzip。然而这是 Linux 中用来压缩文件的命令。响应中指定了内容编码方式为 gzip 是否意味着我们下载下来的文件实际上是一个 gzip 压缩文件呢?

于是我将下载下来的文件扩展名改为 gzip,用压缩文件打开,于是真的可以解压出来真实的图片。

于是确认问题的原因是 WebClient 在处理响应的时候没有根据 Content-Encoding 的值解压缩下载下来的文件。

解决

解决的思路:

  • 使 WebClient 支持下载文件后解压缩

使 WebClient 支持下载文件后解压缩

各种检查后发现,WebClient 竟然没有提供设置解压缩相关的属性。庆幸的是,在网上搜索 WebClientgzip 关键字后,找到了这一篇答案:.net - Automatically decompress gzip response via WebClient.DownloadData - Stack Overflow。

我们需要重写 WebClient.GetWebRequest 方法,然后改写 AutomaticDecompression 属性。此属性可以改成 gzipdeflatebr 或者它们的组合,这与 Postman 发请求时声明支持的值是完全一样的。

class AutoDecompressionWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri address)
    {
        var baseRequest = base.GetWebRequest(address);
        if (baseRequest is HttpWebRequest httpWebRequest)
        {
            httpWebRequest.AutomaticDecompression = DecompressionMethods.All;
        }
        return baseRequest;
    }
}

另外,也可以在拉取到响应的流后自己去做解压,可以参见:

  • .net - How do you download and extract a gzipped file with C#? - Stack Overflow

参考资料

  • .net - Automatically decompress gzip response via WebClient.DownloadData - Stack Overflow
  • c# - WebClient.DownloadFile File Corrupt - Stack Overflow
  • Download file using Webclient shows Wrong Data - CodeProject
  • [Solved] WebClient DownloadFile method downloads damaged PDF files - CodeProject

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • Unity3D 入门:为 Unity 的 C# 项目添加 dll 引用或安装 NuGet 包
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • 如何在终端和 PowerShell 中将一个命令自动重复执行多次
  • WPF:无法对元素“XXX”设置 Name 特性值“YYY”。“XXX”在元素“ZZZ”的范围内,在另一范围内定义它时,已注册了名称。
  • 一点点从坑里爬出来:如何正确打开 WPF 里的 Popup?
  • Windows Linux 系统中获取端口被哪个应用程序占用
  • 设置用户无需密码自动登录到 Windows 系统
  • 最简单的代码,让 WPF 支持响应式布局
  • 当无边框窗口被子窗口遮挡导致难以调节窗口大小时,可通过处理 NCHITTEST 消息重新支持调节窗口大小
  • 如何给 GitHub Pages 配置多个域名?
  • 通过子类化窗口(SubClass)来为现有的某个窗口添加新的窗口处理程序(或者叫钩子,Hook)
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • git 乱改你的换行符?一句话设置让 git 不再碰你某个文件的换行符
  • [ JavaScript ] 数据结构与算法 —— 链表
  • 2017 年终总结 —— 在路上
  • 2017前端实习生面试总结
  • 30天自制操作系统-2
  • Android单元测试 - 几个重要问题
  • Cookie 在前端中的实践
  • JavaScript对象详解
  • JS笔记四:作用域、变量(函数)提升
  • k个最大的数及变种小结
  • QQ浏览器x5内核的兼容性问题
  • React-redux的原理以及使用
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • 安卓应用性能调试和优化经验分享
  • 对超线程几个不同角度的解释
  • 回顾 Swift 多平台移植进度 #2
  • 记一次用 NodeJs 实现模拟登录的思路
  • 聊聊directory traversal attack
  • 小试R空间处理新库sf
  • 用jQuery怎么做到前后端分离
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • ​Python 3 新特性:类型注解
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • #define与typedef区别
  • (175)FPGA门控时钟技术
  • (八)c52学习之旅-中断实验
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (九十四)函数和二维数组
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • *p++,*(p++),*++p,(*p)++区别?
  • .net程序集学习心得
  • .NET简谈设计模式之(单件模式)
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?
  • @RequestParam,@RequestBody和@PathVariable 区别
  • [100天算法】-不同路径 III(day 73)
  • [autojs]autojs开关按钮的简单使用
  • [C++]C++入门--引用
  • [C++从入门到精通] 14.虚函数、纯虚函数和虚析构(virtual)
  • [C++进阶篇]STL中vector的使用
  • [ffmpeg] x264 配置参数解析
  • [Oh My C++ Diary]善用三目运算符(a?b:c)