一篇老文章的标题里有中文,链接复制到微信里发出去,点开是404。同一条链接在浏览器地址栏里回车能打开,从搜索结果点进去也正常。差别只在中间经过了一次复制粘贴。
URL里出现非ASCII字符,浏览器会自动转成百分号编码。一个汉字在UTF-8下占三个字节,转出来是三组百分号加十六进制,比如中这个字变成%E4%B8%AD。这一步是标准做法,不同浏览器转出来的结果一致。
问题出在服务器按哪张表把它解回去。nginx只把%XX还原成原始字节,本身不认字符集,还原出的字节原样交给后端。如果站点的文件系统或者程序内部用的是另一套编码,同一串百分号解出来的东西就对不上,文件找不到,返回404。
老站点上这种情况不少,GBK时代的CMS把中文文件名原样存在磁盘上,迁移之后磁盘还是GBK,Web这一层已经按UTF-8处理。文件确实在,名字对不上,所以报的是找不到。
还有几处会造成同样的现象。程序自己生成链接时用了另一套编码函数,输出的百分号串跟浏览器转的不一样,两边对不上。URL重写规则里做了两次编码或者一次都没做,编码后的串又被当成普通字符转一遍,长度翻三倍。CDN对URI做归一化,把大小写改掉,而有的老程序区分大小写。Windows服务器上传的中文名文件,落到磁盘上被转成了GBK,URL还是UTF-8,中间少了一次转换。这几种的共同点是浏览器地址栏能打开,因为它用的还是页面里那条原始链接,而复制出去的版本经过了别的环节。
定位你要看两处。curl -I带上完整的编码地址请求一次,拿到状态码;再看access_log里记下来的URI是原样还是被解码过。日志里还是%XX,说明解码发生在记录之后;要看到解码后的值,得在log_format里换成$uri。
修的方向是统一到UTF-8。磁盘上的文件名批量转码一次,转之前备份,重名的先处理。程序的连接、模板、数据库字符集一起对齐。转码用convmv,-f和-t分别写原编码与目标编码,它默认只打印不改名,确认无误再加--notest实际执行。
更彻底的做法是链接里不出现中文。用文章ID或者拼音做别名,编辑器保存时自动生成,中文标题只留在页面上。这样链接在任何渠道复制都不会变形,也不会因为编码表换过一次而全部失效。老链接保留301跳转到新地址,收录不会丢。
新发的文章全部用ID做别名,老文章里访问量在前两百的补了跳转,剩下的按访问日志慢慢补。百分号编码本身不是错误,它只是把编码表这件事摆到了台面上。
申请创业报道,分享创业好点子。点击此处,共同探讨创业新机遇!
