浏览器http提交protobuf二进制数据正常,微信小程序失败解决方案
最近开发微信小游戏,客户端和服务器端使用http短连接方式post数据,数据的格式使用protobuf,这样通过网络传输的数据是二进制,对于抓包的外挂使用者来说也不容易篡改数据内容。
之前测试一直使用的是PC浏览器来测试协议,通过浏览器看到不管是格式还是内容都没有问题。
等到最后收尾的时候,开始导入微信开发者工具里,进行测试,结果出问题了,二进制数据 本应该是上面截图红色圈出来的formdata内容,但是微信开发者工具里查看到的是uint8array结构,
所以服务器端收到请求后进行反序列化成对象的时候报错,返回失败。
不过在PC浏览器里却不会发生此问题,所以初步怀疑是PC浏览器在发送protobuf的 uint8array数据的时候会自动转成arrayBuffer 字节,而微信小程序需要手动将uint8array转成arrayBuffer,那么带着这个疑问做了个尝试。
var n = e.prototype;
return n.httpRequest = function (e, t, n, r) {
t = encodeURI(t);
var s = new XMLHttpRequest,
i = function (e, t) {
s && s.abort && s.abort();
var r = setInterval((function () {
n && n(e, t), clearInterval(r)
}), 50)
},
a = !1,
u = setTimeout((function () {
a = !0, i(!1, null), clearInterval(u)
}), 1e4);
this._httpRequestList.push(s), s.open(e, t), s.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"), s.responseType = "blob", s.onreadystatechange = function () {
4 === s.readyState && (a || (clearInterval(u), 200 === s.status ? i(!0, s.response) : i(!1, null)))
}.bind(this), s.send(streambuffer)
}
我们为了验证,将r为protobuf 通过 encode().finish()返回的uint8array转成string,加入如下代码
var streambuffer = "";
for (var i = 0; i < r.length; i++) {
streambuffer += String.fromCharCode(r[i]);
}
var n = e.prototype;
return n.httpRequest = function (e, t, n, r) {
t = encodeURI(t);
var s = new XMLHttpRequest,
i = function (e, t) {
s && s.abort && s.abort();
var r = setInterval((function () {
n && n(e, t), clearInterval(r)
}), 50)
},
a = !1,
u = setTimeout((function () {
a = !0, i(!1, null), clearInterval(u)
}), 1e4);
var streambuffer = "";
for (var i = 0; i < r.length; i++) {
streambuffer += String.fromCharCode(r[i]);
}
this._httpRequestList.push(s), s.open(e, t), s.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"), s.responseType = "blob", s.onreadystatechange = function () {
4 === s.readyState && (a || (clearInterval(u), 200 === s.status ? i(!0, s.response) : i(!1, null)))
}.bind(this), s.send(streambuffer)
}
验证发现实际上发送的字节长度是202,但是到了网络传输的时候发送了203字节,而且服务器端也收到了203字节
服务器的日志如下:
32428:2022/09/01 16:37:34.457 [I] [GameServiceController.go:20] login recv msg bytes [10 194 191 1 101 121 74 104 98 71 99 105 79 105 74 73 85 122 73 49 78 105 73 115 73 110 82 53 99 67 73 54 73 107 112 88 86 67 74 57 46 101 121 74 74 90 67 73 54 77 106 69 51 78 106 73 121 79 84 85 48 79 84 81 50 77 84 107 53 78 84 85 121 76 67 74 106 97 71 70 117 98 109 86 115 73 106 111 121 76 67 74 119 98 71 70 48 97 87 81 105 79 106 65 115 73 109 108 104 100 67 73 54 77 84 89 50 77 106 65 121 77 84 81 49 78 67 119 105 90 88 104 119 73 106 111 120 78 106 89 121 77 68 73 52 78 106 85 48 102 81 46 72 101 113 104 90 77 85 49 86 76 90 114 74 81 54 56 57 85 55 83 71 87 73 121 52 72 117 67 53 88 121 50 86 118 106 99 118 97 107 83 114 87 89 18 6 49 48 48 48 49 52] len:203
微信开发者工具里查看到的信息如下:
那么是不是本身转的方式有问题,这里有很多开发者都会遇到这样的问题,有人提出在发送的头里加入 headers: { 'Accept':'application/x-protobuf'} } 或者使用headers: { 'content-type':'application/octet-stream'} },但实际没有任何意义,这里我再在这里将content-type和accept的区别顺便说明下:
Content-Type:
Content-Type(内容类型),一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。这个就是经常看到一些网页打开之后会下载一个文件或者是一张图片的原因。
Content-Type代表发送端(客户端/服务器)发送的实体数据的数据类型。
Accept
Accept用来指定什么媒体类型的响应是可接受的,即告诉服务器我需要什么媒体类型的数据,此时服务器应该根据Accept请求头生成指定媒体类型的数据。
Accept表示客户端(浏览器)支持的类型,也是希望服务器响应发送回来的数据类型。
区别
-
Content-Type属于实体头
Response Header
,Accept属于请求头Request Header
-
Accept代表客户端希望接收到的类型,如
Accept: application/json;charset=UTF-8
,代表接收JSON数据格式的数据;Content-Type代表服务器发送实体数据的类型,如
Content-Type: application/xml
,代表发送XML数据格式的数据。 -
两者可以结合使用,如:
Content-Type: application/json;charset=UTF-8; Accept: text/html;
后边发现应该是在protobuf encode之后得到的uint8array转arraybuff出了问题,需要将uint8array.slice().buffer
发送给后端即可,slice() 方法会 byteLength
自动截取有效的部分,也就是实际需要的数据。
这样就在浏览器里和微信开发者工具里都得到了protobuf的返回