昨天有人反应苹果手机不能访问我的物联网项目,我大惊,立马开始分析。
发现果然苹果端的safari连接websocket失败了。但是我确信之前一直用着好好的。可能是贱贱的A厂悄悄变更了某些软件吧,没办法,谁让世人都称他是老大,谁让你们那么多人没有iPhone不行呢。我只好认为我自己的服务器有问题了。
于是检查服务器的日志,发现websocket的握手过程显示协议错误。
那么我抓包分析下safari与其他浏览器的行为有什么不一样吧:
这是Google:
Sec-WebSocket-Key:GX2sEWmKLhgktWvslt8xxw==
Sec-WebSocket-Protocol:mqtt
Sec-WebSocket-Version:13
这是Safari:
Sec-WebSocket-Key1:11684026C 1s7
Sec-WebSocket-Key2:' +8h 5 8 O8%07wc3O8 ! 4E az
Sec-WebSocket-Protocol::mqttv3.1
64:CC:A9:CE:F0:57:34:A4
再明显不过了吧,Safari用的websocket协议是基于MD5加密认证的旧版本,应该是websocket7.1草案协议,而现在的websocket都已经升到13版本了。
显然,我在写服务器的时候是没有去理会过时的websocket版本的。
为了支持苹果设备的websocket,我只能增加版本的支持了。我在我的代码里,增加了对
Sec-WebSocket-Key:
Sec-WebSocket-Key1:
Sec-WebSocket-Key2:
的判断,让其据此进入相应的处理流程,其实也可以去判断获取到的版本号的。
然后增加了返回密钥的函数:
void websocket_accept_key(const char* key,int key_len,char* out){
char tmp[128]={0};
unsigned char sha[SHA_DIGEST_LENGTH]={0};
if(key_len==0){
key_len=strlen(key);
}
strcpy(tmp,key);
strcpy(tmp+key_len,"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
SHA1(tmp,key_len+36,sha);
base64_encode(sha, SHA_DIGEST_LENGTH, out, 256);
}
void websocket_accept_key_safari(const char* key1,int key1Len,const char* key2,int key2Len,char* body,char* out){
char tmp[16];
unsigned long long kv1=0;
unsigned long long kv2=0;
int c1=0;
int c2=0;
int i;
if(key1Len<=0){
key1Len=strlen(key1);
}
if(key2Len<=0){
key2Len=strlen(key2);
}
for(i=0;i<key1Len;i++){
if(key1[i]>='0' && key1[i]<='9'){
kv1=kv1*10+(key1[i]-'0');
}else if(key1[i]==' '){
c1++;
}
}
for(i=0;i<key2Len;i++){
if(key2[i]>='0' && key2[i]<='9'){
kv2=kv2*10+(key2[i]-'0');
}else if(key2[i]==' '){
c2++;
}
}
kv1/=c1;
kv2/=c2;
*((unsigned long long *)tmp) = htonl(kv1);
*((unsigned long long *)(tmp+4)) = htonl(kv2);
for(i=0;i<8;i++){
tmp[i+8]=body[i];
}
MD5(tmp,16,out);
out[16]=0;
}
值得一提的是,在旧版本的websocket协议下,返回密钥是通过body返回的,而在13版本中则是通过Header中的Sec-WebSocket-Access值来返回。
本来到这里,修改工作应该是完成了,然而……
我在windows上装的safari一直测试不能通过。我左改右改地就这么折腾了一天,心累了一天,最后一试苹果手机上的safari居然轻易地连上了!
原来safari也不是每一个版本都支持websocket,我被这个又坑了一把。