解析原始 websocket 框架非常简单。
但您必须一次检查一个字节的标头。
这是一个粗略的例子:
我留下了一些 TODO 供您自行解决(当然是在阅读 RFC-6455 规范之后)
您可以验证的事情:
基本成帧协议:RFC-6455 - 第 5.2 节
- 操作码是否是规范中定义的有效操作码之一?
- RSV 位是否使用不当?
客户端到服务器屏蔽:RFC 6455 - 第 5.3 节
- 如果帧是由客户端发送的,该帧是否被屏蔽?
- 蒙版在帧与帧之间是随机的吗?
- 不允许使用 [0x00, 0x00, 0x00, 0x00] 作为掩码。
分段:RFC 6455 - 第 5.4 节
- 是不是一个碎片化的控制帧?
- 由多个帧组成的大消息的分段是否无序?
- 是否有新消息在带有 FIN 标志的前一个消息完成之前开始?
控制帧:RFC 6455 - 第 5.5 节
- 控制帧的净荷长度是否超过125字节?
- 有效负载是否碎片化?
关闭框架:RFC 6455 - 第 5.5.1 节
- 如果有效负载中提供了状态代码,则该状态代码是否符合中声明的状态代码之一第 7.4.1 节?不要忘记检查Websocket 状态代码的 IANA 注册表RFC 最终确定后添加的)
- 状态码是否允许在帧中通过网络发送? (例如,请参见代码 1005 和 1006)
- 如果框架中提供了/reason/,它是否符合UTF-8编码规则?
- 在关闭帧后,您是否收到过任何类型的帧? (这是禁忌)
数据帧:RFC 6455 - 第 5.6 节
- 如果您收到 TEXT 有效负载数据(来自 TEXT + CONTINUATION 帧),该有效负载数据是否符合 UTF-8 编码规则?
虽然您可以在单个帧级别进行验证,但您会发现上面的一些验证是多个帧之间的状态和行为的验证。您可以在以下位置找到更多此类验证:发送和接收数据:RFC 6455 - 第 6 节.
但是,如果混合中有扩展,那么您还需要从协商的扩展堆栈的角度来处理帧。
当使用扩展时,上面的一些测试似乎无效。
示例:你有压缩扩展 (RFC-7692)(例如permessage-deflate
) 在使用中,则无法使用网络外的原始帧来验证 TEXT 有效负载,因为您必须首先通过扩展传递该帧。请注意,扩展可以更改碎片以满足其需要,这也可能会扰乱您的验证。
package websocket;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class RawParse
{
public static class Frame
{
byte opcode;
boolean fin;
byte payload[];
}
public static Frame parse(byte raw[])
{
// easier to do this via ByteBuffer
ByteBuffer buf = ByteBuffer.wrap(raw);
// Fin + RSV + OpCode byte
Frame frame = new Frame();
byte b = buf.get();
frame.fin = ((b & 0x80) != 0);
boolean rsv1 = ((b & 0x40) != 0);
boolean rsv2 = ((b & 0x20) != 0);
boolean rsv3 = ((b & 0x10) != 0);
frame.opcode = (byte)(b & 0x0F);
// TODO: add control frame fin validation here
// TODO: add frame RSV validation here
// Masked + Payload Length
b = buf.get();
boolean masked = ((b & 0x80) != 0);
int payloadLength = (byte)(0x7F & b);
int byteCount = 0;
if (payloadLength == 0x7F)
{
// 8 byte extended payload length
byteCount = 8;
}
else if (payloadLength == 0x7E)
{
// 2 bytes extended payload length
byteCount = 2;
}
// Decode Payload Length
while (--byteCount > 0)
{
b = buf.get();
payloadLength |= (b & 0xFF) << (8 * byteCount);
}
// TODO: add control frame payload length validation here
byte maskingKey[] = null;
if (masked)
{
// Masking Key
maskingKey = new byte[4];
buf.get(maskingKey,0,4);
}
// TODO: add masked + maskingkey validation here
// Payload itself
frame.payload = new byte[payloadLength];
buf.get(frame.payload,0,payloadLength);
// Demask (if needed)
if (masked)
{
for (int i = 0; i < frame.payload.length; i++)
{
frame.payload[i] ^= maskingKey[i % 4];
}
}
return frame;
}
public static void main(String[] args)
{
Charset UTF8 = Charset.forName("UTF-8");
Frame closeFrame = parse(hexToByteArray("8800"));
System.out.printf("closeFrame.opcode = %d%n",closeFrame.opcode);
System.out.printf("closeFrame.payload.length = %d%n",closeFrame.payload.length);
// Examples from https://www.rfc-editor.org/rfc/rfc6455#section-5.7
Frame unmaskedTextFrame = parse(hexToByteArray("810548656c6c6f"));
System.out.printf("unmaskedTextFrame.opcode = %d%n",unmaskedTextFrame.opcode);
System.out.printf("unmaskedTextFrame.payload.length = %d%n",unmaskedTextFrame.payload.length);
System.out.printf("unmaskedTextFrame.payload = \"%s\"%n",new String(unmaskedTextFrame.payload,UTF8));
Frame maskedTextFrame = parse(hexToByteArray("818537fa213d7f9f4d5158"));
System.out.printf("maskedTextFrame.opcode = %d%n",maskedTextFrame.opcode);
System.out.printf("maskedTextFrame.payload.length = %d%n",maskedTextFrame.payload.length);
System.out.printf("maskedTextFrame.payload = \"%s\"%n",new String(maskedTextFrame.payload,UTF8));
}
public static byte[] hexToByteArray(String hstr)
{
if ((hstr.length() < 0) || ((hstr.length() % 2) != 0))
{
throw new IllegalArgumentException(String.format("Invalid string length of <%d>",hstr.length()));
}
int size = hstr.length() / 2;
byte buf[] = new byte[size];
byte hex;
int len = hstr.length();
int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
for (int i = 0; i < len; i++)
{
hex = 0;
if (i >= 0)
{
hex = (byte)(Character.digit(hstr.charAt(i),16) << 4);
}
i++;
hex += (byte)(Character.digit(hstr.charAt(i),16));
buf[idx] = hex;
idx++;
}
return buf;
}
}