Safari 5 / iOS,WebSocket 握手有时有效
Posted
技术标签:
【中文标题】Safari 5 / iOS,WebSocket 握手有时有效【英文标题】:Safari 5 / iOS, WebSocket handshake only works sometimes 【发布时间】:2012-04-27 03:04:21 【问题描述】:我在 java 中创建了一个类,它允许我用 WebSocket 协议包装现有的套接字。我的一切都适用于RFC6445 协议,一切都适用于 chrome 和 FF。然而 Safari 和 ios 使用的是 hixie76 / HyBi00 协议(根据***)。
我一切正常,Safari 和 iOS 正确握手并开始发送/接收消息……嗯,至少大部分时间是这样。
大约 20-30% 的时间,握手失败并且 Safari 会关闭连接。 (Java 在尝试读取第一帧时读取 -1 字节)。 Safari 不会在控制台报告任何错误,只是调用 onclose 事件处理程序。
为什么握手只在部分时间起作用?
这是我的握手代码:
注意:不会抛出异常,并且“握手完成”会写入控制台。但是在尝试读取第一帧时,连接被关闭。 (Java 在 inst.read() 上返回 -1)
// Headers are read in a previous method which wraps the socket using RFC6445
// protocol. If it detects 2 keys it will call this and pass in the headers.
public static MessagingWebSocket wrapOldProtocol(HashMap<String, String> headers, PushbackInputStream pin, Socket sock) throws IOException, NoSuchAlgorithmException
// SPEC
// https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76#page-32
// Read the "key3" value. This is 8 random bytes after the headers.
byte[] key3 = new byte[8];
for ( int i=0; i<key3.length; i++ )
key3[i] = (byte)pin.read();
// Grab the two keys we need to use for the handshake
String key1 = headers.get("Sec-WebSocket-Key1");
String key2 = headers.get("Sec-WebSocket-Key2");
// Count the spaces in both keys
// Abort the connection is either key has 0 spaces
int spaces1 = StringUtils.countMatches(key1, " ");
int spaces2 = StringUtils.countMatches(key2, " ");
if ( spaces1 == 0 || spaces2 == 0 )
throw new IOException("Bad Handshake Request, Possible Cross-protocol attack");
// Strip all non-digit characters from each key
// Use the remaining value as a base-10 integer.
// Abort if either number is not a multiple of it's #spaces counterpart
// Need to use long because the values are unsigned
long num1 = Long.parseLong( key1.replaceAll("\\D", "") );
long num2 = Long.parseLong( key2.replaceAll("\\D", "") );
if ( !(num1 % spaces1 == 0) || !(num2 % spaces2 == 0) )
throw new IOException("Bad Handshake Request. Possible non-conforming client");
// Part1/2 is key num divided by the # of spaces
int part1 = (int)(num1 / spaces1);
int part2 = (int)(num2 / spaces2);
// Now calculate the challenge response
// MD5( num1 + num2 + key3 ) ... concat, not add
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(ByteBuffer.allocate(4).putInt(part1));
md.update(ByteBuffer.allocate(4).putInt(part2));
md.update(key3);
byte[] response = md.digest();
// Now build the server handshake response
// Ignore Sec-WebSocket-Protocol (we don't use this)
String origin = headers.get("Origin");
String location = "ws://" + headers.get("Host") + "/";
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 101 WebSocket Protocol Handshake").append("\r\n");
sb.append("Upgrade: websocket").append("\r\n");
sb.append("Connection: Upgrade").append("\r\n");
sb.append("Sec-WebSocket-Origin: ").append(origin).append("\r\n");
sb.append("Sec-WebSocket-Location: ").append(location).append("\r\n");
sb.append("\r\n");
// Anything left in the buffer?
if ( pin.available() > 0 )
throw new IOException("Unexpected bytes after handshake!");
// Send the handshake & challenge response
OutputStream out = sock.getOutputStream();
out.write(sb.toString().getBytes());
out.write(response);
out.flush();
System.out.println("[MessagingWebSocket] Handshake Complete.");
// Return the wrapper socket class.
MessagingWebSocket ws = new MessagingWebSocket(sock);
ws.oldProtocol = true;
return ws;
谢谢!
注意:我不是在寻找 WebSockets 的第三方替代品,例如 jWebSocket、Jetty 和 Socket.IO。其中很多我已经知道了。
【问题讨论】:
您对 OutputStream 的处理非常错误。您使用的是 Java 7 吗?如果没有,请升级到它并使用 try-with-resources。 不,现在一切都是 java 6。 如果您可以升级,请这样做。与这样的资源交互时,您将有更好的时间。否则,我会立即修复 OutputStream 处理,因为它只会掩盖其他问题。 升级会很好,但短期内不会发生。 哦,好吧,那么您将需要以令人讨厌的旧方式处理那些 OutputStream 资源。网上有大量关于如何做到这一点的资料。 【参考方案1】:您的 MD5 摘要方法有一个错误: 协议描述如下:https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76#section-5.2
byte[] bytes = new byte[16];
BytesUtil.fillBytesWithArray(bytes, 0, 3, BytesUtil.intTobyteArray(part1));
BytesUtil.fillBytesWithArray(bytes, 4, 7, BytesUtil.intTobyteArray(part2));
BytesUtil.fillBytesWithArray(bytes, 8, 15, key3);
我认为您的问题是由 Little Endian 和 Big Endian 引起的。
【讨论】:
以上是关于Safari 5 / iOS,WebSocket 握手有时有效的主要内容,如果未能解决你的问题,请参考以下文章
Websockets 在 iOS 和 Safari 上不起作用 - OSStatus 错误 9837
Safari 扩展:WebSocket 连接失败调用 onclose 而不是 onerror 并且没有异常
哪个是 websocket 协议的更好方法?本机 ios 或混合或网络应用程序?