S3的中文编码问题及修复方案

六月ゝ 毕业季﹏ 提交于 2020-01-13 13:18:20

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

通用实时日志分类统计实践

从清档需求谈谈 Redis 二级索引的使用

Swap 与 Swappiness

网易游戏海外 AWS 动态伸缩实践

深入理解实时计算中的 Watermark

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!