背景介绍:
问题:报警监控显示项目的活动接口异常并报错,提示json_decode解析异常,但不必现,无法立刻判断原因。
先介绍下我们的数据传递流程:
【app客户端】发送加密数据 -------->【网关】解密-透传------>【php服务器】业务逻辑
图1.app报错页面
监控时常显示项目的某个接口报错异常,报警提示如下:
图2.报警邮件
过程分析:
很明显,json_decode($_POST['result'])出错,解析的数据格式非标准encode格式
那么查看下decode的数据呗,看是什么问题
抓包app给网关的数据(正确):
answer=[{"answer":"https://h5.youzan.com/v2/goods/277ola8ay4b6x?reft=1512028692491&spm=f47826345&sf=wx_menu"}]
php收到网关的$_POST数据(错乱),
"answer":"[{\"answer\":\"https:\/\/h5.youzan.com\/v2\/goods\/277ola8ay4b6x?reft=1512028692491","spm":"f47826345","sf":"wx_menu\"}]"
显然经过网关后,【answer字段里的值结构发生了变化】,导致解析失败。
所以就是【网关】的问题呗。
结论为时过早,还真不全是他的问题
图3.原始访问数据截图
php收到的file_get_contents('php://input')数据(正常)
answer=[{"answer":"https://h5.youzan.com/v2/goods/277ola8ay4b6x?reft=1512028692491&spm=f47826345&sf=wx_menu"}]
由图看php原始数据记录可得,网关传递时数据格式显示是正常的,格式错误是因为php底层给$_POST赋值时(PHP生命周期第二步)错误造成,也就是说,是php的问题。但底层为什么会错乱呢?
问题解决:
两种方式指定数据传输的Content-type都为:
application/x-www-form-urlencoded
细心的同学应该发现一个区别,图3中用postman模拟的数据,key对的value是被转义掉的,
php底层对$_POST赋值是根据&切割的,但是网关传来的数据没有转义,所以当value中有"&"值时自动分开造成错误切割。
真相大白,原因是app在传递数据时只是做了加密处理,忘记做urlencode处理,网关透传到php服务器,造成php解析错误。找到真正的原因,那问题就好解决的,
根本解决方案:app做urlecode
临时解决方案:php做正则处理,用file_get_contents('php://input')里的方案。
$raw_str =file_get_contents('php://input');
preg_match("/&answer=(.*)}]&/",$raw_str,$res);
$answer = empty($res[1])? Yii::$app->getRequest()->post('answer') : $res[1].'}]';
知识延伸:
本次问题解决的过程中,曝露出了一个问题,对http的content-type并不是十分熟悉,借此机会延伸学习一下。
Content-Type:
作用:说明了实体主体内对象的媒体类型(MIME)
包含类型:
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/pdf :pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/json : JSON数据格式
application/x-www-form-urlencoded :form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
另外一种常见的媒体格式是上传文件之时使用的:
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
着重看这么几个:
1.multipart/form-data
2.application/x-www-form-urlencoded 【默认类型】
3.application/json
1.multipart/form-data
就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息;
由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。
2.application/x-www-form-urlencoded
浏览器用x-www-form-urlencoded的编码方式把form数据的value值urlencode,转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割,加载这个新的url。
3.raw -application/json
可以上传任意格式的文本,可以上传text、json、xml、html等
总结:
1.各php对各Content-Type的应用情况
所以,只要特殊记一下,
当
Content-Type==json,用
file_get_contents('php://input')接收并且json_decode,$_POST是无值的
当
Content-Type==form/data,不要用
file_get_contents('php://input')接受
2.form-data与urlencode小知识点
Content-Type:multipart/form-data body体发送有边界值
Content-Type:application/x-www-form-urlencoded
会把参数用&链接组装,并且对value做转义操作【很关键】,详细解释
3.
php://input与
$HTTP_RAW_POST_DATA区别
php://input访问请求的原始数据的只读流。不依赖于特定的 php.ini 指令
$HTTP_RAW_POST_DATA 包含 POST 提交的原始数据,不建议使用,php7已经被废弃
4.php 的curl与 linux发送curl,在www_url_encode是否会转译数据
问题:
1.php底层如何对$_POST进行赋值的,注意 Content-Type:multipart/form-data下的情况
2.file_get_contents('php://input');原理,为什么在Content-Type:multipart/form-data下无法捕获到值
参考地址:
php://input :
http://php.net/manual/zh/wrappers.php.php
form-data、x-www-form-urlencoded、raw、binary:
http://blog.csdn.net/ye1992/article/details/49998511
数据交互截图记录:
图1.multipart/form-data
图2.application/x-www-form-urlencoded
图3.application/json