Resolve-min/tlanyan.me/resolve-min…

The client of the project needs to be reconstructed. After discussion, WebSocket protocol is used for the interaction between the client and the server. The back-end network layer framework is Mina, and the seamless migration can be completed by adding the resolution of WebSocket on the existing basis. It just so happens that someone on the Apache discussion group is providing Mina’s WebSocket filter code. Download it, add it to the project, throw it on the server and everything works fine.

Today, the front-end colleague reported that a request for an interface will cause the client to immediately report an error and disconnect. The message “One or more reserved bits are on, reserveD1 = 0, reserved2 = 1, reserved3 = 1” is displayed. When you see the message, your first reaction is: is the data being sent to the client unwrapped? Check log and make sure data is encapsulated in WebSocket format and then sent out.

According to the error information online search solutions, basically blame antivirus software, system, browser, etc., did not find reliable reasons and solutions. The wireshark is used to capture packets and compare the packets with the server data. After a few twists and turns, it is confirmed that the interface returned a large amount of data and the client failed to receive it completely.

Is there a limit to the size of the WebSocket client buffer or single transfer? A search on the Internet found that there were no restrictions, most likely because the server did not packet data correctly. Since we are using third-party non-authoritative code packets for data, it is necessary to go through the encoded code:

1 /* 2 * To change this template, choose Tools | Templates 3 * and open the template in the editor. 4 */ 5 package com.shephertz.appwarp.websocket.binary;  6 7 import org.apache.mina.core.buffer.IoBuffer; 8 import org.apache.mina.core.session.IoSession; 9 import org.apache.mina.filter.codec.ProtocolEncoderAdapter; 10 import org.apache.mina.filter.codec.ProtocolEncoderOutput; 11 12 /** 13 * Encodes incoming buffers in a manner that makes the receiving client type transparent to the 14 * encoders further up in the filter chain. If the receiving client is a native client then 15 * the buffer contents are simply passed through. If the receiving client is a websocket, it will encode 16 * the buffer contents in to WebSocket DataFrame before passing it along the filter chain. 17 * 18 * Note: you must wrap the IoBuffer you want to send around a WebSocketCodecPacket instance. 19 * 20 * @author DHRUV CHOPRA 21 */  22 public class WebSocketEncoder extends ProtocolEncoderAdapter{ 23 24 @Override 25 public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { 26 boolean isHandshakeResponse = message instanceof WebSocketHandShakeResponse; 27 boolean isDataFramePacket = message instanceof WebSocketCodecPacket; 28 boolean isRemoteWebSocket = session.containsAttribute(WebSocketUtils.SessionAttribute) && (true==(Boolean)session.getAttribute(WebSocketUtils.SessionAttribute)); 29 IoBuffer resultBuffer; 30 if(isHandshakeResponse){ 31 WebSocketHandShakeResponse response = (WebSocketHandShakeResponse)message; 32 resultBuffer = WebSocketEncoder.buildWSResponseBuffer(response); 33 } 34 else if(isDataFramePacket){ 35 WebSocketCodecPacket packet = (WebSocketCodecPacket)message; 36 resultBuffer = isRemoteWebSocket ? WebSocketEncoder.buildWSDataFrameBuffer(packet.getPacket()) : packet.getPacket(); 37 } 38 else{ 39 throw (new Exception("message not a websocket type")); 40 } 41 42 out.write(resultBuffer); 43 } 44 45 // Web Socket handshake response go as a plain string. 46 private static IoBuffer buildWSResponseBuffer(WebSocketHandShakeResponse response) { 47 IoBuffer buffer = IoBuffer.allocate(response.getResponse().getBytes().length, false); 48 buffer.setAutoExpand(true); 49 buffer.put(response.getResponse().getBytes()); 50 buffer.flip(); 51 return buffer; RFC 6455 55 private static IoBuffer 63 // Encode the in buffer according to the Section 5.2. RFC 6455 55 buildWSDataFrameBuffer(IoBuffer buf) { 56 57 IoBuffer buffer = IoBuffer.allocate(buf.limit() + 2, false); 58 buffer.setAutoExpand(true); 59 buffer.put((byte) 0x82); 60 if(buffer.capacity() <= 125){ 61 byte capacity = (byte) (buf.limit()); 62 buffer.put(capacity); 63 } 64 else{ 65 buffer.put((byte)126); 66 buffer.putShort((short)buf.limit()); 67 } 68 buffer.put(buf); 69 buffer.flip(); 70 return buffer; 72 71}}Copy the code

Lines 51 through 71 of the code encapsulate the data, and your intuition suggests that lines 60 through 67 have problems with large amounts of data. Open the RFC page of WebSocket to see the packet structure defined in the specification. According to the specification, when a packet is less than 125, only two bytes need to be added to the packet header. If the packet is greater than or equal to 126 and less than or equal to 2^16, the packet header contains 4 bytes. For larger data lengths, the packet header has 10 bytes, of which 8 bytes are used to store the length of the data.

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload  Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+Copy the code

The downloaded third-party code processes only the payload len<=126. When there is a large amount of data, the downloaded third-party code processes the payload len == 127 as payload len == 126. Therefore, the client receives incomplete data. Change the function that builds WebSocket data frames to the following to solve the problem:

RFC 6455 private static IoBuffer buildWSDataFrameBuffer(IoBuffer buildWSDataFrameBuffer buf) { IoBuffer buffer = null; if (buf.limit() <= 125) { buffer = IoBuffer.allocate(buf.limit() + 2, false); buffer.put((byte) 0x82); buffer.put((byte)buf.limit()); } else if (buf.limit() <= 0xFFFF) { buffer = IoBuffer.allocate(buf.limit() + 4, false); buffer.put((byte) 0x82); buffer.put((byte)126); buffer.putShort((short)buf.limit()); } else { buffer = IoBuffer.allocate(buf.limit() + 10, false); buffer.put((byte) 0x82); buffer.put((byte)127); buffer.putLong((long)buf.limit()); } buffer.put(buf); buffer.flip(); return buffer; }Copy the code

There are two major changes: 1. The processing of new data greater than 2^16; 2. Allocate a buffer of an appropriate size to avoid triggering redistribution.

conclusion

According to the specification, the error message “One or more reserved bits are on” is caused by incorrect server packet encapsulation. In addition, a github search for webSocket resolution library found cjoo/WebSocket_mina packet processing error. The wrong place is github.com/cjoo/WebSoc… Lines 70 to 72 in.

reference

  1. Issues.apache.org/jira/browse…
  2. Tools.ietf.org/html/rfc645…