S3的中文编码问题及修复方案
原创 小包子大 网易游戏运维平台 2019-08-10
小包子大
06 年加入网易游戏,先后负责过多个端游/手游产品的运维工作;多年运维生涯,历经数次运维技术变革;本人关注广泛,Web/CDN,自动化,分布式等,欢迎来侃;作为十多年运维老兵, 平日写些别人看着晦涩的东西,擅长手术刀式的运维杂症分析。
二个月前,游戏的流媒体站点从物理机迁移到了 S3,迁移过程中发生了一些小插曲,今天分享下其中的 S3 中文文件名的编码问题及解决方法。
这里指的中文,是指文件名带中文,而不是文件内容。
中文主要有 2 种编码,UTF-8 与 GBK,服务器环境大都是 UTF-8 编码,而 Windows 系统则采用 GBK。
PS. 本文档不区分字符集与字符编码,二者在这里可以混用。
一、中文文件名与S3上传的编码问题
当上传到 S3 的文件名带有中文时,上传时的编码环境很重要。
文件名采用什么编码,就需要在相应的编码环境上传,否则无法上传
比如,一个文件名采用 GBK 编码的文件,在 GBK 编码环境下,正常上传
而同一个文件,切换到 UTF-8 环境下,上传报错
上面这个编码要求还算说得过去,但当以目录为单位上传到 S3 时,异常编码的文件,会被 “静默地“ 忽略掉!
即当你想同步整个目录到 S3 时,实际只是同步了名字编码没有问题的文件,请注意这个坑。
二、S3 Website的中文文件名问题
上传的编码问题,还好解决,下行的问题,就比较麻烦了,原因是 S3 只支持 UTF-8!
所有上传到 S3 的文件,文件名都将被强制为 UTF-8 编码
上面的测试中,我们在 GBK 编码环境下,上传了一个文件名采用 GBK 编码的文件,现在来访问它。
当使用 GBK 编码时,返回 400,注意,是 400!
切到 UTF-8 编码,则可以正常访问,即我们上传的 GBK 编码的中文文件名,被强制转为了 UTF-8
三、S3强制使用UTF-8编码的影响
大家知道,目前的端游,几乎都是基于 Windows 平台,而 Windows 使用的是 GBK 编码,因此,这些端游的客户端采用 GBK 编码,这些客户端生成的文件,也是 GBK 编码的。我们所熟知的西游系列的端游,会产生大量的战斗录像,这些战斗录像,有一些是使用中文文件名,当这些录像文件,上传到 S3 后,客户端/播放器 再尝试以 GBK 编码去请求录像时,就会产生 400 错误。
四、当前的文件访问路径
这里只说下 S3 Proxy 的用途
-
实现基于 S3 Bucket 子目录的虚拟主机
-
为后续扩容的多个 Buckets 提供统一的入口
五、如何修复这个编码问题
大致的思路是,服务端作适配,将 Client 的 GBK 编码的请求,在转发到 S3 Bucket 前,将请求的 URI 转为 UTF-8 编码,
其它诸如在客户端作调整的方案,暂不考虑!(对业务的少侵入/零侵入原则)
目前,这个 URI 转编码,理论上,在 LBC,S3 Proxy 或 S3 GW 上面都可以做,考虑到在通用业务上作调整的风险太大,故这个转编码的工作,放在业务专用的 S3 Proxy 上。
URI 转编码考虑的主要因素:
-
中文文件名可以采用 GBK 或 UTF-8 编码,Client 访问这些文件时,可能是 UTF-8 或 GBK 编码
-
如果用户请求的 URI 属于单字节编码的字符集,无需调整编码 (GBK/UTF-8 都可以与之兼容)
-
如果用户是使用 GBK 编码,那 URI 需要先转码为 UTF-8,再转发到 S3
-
如果用户是使用 UTF-8 编码,无需调整 URI 编码,直接转发到 S3
看看nginx的URI转编码方案
# nginx + lua/iconv (openresty)
rewrite_by_lua_block {
local iconv = require 'resty.iconv'
local request_uri = ngx.var.request_uri
local handler, errmsg = iconv:new("utf-8", "gbk")
if not handler then
return ngx.say(errmsg)
end
local request_uri_utf8, words = handler:convert(request_uri)
if not request_uri_utf8 then
return ngx.say(words)
end
ngx.req.set_uri(request_uri_utf8)
}
上述功能,也可以使用 nginx 第三方 iconv 扩展来做;
但主要的问题是,Nginx/Openresty 如何原生地获取到 Client 请求时使用的编码,上述方案都是假设 Client 固定使用 GBK。
当前无法判断Client请求的编码,故采取新的思路
强制(盲转)转码,如果转码异常,请求转到 S3 后将返回 403,我们将这个 403 捕捉,再进行一次正常合理的转编码, 而这次转编码后,S3 将正常返回。
以下是主要的 nginx 配置代码
#下面定义了2个Server,请注意,2个Server的Upstream,是同一个,指向同一个S3 !
#定义 8003 服务器,使用S3作为Upstream
server {
listen 8003 default_server;
server_name wahaha-wahaha.s3.nie.netease.com;
location / {
#这里进行URI编码盲转,强制认为Client编码为UTF-8,并转码为UTF-8 -_-
#当Client编码为非UTF-8时,转编码后,Upstream将返回403,这个403将被捕捉!
proxy_pass http://wahaha-wahaha.s3.nie.netease.com;
}
}
#定义 8004 服务器,使用S3作为Upstream
server {
listen 8004 default_server;
server_name wahaha-wahaha.s3.nie.netease.com;
location / {
#这里进行URI编码盲转,强制认为Client编码为GBK,并转码为UTF-8 -_-
#当Client编码为非GBK时,转编码后,Upstream将返回403,这个403将被捕捉!
proxy_pass http://wahaha-wahaha.s3.nie.netease.com;;
}
}
# 注意:S3没有404的概念,原本当编码异常时,S3会返回400(不可捕捉), 强制转编码后,S3返回403(可捕捉)
# 再定义一个Upstream,引用上述8003,8004服务器
upstream v.nie {
server 127.0.0.1:8003; #绝大多数情况下,URI都是单字节编码的字符集,所以将UTF-8设置为默认Client编码
server 127.0.0.1:8004 backup; #少数文件名采用GBK编码,故作为从站
}
# S3 Proxy入口配置
server {
listen 80;
server_name wahaha.wahaha.netease.com;
#以2个RTT为代价,实现编码异常时的Failover(捕捉403)
proxy_next_upstream http_403;
location / {
#回源到8003,8004, 并最终回源到S3 Bucket
proxy_pass http://v.nie$uri;
}
}
验证,URI 转码后,符合需求!
GBK 编码下,正常请求
UTF 编码下,正常请求
小结
这个方案,同时支持采用 GBK 与 UTF-8 编码访问 S3 Website 上的同一个文件!
往期精彩
NEW
﹀
﹀
﹀