okhttp上传文件中文名报错问题分析。

316 阅读3分钟

问题描述

当我们在使用okhttp上传文件的时候可能会出现以下报错:

java.lang.IllegalArgumentException:
 Unexpected char 0x901a at 50 incontent-disposition value: filename="中文.docx"

尝试解决

第一步,百度一下

最初我在网上搜索okhttp上传文件中文名报错或者把异常复制,有很多同样的问题案例,一度让我以为okhttp就是单纯上传文件不支持中文,而有些文章会告诉你通过转码,避免使用中文来解决。。

(ps:bing现在的搜索结果和我第一次遇见这个问题的时候大不相同,可能这就是ai加持吧)

于是乎,我就这样傻乎乎的用了一年多的转码来解决这个问题。

转码怎么用?在报错的地方加个这个就好 URLEncoder.encode(name,"UTF-8")这样我们的中文就被麻溜的转换成了%20这种url安全编码了。

第二步,思考一下

转码能解决这个问题么?能的。不然我的第二步也不会来的这么晚。

转码确实能够解决中文导致的报错问题,而且说实话,它可能是最简单最稳定的改法。但是它有两个问题比较麻烦。

一个是长度

转码前:中文
转码后:%E4%B8%AD%E6%96%87

转码后长度会大大增加,基本上用中文转码,怎么转都是越转越长,对有限制的请求来说可能会有影响。

一个是接收端也需要修改,接收端也就是服务端必须同步修改增加反转码将客户端转码的内容重新转回中文,才能保证原有的展示能够正常使用。

然后我们可怜的客户端就只能给服务端提需求,说我们的请求框架不支持中文,能不能配合一下我们加一个解密的转码?很low也很卑微

第三步,看源码

于是乎,我想看看到底是为什么不能上传中文名文件,有没有其他的一劳永逸的方案呢。

我使用的是okhttp3.12.0版本

以下为3.12.0源码逻辑

在传入文件名时,请求头的of生成方法,会调用两个check来检查值是否为ascii,其中value的check导致中文报错。

以下为3.12.3源码逻辑

在3.12.3中,请求头的创建方法由of改为了builder模式,同时调用了addUnsafeNonAscii方法,只检查name不再检查value是否复合ascii。

以下为3.11.0源码逻辑

在3.11.0中,请求头与3.12.0一样使用of方法生成,但是of方法中,没有checkName和checkValue方法,

看官网

okhttp的官方网站

在okhttp官网的Change Logs中,可以找到,在3.12.3版本,修复:允许多部分文件名包含非 ASCII 字符

image.png

结论

okhttp3.12有14个版本之多,是okhttp3中版本最多的中版本,也说明了12添加了许多新功能与新bug,所以我认为,这个中文名异常属于是新功能产生的新bug。在okhttp3.12.3以后和3.11.0之前都可以规避这个问题。(也就是有问题的只有3.12.0、3.12.1、3.12.2三个版本)。推荐将项目okhttp调整到3.12.13

如果遇到okhttp依赖是封装到jar包中不方便修改版本号的情况,可以在引用了包含okhttp的jar的模块(如果多个引用,每个都需要加)的build.gradle中加入以下代码,可以强制使用特定版本的OkHttp库。

//在包含okhttp的模块的build.gradle中加入
configurations.all {
    resolutionStrategy {
        force 'com.squareup.okhttp3:okhttp:3.12.13' // 强制使用特定版本的OkHttp库
    }
}