记录一个因为使用不规范引发的debug开启后报错问题

我想不止我一个人在使用thinkPHP的时候出现过很多诡异的情况,也不止我一个人在打开和关闭框架中的DEBUG时得到2种运行结果。这次就较一下真,看下到底是框架垃圾还是我们使用过程中的确存在不规范(人垃圾)

背景

因为事业环境因素需要统一过滤项目组中各个业务线的搜索词是否合法。采用的解决方案如下

  1. 用Go的生态 load 一颗 trie树到内存
  2. PHP 走 RPC 调用启动的这个Go服务
  3. PHP 提供一个短连接的内网RPC服务供各个业务线使用

疑问:

  • 为什么要用trie树

    trie树 + AC自动机是目前比较高效的违禁词过滤方法

  • 为什么不直接调用Go服务而要过一层PHP

    想在这层PHP上做熔断报警机制,业务层并发量大PHP的curl库超时最少1秒,在并发量比较高的场景1秒的等待开销太大。

技术选型

RPC协议
小马哥的Hprose

项目框架
thinkPHP3.1 (历史原因)

常驻监控
supervisor

问题

在实际操作期间,其实蛮顺利的

  • GO常驻服务&&接口暴露
  • 服务层PHP调用Go接口
  • 应用层PHP调用PHP提供的服务接口

只是再最后一步出现了一个问题我发现应用层的PHP调用服务层的掉不通,而且会抛出错误。经过一些调试发现最诡异的是把thinkPHP的APP_DEBUG关掉接能正常调用

解决

首先想到的是各种语法层面的问题

发现Hprose的方法发布核心函数addFunction 是这样写的

1
2
3
4
5
6
7
8
9
10
11
12
13
public function addFunction($func, $alias = '', array $options = array()) {
if (!is_callable($func)) {
throw new Exception('Argument func must be callable.');
}
if (is_array($alias) && empty($options)) {
$options = $alias;
$alias = '';
}
if (empty($alias)) {
if (is_string($func)) {
$alias = $func;
}
...

is_callable可以得出作者想让我们传入的是一个回调函数,这里为了排除是魔改过后的框架有类调用找不到的情况,传入一个匿名函数个人感觉比较稳妥而且没什么问题(排除法慢慢来)代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function xxx()
{
$server = new \Hprose\Http\Server();

$server->addFunction(function ($keyword) {
try {
return $this->client->Validate($keyword);
}catch (Exception $e) {
return $this->error($keyword);
}

}, "validate");
$server->start();
}

尝试了这种方法后继续掉用,发现问题还是没有得到解决但是报错信息变成了

1
2
3
4
Exception: Wrong Response: 
Rm2{s5"state"s5"false"s7"keyword" in /Users/zhangjunjie/code/testdocker/phpdocker/www/blog/vendor/hprose/hprose/src/Hprose/Client.php:351
Stack trace:

我们定位到 Client.php:351 行

从这一行基本可以肯定的是代码在处理$response值的时候得到的不是预期值,那么这个和我们的 thinkPHP的APP_DEBUG有什么必然的联系吗?

我们继续尝试,直接输出这个$response值

打开了APP_DEBUG

1
string(36) "Rm2{s5"state"s5"false"s7"keyword""

关闭了APP_DEBUG

1
2
string(36) "Rm2{s5"state"s5"false"s7"keyword"e}z"

这里就比较清楚了乖乖,在打开了debug的情况下框架吐出来的值吃了我们3个字符 e}z

从文档中可以看出序列化协议规范

名称 含义
0x52(‘R’) RPC 结果
m 可递归引用类型
2 map长度
0x7A(‘z’) 结束

这里可一很明确的肯定了,在打开thinkPHP的APP_DEBUG的情况下会丢失结尾标识符

下面我们就继续扒代码看下是什么原因产生的,我们顺着服务端的 $server->start();断点法不断找进去发现

在这个方法里面作者指定了一个header头,这是http协议用来告诉应用程序它的报文长度的。

那么由此我们可以想到是这个header头给了错误的限制导致这样的情况,涉及到http协议我们这边使用抓包工具分析报文是最稳妥的方法(直接看页面输出的值很多凭借肉眼都很难识别出来)

打开了APP_DEBUG

关闭了APP_DEBUG

看见那个熟悉的...了吗,看见那个ef bb bf了吧,对它就是我们最TM恨的BOM头!

那么最后一个问题,这个BOM是哪个文件里面的呢?

想一下为什么打开APP_DEBUG和关闭它得出的结果不一样,对!!!

打开APP_DEBUG的时候配置文件会实时的读入程序通过拼接的方式往下执行,那么带着这个猜想我们去看下配置文件

结论

  • thinkPHP在APP_DEBUG打开的情况下会把配置文件读入程序进行拼接。
  • BOM头会产生各种各样奇怪的问题
  • 不能怪框架垃圾,这确实是我们使用中不规范造成的问题
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~