blob字段乱码怎么处理_下载的附件名总乱码?你该去读一下 RFC 文档了!

纸上得来终觉浅,绝知此事要躬行

Web 开发过程中,相信大家都遇到过附件下载的场景,其中,各浏览器下载后的文件名中文乱码问题或许一度让你苦恼不已。

网上搜索一下,大部分都是通过Request Headers中的UserAgent字段来判断浏览器类型,根据不同的浏览器做不同的处理,类似下面的代码:

// MicroSoft Browser
if (agent.contains("msie") || agent.contains("trident") || agent.contains("edge")) {
  // filename 特殊处理
}
// firefox
else if (agent.contains("firefox")) {
  // filename 特殊处理
}
// safari
else if (agent.contains("safari")) {
  // filename 特殊处理
}
// Chrome
else if (agent.contains("chrome")) {
  // filename 特殊处理
}
// 其他
else{
 // filename 特殊处理
}
//最后把特殊处理后的文件名放到head里
response.setHeader("Content-Disposition",
                    "attachment;fileName=" + filename);

不过,这样的代码看起来很魔幻,为什么每个浏览器的处理方式都不一样?难道每次新出一个浏览器都要做兼容吗?就没有一个统一标准来约束一下这帮浏览器吗?

带着这个疑惑,我翻阅了 RFC 文档,最终得出了一个优雅的解决方案:

// percentEncodedFileName 为百分号编码后的文件名
response.setHeader("Content-disposition",
        "attachment;filename=" + percentEncodedFileName +
                ";filename*=utf-8''" + percentEncodedFileName);

经过测试,这段响应头可以兼容市面上所有主流浏览器,由于是 HTTP 协议范畴,所以语言无关。只要按这个规则设置响应头,就能一劳永逸地解决恼人的附件名中文乱码问题。

接下来课代表带大家抽丝剥茧,通过阅读 RFC 文档,还原一下这个响应头的产出过程。

1. Content-Disposition

一切要从 RFC 6266[1] 开始,在这份文档中,介绍了Content-Disposition响应头,其实它并不属于HTTP标准,但是因为使用广泛,所以在该文档中进行了约束。它的语法格式如下:

content-disposition = "Content-Disposition" ":"
                            disposition-type *( ";" disposition-parm )

     disposition-type    = "inline" | "attachment" | disp-ext-type
                         ; case-insensitive
     disp-ext-type       = token

     disposition-parm    = filename-parm | disp-ext-parm

     filename-parm       = "filename" "=" value
                         | "filename*" "=" ext-value

其中的disposition-type有两种:

  • inline 代表默认处理,一般会在页面展示
  • attachment 代表应该被保存到本地,需要配合设置filenamefilename*

注意到disposition-parm中的filenamefilename*,文档规定:这里的信息可以用于保存的文件名。

它俩的区别在于,filename 的 value 不进行编码,而filename*遵从 RFC 5987[2]中定义的编码规则:

Producers MUST use either the "UTF-8" ([RFC3629]) or the "ISO-8859-1"
   ([ISO-8859-1]) character set.

由于filename*是后来才定义的,许多老的浏览器并不支持,所以文档规定,当二者同时出现在头字段中时,需要采用filename*,忽略filename

至此,响应头的骨架已经呼之欲出了,摘录 [RFC 6266] 中的示例如下:

 Content-Disposition: attachment;
                      filename="EURO rates";
                      filename*=utf-8''%e2%82%ac%20rates

这里对filename*=utf-8''%e2%82%ac%20rates做一下说明,这个写法乍一看可能会觉得很奇怪,它其实是用单引号作为分隔符,将等号右边分成了三部分:第一部分是字符集(utf-8),中间部分是语言(未填写),最后的%e2%82%ac%20rates代表了实际值。对于这部分的组成,在RFC 2231[3].section 4 中有详细说明:

 A single quote is used to
   separate the character set, language, and actual value information in
   the parameter value string, and an percent sign is used to flag
   octets encoded in hexadecimal.

2.PercentEncode

PercentEncode 又叫 Percent-encoding 或 URL encoding.

正如前文所述,filename*遵守的是[RFC 5987] 中定义的编码规则,在[RFC 5987] 3.2中定义了必须支持的字符集:

recipients implementing this specification 
MUST support the character sets "ISO-8859-1" and "UTF-8".

并且在[RFC 5987] 3.2.1规定,百分号编码遵从 RFC 3986[4].section 2.1中的定义,摘录如下:

A percent-encoding mechanism is used to represent a data octet in a
component when that octet's corresponding character is outside the
allowed set or is being used as a delimiter of, or within, the
component.  A percent-encoded octet is encoded as a character
triplet, consisting of the percent character "%" followed by the two
hexadecimal digits representing that octet's numeric value.  For
example, "%20" is the percent-encoding for the binary octet
"00100000" (ABNF: %x20), which in US-ASCII corresponds to the spacecharacter (SP).  Section 2.4 describes when percent-encoding and
decoding is applied.

注意了,[RFC 3986] 明确规定了空格 会被百分号编码为%20

而在另一份文档 RFC 1866[5].Section 8.2.1 The form-urlencoded Media Type 中却规定:

The default encoding for all forms is `application/x-www-form-
   urlencoded'. A form data set is represented in this media type as
   follows:
        1. The form field names and values are escaped: space
        characters are replaced by `+', and then reserved characters
        are escaped as per [URL]

这里要求application/x-www-form-urlencoded类型的消息中,空格要被替换为+,其他字符按照[URL]中的定义来转义,其中的[URL]指向的是RFC 1738[6] 而它的修订版中和 URL 有关的最新文档恰恰就是 [RFC 3986]

这也就是为什么很多文档中描述空格(white space)的百分号编码结果都是 +%20,如:

w3schools:URL encoding normally replaces a space with a plus (+) sign or with %20.

MDN:Depending on the context, the character ' ' is translated to a '+' (like in the percent-encoding version used in an application/x-www-form-urlencoded message), or in '%20' like on URLs.

那么问题来了,开发过程中,对于空格符的百分号编码我们应该怎么处理?

课代表建议大家遵循最新文档,因为 [RFC 1866] 中定义的情况仅适用于application/x-www-form-urlencoded类型, 就百分号编码的定义来说,我们应该以 [RFC 3986] 为准,所以,任何需要百分号编码的地方,都应该将空格符 百分号编码为%20,stackoverflow 上也有支持此观点的答案:When to encode space to plus (+) or %20?[7]

3. 代码实践

有了理论基础,代码写起来就水到渠成了,直接上代码:

@GetMapping("/downloadFile")
public String download(String serverFileName, HttpServletRequest request, HttpServletResponse response) throws IOException {

    request.setCharacterEncoding("utf-8");
    response.setContentType("application/octet-stream");

    String clientFileName = fileService.getClientFileName(serverFileName);
    // 对真实文件名进行百分号编码
    String percentEncodedFileName = URLEncoder.encode(clientFileName, "utf-8")
            .replaceAll("\\+", "%20");

    // 组装contentDisposition的值
    StringBuilder contentDispositionValue = new StringBuilder();
    contentDispositionValue.append("attachment; filename=")
            .append(percentEncodedFileName)
            .append(";")
            .append("filename*=")
            .append("utf-8''")
            .append(percentEncodedFileName);
    response.setHeader("Content-disposition",
            contentDispositionValue.toString());
    
    // 将文件流写到response中
    try (InputStream inputStream = fileService.getInputStream(serverFileName);
         OutputStream outputStream = response.getOutputStream()
    ) {
        IOUtils.copy(inputStream, outputStream);
    }

    return "OK!";
}

代码很简单,其中有两点需要说明一下:

  1. URLEncoder.encode(clientFileName, "utf-8")方法之后,为什么还要.replaceAll("\\+", "%20")

    正如前文所述,我们已经明确,任何需要百分号编码的地方,都应该把 空格符编码为 %20,而URLEncoder这个类的说明上明确标注其会将空格符转换为+:

    The space character "   " is converted into a plus sign "{@code +}".

    其实这并不怪 JDK,因为它的备注里说明了其遵循的是application/x-www-form-urlencoded( PHP 中也有这么一个函数,也是这么个套路)

    Translates a string into {@code application/x-www-form-urlencoded} format using a specific encoding scheme. This method uses the

    所以这里我们用.replaceAll("\\+", "%20")+号处理一下,使其完全符合 [RFC 3986] 的百分号编码规范。这里为了方便说明问题,把所有操作都展现出来了。当然,你完全可以自己实现一个PercentEncoder类,丰俭由人。

  2. [RFC 6266] 标准中filename=value是不需要编码的,这里的filename=后面的 value 为什么要百分号编码?

    回顾 [RFC 6266] 文档, filenamefilename*同时出现时取后者,浏览器太老不支持新标准时取前者。

    目前主流的浏览器都采用自升级策略,所以大部分都支持新标准------除了老版本IE。老版本的IE对 value 的处理策略是 进行百分号解码 并使用。所以这里专门把filename=value进行百分号编码,用来兼容老版本 IE。

    PS:课代表实测 IE11 及 Edge 已经支持新标准了。

4. 浏览器测试

根据下图 statcounter 统计的 2019 年中国市场浏览器占有率,课代表设计了一个包含中文,英文,空格的文件名 下载-down test .txt用来测试

d9bfbea505132757304502ea19c7feea.png

测试结果:

BrowserVersionpass
Chrome84.0.4147.125true
UCV6.2.4098.3true
Safari13.1.2true
QQ Browser10.6.1(4208)true
IE7-11true
Firefox79.0true
Edge44.18362.449.0true
360安全浏览器1212.2.1.362.0true
Edge(chromium)84.0.522.59true

根据测试结果可知:基本已经能够兼容市面上所有主流浏览器了。

5.总结

回顾本文内容,其实就是浏览器兼容性问题引发的附件名乱码,为了解决这个问题,查阅了两类标准文档:

  1. HTTP 响应头相关标准

    [RFC 6266]、[RFC 1866]

  2. 编码标准

    [RFC 5987]、[RFC 2231]、[3986]、[1738]

我们以 [RFC 6266] 为切入点,全文总共引用了 6 个 [RFC] 相关文档,引用都标明了出处,感兴趣的同学可以跟着文章思路阅读一下原文档,相信你会对这个问题有更深入的理解。文中代码已上传 github[8]

最后不禁要感叹一下:规范真是个好东西,它就像 Java 语言中的 interface,只制定标准,具体实现留给大家各自发挥。

参考资料

[1]

RFC 6266: https://tools.ietf.org/html/rfc6266

[2]

RFC 5987: https://tools.ietf.org/html/rfc5987

[3]

RFC 2231: https://tools.ietf.org/html/rfc2231

[4]

RFC 3986: https://tools.ietf.org/html/rfc3986

[5]

RFC 1866: https://tools.ietf.org/html/rfc1866

[6]

RFC 1738: https://tools.ietf.org/html/rfc1738

[7]

When to encode space to plus (+) or %20?: https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20

[8]

课代表的 github: https://github.com/zhengxl5566/springboot-demo

精彩推荐

强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!冒着被开除的风险也要给大家看看看这份SpringCloud 总结微服务 2.0 技术栈选型手册天天在用Stream,那你知道如此强大的Stream的实现原理吗?

c60a2c0408a8daef8a344c256a137f2a.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/522155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI ProCon倒计时6天:60+业内技术专家,探秘9大核心技术专题

2019 AI ProCon“598元学生票”限量抢购中!3日通票团购倒计时6天,扫码查看▲2018 年,由 CSDN 举办的第一届 AI 开发者大会喊出“只讲技术,拒绝空谈”,两天会议时间,国内外几十家顶尖科技企业讲述了其主流技…

工作流实战_28_flowable 任务多实例

原文连接:https://blog.csdn.net/liuwenjun05101/article/details/103680299 项目地址:https://gitee.com/lwj/flowable.git 分支flowable-base 任务多实例由2种形式: 第1种场景:当多实例中的每一个势力都办理完任务后&#xff0…

架构师如何应对复杂业务场景?领域建模的实战案例解析

摘要: 阿里妹导读:你还在用面向对象的语言写面向过程的代码吗?你是否正在被复杂的业务逻辑折磨?是否有时觉得应用开发没意思、没挑战、技术含量低?其实,应用开发一点都不简单,也不无聊&#xff…

【深圳云栖大会】阿里云弹性计算ESSD云盘产品全面解析

摘要: 2018年3月29日,在深圳云栖大会弹性计算技术专场上,来自阿里云弹性计算产品专家崆闻做了主题为《百万级别IOPS云盘产品全面解析》的技术分享,主要就阿里云新一代ESSD云盘的产品特点、适用业务场景和对业务的实际性能提升进行…

git分支合并指定代码_git的几种实用操作(合并代码与暂存复原代码)

总述git工具也用了很久,自己也写了几篇使用教程,今天继续给大家分享一些我工作中使用过的git操作。1.git合并远程仓库的代码2.git stash保存当前的修改这两种情况大家应该都使用比较多,现在大家使用git进行团队开发代码的情况比较普遍&#x…

flowable实战(十五)关于流程设计器 bpmn.js与vue的整合

一、前言: 由于flowable本身带的Moder风格实在与前端页面风格不一样,同时由于Modeler采用Angular.js写的,改造起来相对有一定的难度,所以打算换成bpmn.js当成流程设计器。二、与vue的整合 1.安装以下包进开发环境 npm install -…

大数据实践的6个阶段

戳蓝字“CSDN云计算”关注我们哦!来源公众号 | 智领云科技作者 | 智领云 彭锋博士在最新的“2018年Gartner数据管理技术成熟度曲线”报告中,DataOps的概念被首次提出,Gartner标记其目前在“极为初级”这个阶段里面,并预计需要5-10…

离线计算成本节省的神兵利器

摘要: 对于创业成长型的企业来说,离线计算已经必不可少了,通过离线计算我们可以生成复杂的业务报表,通过离线计算我们也能精确的算出用户画像。离线计算已经当今的企业中成为了不可或缺的存在。那么使用弹性计算能够对离线计算领域…

opencv论坛_Opencv批量添加logo的解决方案

知乎的水印是如何批量添加的?您想拥有这个本领吗?我在opencv论坛发现了这个趣图添加logo的方法,也许您正需要这个代码,那我就诚心分享下吧。如何删除结果图像中mainlogo.png周围的黑色边框?import cv2 import numpy as np import…

「深圳云栖大会」大数据时代以及人工智能推动下的阿里云异构计算

摘要: 最近几年,在大数据和人工智能的推动下,异构计算有了长足的发展。无论是在产品形态上,还是在应用领域上,阿里云异构计算都取得了累累硕果。 最近几年,在大数据和人工智能的推动下,异构计算…

mysql表设计要注意什么?

戳蓝字“CSDN云计算”关注我们哦!转自 | 孤独烟引言大家应该知道烟哥最近要(tiao 咳咳咳),嗯,不可描述!随手讲其中一部分知识,都是一些烟哥自己平时工作的总结以及经验。大家看完,其实能避开很多坑。而且很…

html table样式_CSS表格样式

表格的样式一般可以在HTML中直接定义,但是结构和样式需要分开声明,这可以方便后期的维护和修改。CSS中的表格样式有caption-side(表格标题位置)、border-collapse(表格边框合并)、border-spacing(表格边框间距)。 caption-sidecaption-side属性取值只有2…

AI开发者福音!阿里云推出国内首个基于英伟达NGC的GPU优化容器

摘要: 3月28日,在2018云栖大会深圳峰会上,阿里云宣布与英伟达GPU 云 合作 (NGC),开发者可以在云市场下载NVIDIA GPU 云镜像和运行NGC 容器,来使用阿里云上的NVIDIA GPU计算平台。 阿里云推出国内…

TensorFlow Hub介绍:TensorFlow中可重用的机器学习模块库

摘要: 本文对TensorFlow Hub库的介绍,并举例说明其用法。 在软件开发中,最常见的失误就是容易忽视共享代码库,而库则能够使软件开发具有更高的效率。从某种意义上来说,它改变了编程的过程。我们常常使用库构建块或模块…

新建文本文档好玩的代码_(03)用什么工具写ASP源代码?

有很多工具可以书写ASP源代码,我使用Windows自带的记事本来编写ASP源代码。下面做一下简要介绍。一. ASP源代码文件的格式1. ASP源代码文件是以.asp为后缀命名的文本文件,如index.asp,conn.asp等,前面设置的IIS就是执行这些文件内…

漫话:如何给女朋友解释鸿蒙OS是怎样实现跨平台的?

戳蓝字“CSDN云计算”关注我们哦!周末在家休息,女朋友在刷朋友圈,突然她问我:鸿蒙OS回顾2019年8月9日华为开发者大会上,华为消费者业务CEO余承东正式宣布发布自有操作系统鸿蒙,内核为Linux内核、鸿蒙微内核…

apache ant 安装_Jmeter+ Ant+jenkins 接口自动化框架实现

一、文件配置•编写jmeter脚本 •上传jmx脚本到jmeter目录下,新建一个Loadtest目录,•在Tomcat webapp 文件夹下面新建报告输出文件夹testReport;•将jmeter中extras 文件夹ant-jmeter-1.1.1.jar 包放置于ant目录下lib文件夹 •将jmeter的ext…

机器学习必备:前20名Python人工智能和机器学习开源项目

摘要: 机器学习之旅必了解:前20名Python人工智能和机器学习开源项目! 如今机器学习和人工智能已经变得家喻户晓,有很多爱好者进入了该领域。但是,什么才是能够进入该领域的正确路径呢?如何保持自己跟上该领…

索非亚机器人的采访_还记得曾经扬言要“毁灭人类”的机器人索菲亚吗?如今过成这样...

如今人类电子信息技术的不断发展,人工智能大量出现在我们的生活之中,比如手机、电脑等,他们让我们的生活更加便捷和高效,人工智能作为一项服务人类的技术,在带给我们生活的便利的同时,却也让很多人表示担忧…

华为5G设备全球分布图曝光:欧洲占总量近6成;地平线发布首款车规级AI芯片,名叫征程2.0;奥迪与比亚迪达成电池供货协议……...

关注并标星星CSDN云计算极客头条:速递、最新、绝对有料。这里有企业新动、这里有业界要闻,打起十二分精神,紧跟fashion你可以的!每周三次,打卡即read更快、更全了解泛云圈精彩newsgo go go 首个金融教育主题微信小游戏…