/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ // (BSD License: http://www.opensource.org/licenses/bsd-license) // // Copyright (c) 2011, Joe Walnes and contributors // All rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the // following conditions are met: // // * Redistributions of source code must retain the above // copyright notice, this list of conditions and the // following disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the // following disclaimer in the documentation and/or other // materials provided with the distribution. // // * Neither the name of the Webbit nor the names of // its contributors may be used to endorse or promote products // derived from this software without specific prior written // permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package io.netty.handler.codec.http.websocketx; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.TooLongFrameException; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.List; /** * <p> * Encodes a web socket frame into wire protocol version 8 format. This code was forked from <a * href="https://github.com/joewalnes/webbit">webbit</a> and modified. * </p> */ public class WebSocket08FrameEncoder extends MessageToMessageEncoder<WebSocketFrame> implements WebSocketFrameEncoder { private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class); private static final byte OPCODE_CONT = 0x0; private static final byte OPCODE_TEXT = 0x1; private static final byte OPCODE_BINARY = 0x2; private static final byte OPCODE_CLOSE = 0x8; private static final byte OPCODE_PING = 0x9; private static final byte OPCODE_PONG = 0xA; /** * The size treshold for gathering writes. Non-Masked messages bigger than this size will be be sent fragmented as * a header and a content ByteBuf whereas messages smaller than the size will be merged into a single buffer and * sent at once.<br> * Masked messages will always be sent at once. */ private static final int GATHERING_WRITE_TRESHOLD = 1024; private final boolean maskPayload; /** * Constructor * * @param maskPayload * Web socket clients must set this to true to mask payload. Server implementations must set this to * false. */ public WebSocket08FrameEncoder(boolean maskPayload) { this.maskPayload = maskPayload; } /** * 将websocket frame 片段编码成满足websocket13协议的 * <br> * 其执行过程是将msg 按照要求处理之后,变成ByteBuffere 然后放到List中。 */ @Override protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception { final ByteBuf data = msg.content(); byte[] mask; // 判断msg 类型,如文本,二进制等等,其中要特别注意的是ContinuationWebSocketFrame byte opcode; if (msg instanceof TextWebSocketFrame) { opcode = OPCODE_TEXT; } else if (msg instanceof PingWebSocketFrame) { opcode = OPCODE_PING; } else if (msg instanceof PongWebSocketFrame) { opcode = OPCODE_PONG; } else if (msg instanceof CloseWebSocketFrame) { opcode = OPCODE_CLOSE; } else if (msg instanceof BinaryWebSocketFrame) { opcode = OPCODE_BINARY; } else if (msg instanceof ContinuationWebSocketFrame) { opcode = OPCODE_CONT; } else { throw new UnsupportedOperationException("Cannot encode frame of type: " + msg.getClass().getName()); } // frame 实际内容长度 int length = data.readableBytes(); if (logger.isDebugEnabled()) { logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length); } // b0 是第一个byte,如果不是Continuation,则FIN 至1 int b0 = 0; if (msg.isFinalFragment()) { b0 |= 1 << 7; } // 判断是不是用到协议扩展 b0 |= msg.rsv() % 8 << 4; // 添加opcode 来自WebsocketFrame b0 |= opcode % 128; // ping 没有内容,或者内容不允许长度大于125 if (opcode == OPCODE_PING && length > 125) { throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length); } boolean release = true; ByteBuf buf = null; try { // 如果掩码处理,添加4个byte 的掩码 int maskLength = maskPayload ? 4 : 0; if (length <= 125) { // 长度小于125 ,该处写入2个 byte--- // 两个字节的头部(FIN+opcode#mask+loadLen)加四个字节的掩码 int size = 2 + maskLength; // 如果有掩码或者是frame 真实内容长度小于1k,加上真实内容长度 if (maskPayload || length <= GATHERING_WRITE_TRESHOLD) { size += length; } buf = ctx.alloc().buffer(size); buf.writeByte(b0); // b 是第二个byte,负责 mask +load Len byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length); buf.writeByte(b); } else if (length <= 0xFFFF) { // 长度小于2^16 ,该处写入4个byte--- int size = 4 + maskLength; if (maskPayload || length <= GATHERING_WRITE_TRESHOLD) { size += length; } buf = ctx.alloc().buffer(size); buf.writeByte(b0); buf.writeByte(maskPayload ? 0xFE : 126); buf.writeByte(length >>> 8 & 0xFF); buf.writeByte(length & 0xFF); } else { // 长度小于2^64 ,该处写入10个byte--- int size = 10 + maskLength; if (maskPayload || length <= GATHERING_WRITE_TRESHOLD) { size += length; } buf = ctx.alloc().buffer(size); buf.writeByte(b0); buf.writeByte(maskPayload ? 0xFF : 127); buf.writeLong(length); } // Write payload if (maskPayload) { int random = (int) (Math.random() * Integer.MAX_VALUE); // 掩码四个byte ,写入 mask = ByteBuffer.allocate(4).putInt(random).array(); buf.writeBytes(mask); ByteOrder srcOrder = data.order(); ByteOrder dstOrder = buf.order(); int counter = 0; int i = data.readerIndex(); int end = data.writerIndex(); if (srcOrder == dstOrder) { // Use the optimized path only when byte orders match // Remark: & 0xFF is necessary because Java will do signed expansion from // byte to int which we don't want. int intMask = ((mask[0] & 0xFF) << 24) | ((mask[1] & 0xFF) << 16) | ((mask[2] & 0xFF) << 8) | (mask[3] & 0xFF); // If the byte order of our buffers it little endian we have to bring our mask // into the same format, because getInt() and writeInt() will use a reversed byte order if (srcOrder == ByteOrder.LITTLE_ENDIAN) { intMask = Integer.reverseBytes(intMask); } for (; i + 3 < end; i += 4) { int intData = data.getInt(i); buf.writeInt(intData ^ intMask); } } for (; i < end; i++) { byte byteData = data.getByte(i); // 掩码处理之后,写入frame 真实内容 buf.writeByte(byteData ^ mask[counter++ % 4]); } out.add(buf); } else { if (buf.writableBytes() >= data.readableBytes()) { // merge buffers as this is cheaper then a gathering write if the payload is small enough buf.writeBytes(data); out.add(buf); } else { out.add(buf); out.add(data.retain()); } } release = false; } finally { if (release && buf != null) { buf.release(); } } } }