在不久的过去,小站经历了一些事情,本来想昨天就发的总结记录,结果因为各种事情拖到现在,也是比较惭愧。
俗话说,人在家中坐,锅从天上来。搞 Web 的孩子,也常常遇到各种各样的神奇的事情,这里也记录记录,供后人参考。
大概凌晨5点多,站长迷迷糊糊醒来,看到@payne 发来的微信,“0xFFFF炸了”。
吓得从床上弹起来,睡意全无,想起网站有一层 CloudFlare CDN 缓存,第一反应是 Nginx 还是 php-fpm 服务挂了,还是数据库问题?或者是被 Hacker 种了 WebShell 搞破坏?我的妈呀,也好久没备份了啊,赶紧 ssh 上主机,把Nginx,php-fpm,MySQL都重启了一遍,但无济于事。
检查了一下同服务器跑着的其他网站,没有问题,大概就是程序的问题了。测试发现管理员页面还能访问,然后清理缓存,禁用CDN和各种插件,捣鼓了大半小时,首页依旧是500,想到 flarum 这个程序引了一大堆的库,随便请求个页面都可能几十层函数调用栈,此时也是有点绝望,这么复杂的执行过程,想把问题揪出来,恐怕是猴年马月才能做到。
不过,还是得试试的,万一有路子呢?先是把数据库备份到本地,网站也打包了下来。然后,无意中在 config.php
里面,发现了 'debug' => false,
这样的字段,false
改成 true
之后,无意中打开了新世界的大门。
是的,没错,完整的错误以及报错时的函数调用栈帧信息,一览无余!(听说是Zend Framework的特殊加成)
可以看到,这里的报错是在 json_encode
的过程中产生的。搜索这个错误,找到一些相关资料,其中有一个就是 Flarum 的。
- php - 'Malformed UTF-8 characters, possibly incorrectly encoded' in Laravel - Stack Overflow
- Unable to encode data to JSON in Zend\Diactoros\Response\JsonResponse: Malformed UTF-8 characters, possibly incorrectly encoded · Issue #1452 · flarum/core
- PHP: json_encode - Manual
其中,PHP文档提醒了字符串的编码应该是UTF-8,在 Flarum 项目主页中,也有人提到了这个问题,他的解决办法是把数据字段的 TEXT 类型换成能容纳更多的 LONG TEXT。
change data type 'text' to 'long text' in content field of 'post' table (flarum db). this fixed my issue.
The column used to be TEXT, which is limited to 65535 characters. but LONG TEXT does the job very well if u r make long text size post
合理推断,会不会是因为存在了过长的帖子而导致类似“宽字节注入”之类的截断,导致PHP在JSON序列化的时候崩溃呢?
进入数据库,简单地按帖子长度给所有帖子一起排了个序,嗷,好像是师兄的年终总结,竟然,有 3w 多个字符,那,占用65535 Bytes 应该也是很自然的事情。至于为啥那么多条记录,大概是因为这时候程序出错了,导致多提交了几次。
把第一条记录的内容保存成文件,大小刚刚好 65535 字节,可以确定是字数超过数据库字段限制,然后被截断了。
顺着PHP的调用栈,顺藤摸瓜,往下层层排查,顺便用 serialize 函数,把原始的要编码的数据 dump 到了一个文件,VSCode 配合二进制模式 Vim 辅助,打开,发现了凶手所在。
是的,最后用 UTF-8 方式组织存储的中文字符中,三个字节被砍了一个,剩下 0xE4BB 这两个字节,以UTF-8的形式解析,乱码也是自然的事情。
查看师兄博客的原文对比发现,凶手竟然是—“他”!
把数据库对应记录的无效字符删掉,修改表属性使其为 LONG TEXT,一切恢复了正常,天也亮了。
经验
- MySQL里面 TEXT 类型的数据最大只支持65535字节,多了会默认截断且返回错误。
- PHP的
json_encode
的字符串所指向的内存需要遵循 UTF-8 编码来组织字节,非UTF-8会出错。
- 当程序设计往更高一级抽象且越来越复杂时,成熟的框架往往会配套一些对应的抽象层级所用的工具,在合适的视角与抽象层级下,可以让我们更快地定位问题所在。