[#910] Make use of ByteBufHolder in SPDY, HTTP multipart and WebSockets to allow for buffer pooling

This commit is contained in:
Norman Maurer 2013-01-11 07:46:56 +01:00
parent f568aa42f0
commit dfbecb796c
40 changed files with 585 additions and 416 deletions

View File

@ -353,4 +353,12 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
return file; return file;
} }
@Override
public boolean isFreed() {
if (file == null || !file.exists()) {
return true;
}
return false;
}
} }

View File

@ -15,8 +15,11 @@
*/ */
package io.netty.handler.codec.http.multipart; package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpConstants; import io.netty.handler.codec.http.HttpConstants;
import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
/** /**
@ -96,4 +99,19 @@ public abstract class AbstractHttpData implements HttpData {
public long length() { public long length() {
return size; return size;
} }
@Override
public ByteBuf data() {
try {
return getByteBuf();
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void free() {
delete();
}
} }

View File

@ -226,4 +226,9 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
public File getFile() throws IOException { public File getFile() throws IOException {
throw new IOException("Not represented by a file"); throw new IOException("Not represented by a file");
} }
@Override
public boolean isFreed() {
return data().isFreed();
}
} }

View File

@ -30,4 +30,7 @@ public interface Attribute extends HttpData {
* Sets the value of this HttpData. * Sets the value of this HttpData.
*/ */
void setValue(String value) throws IOException; void setValue(String value) throws IOException;
@Override
Attribute copy();
} }

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http.multipart; package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpConstants; import io.netty.handler.codec.http.HttpConstants;
import java.io.IOException; import java.io.IOException;
@ -138,4 +139,19 @@ public class DiskAttribute extends AbstractDiskHttpData implements Attribute {
protected String getPrefix() { protected String getPrefix() {
return prefix; return prefix;
} }
@Override
public DiskAttribute copy() {
DiskAttribute attr = new DiskAttribute(getName());
attr.setCharset(getCharset());
ByteBuf content = data();
if (content != null) {
try {
attr.setContent(content.copy());
} catch (IOException e) {
throw new ChannelException(e);
}
}
return attr;
}
} }

View File

@ -15,9 +15,12 @@
*/ */
package io.netty.handler.codec.http.multipart; package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
/** /**
@ -159,4 +162,19 @@ public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload {
protected String getPrefix() { protected String getPrefix() {
return prefix; return prefix;
} }
@Override
public DiskFileUpload copy() {
DiskFileUpload upload = new DiskFileUpload(getName(),
getFilename(), getContentType(), getContentTransferEncoding(), getCharset(), size);
ByteBuf buf = data();
if (buf != null) {
try {
upload.setContent(buf.copy());
} catch (IOException e) {
throw new ChannelException(e);
}
}
return upload;
}
} }

View File

@ -55,4 +55,7 @@ public interface FileUpload extends HttpData {
* @return the Content-Transfer-Encoding * @return the Content-Transfer-Encoding
*/ */
String getContentTransferEncoding(); String getContentTransferEncoding();
@Override
FileUpload copy();
} }

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http.multipart; package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -25,7 +26,7 @@ import java.nio.charset.Charset;
/** /**
* Extended interface for InterfaceHttpData * Extended interface for InterfaceHttpData
*/ */
public interface HttpData extends InterfaceHttpData { public interface HttpData extends InterfaceHttpData, ByteBufHolder {
/** /**
* Set the content from the ChannelBuffer (erase any previous data) * Set the content from the ChannelBuffer (erase any previous data)
* *
@ -176,4 +177,6 @@ public interface HttpData extends InterfaceHttpData {
*/ */
File getFile() throws IOException; File getFile() throws IOException;
@Override
HttpData copy();
} }

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http.multipart; package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpConstants; import io.netty.handler.codec.http.HttpConstants;
import java.io.IOException; import java.io.IOException;
@ -100,4 +101,18 @@ public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute
return getName() + '=' + getValue(); return getName() + '=' + getValue();
} }
@Override
public MemoryAttribute copy() {
MemoryAttribute attr = new MemoryAttribute(getName());
attr.setCharset(getCharset());
ByteBuf content = data();
if (content != null) {
try {
attr.setContent(content.copy());
} catch (IOException e) {
throw new ChannelException(e);
}
}
return attr;
}
} }

View File

@ -15,8 +15,11 @@
*/ */
package io.netty.handler.codec.http.multipart; package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
/** /**
@ -125,4 +128,20 @@ public class MemoryFileUpload extends AbstractMemoryHttpData implements FileUplo
"Completed: " + isCompleted() + "Completed: " + isCompleted() +
"\r\nIsInMemory: " + isInMemory(); "\r\nIsInMemory: " + isInMemory();
} }
@Override
public MemoryFileUpload copy() {
MemoryFileUpload upload = new MemoryFileUpload(getName(), getFilename(), getContentType(),
getContentTransferEncoding(), getCharset(), size);
ByteBuf buf = data();
if (buf != null) {
try {
upload.setContent(buf.copy());
return upload;
} catch (IOException e) {
throw new ChannelException(e);
}
}
return upload;
}
} }

View File

@ -199,4 +199,23 @@ public class MixedAttribute implements Attribute {
return attribute.getFile(); return attribute.getFile();
} }
@Override
public Attribute copy() {
return attribute.copy();
}
@Override
public ByteBuf data() {
return attribute.data();
}
@Override
public void free() {
attribute.free();
}
@Override
public boolean isFreed() {
return attribute.isFreed();
}
} }

View File

@ -224,4 +224,23 @@ public class MixedFileUpload implements FileUpload {
return fileUpload.getFile(); return fileUpload.getFile();
} }
@Override
public FileUpload copy() {
return fileUpload.copy();
}
@Override
public ByteBuf data() {
return fileUpload.data();
}
@Override
public void free() {
fileUpload.free();
}
@Override
public boolean isFreed() {
return fileUpload.isFreed();
}
} }

View File

@ -27,7 +27,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* Creates a new empty binary frame. * Creates a new empty binary frame.
*/ */
public BinaryWebSocketFrame() { public BinaryWebSocketFrame() {
setBinaryData(Unpooled.EMPTY_BUFFER); super(Unpooled.buffer(0));
} }
/** /**
@ -37,7 +37,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public BinaryWebSocketFrame(ByteBuf binaryData) { public BinaryWebSocketFrame(ByteBuf binaryData) {
setBinaryData(binaryData); super(binaryData);
} }
/** /**
@ -51,14 +51,11 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) { public BinaryWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
setBinaryData(binaryData);
} }
@Override @Override
public String toString() { public BinaryWebSocketFrame copy() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return new BinaryWebSocketFrame(isFinalFragment(), rsv(), data().copy());
} }
} }

View File

@ -30,7 +30,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* Creates a new empty close frame. * Creates a new empty close frame.
*/ */
public CloseWebSocketFrame() { public CloseWebSocketFrame() {
setBinaryData(Unpooled.EMPTY_BUFFER); super(Unpooled.buffer(0));
} }
/** /**
@ -72,9 +72,10 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* Reason text. Set to null if no text. * Reason text. Set to null if no text.
*/ */
public CloseWebSocketFrame(boolean finalFragment, int rsv, int statusCode, String reasonText) { public CloseWebSocketFrame(boolean finalFragment, int rsv, int statusCode, String reasonText) {
setFinalFragment(finalFragment); super(finalFragment, rsv, newBinaryData(statusCode, reasonText));
setRsv(rsv); }
private static ByteBuf newBinaryData(int statusCode, String reasonText) {
byte[] reasonBytes = EMTPY_REASON; byte[] reasonBytes = EMTPY_REASON;
if (reasonText != null) { if (reasonText != null) {
reasonBytes = reasonText.getBytes(CharsetUtil.UTF_8); reasonBytes = reasonText.getBytes(CharsetUtil.UTF_8);
@ -87,7 +88,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
} }
binaryData.readerIndex(0); binaryData.readerIndex(0);
setBinaryData(binaryData); return binaryData;
} }
/** /**
@ -101,21 +102,15 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* the content of the frame. Must be 2 byte integer followed by optional UTF-8 encoded string. * the content of the frame. Must be 2 byte integer followed by optional UTF-8 encoded string.
*/ */
public CloseWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) { public CloseWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
if (binaryData == null) {
setBinaryData(Unpooled.EMPTY_BUFFER);
} else {
setBinaryData(binaryData);
}
} }
/** /**
* Returns the closing status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. If * Returns the closing status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. If
* a status code is set, -1 is returned. * a status code is set, -1 is returned.
*/ */
public int getStatusCode() { public int statusCode() {
ByteBuf binaryData = getBinaryData(); ByteBuf binaryData = data();
if (binaryData == null || binaryData.capacity() == 0) { if (binaryData == null || binaryData.capacity() == 0) {
return -1; return -1;
} }
@ -131,8 +126,8 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* Returns the reason text as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a> If a reason * Returns the reason text as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a> If a reason
* text is not supplied, an empty string is returned. * text is not supplied, an empty string is returned.
*/ */
public String getReasonText() { public String reasonText() {
ByteBuf binaryData = getBinaryData(); ByteBuf binaryData = data();
if (binaryData == null || binaryData.capacity() <= 2) { if (binaryData == null || binaryData.capacity() <= 2) {
return ""; return "";
} }
@ -145,7 +140,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
} }
@Override @Override
public String toString() { public CloseWebSocketFrame copy() {
return getClass().getSimpleName(); return new CloseWebSocketFrame(isFinalFragment(), rsv(), data().copy());
} }
} }

View File

@ -31,7 +31,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* Creates a new empty continuation frame. * Creates a new empty continuation frame.
*/ */
public ContinuationWebSocketFrame() { public ContinuationWebSocketFrame() {
setBinaryData(Unpooled.EMPTY_BUFFER); super(Unpooled.buffer(0));
} }
/** /**
@ -41,7 +41,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* @param binaryData the content of the frame. * @param binaryData the content of the frame.
*/ */
public ContinuationWebSocketFrame(ByteBuf binaryData) { public ContinuationWebSocketFrame(ByteBuf binaryData) {
setBinaryData(binaryData); super(binaryData);
} }
/** /**
@ -55,9 +55,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
setBinaryData(binaryData);
} }
/** /**
@ -75,9 +73,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
*/ */
public ContinuationWebSocketFrame( public ContinuationWebSocketFrame(
boolean finalFragment, int rsv, ByteBuf binaryData, String aggregatedText) { boolean finalFragment, int rsv, ByteBuf binaryData, String aggregatedText) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
setBinaryData(binaryData);
this.aggregatedText = aggregatedText; this.aggregatedText = aggregatedText;
} }
@ -92,19 +88,14 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* text content of the frame. * text content of the frame.
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
setFinalFragment(finalFragment); this(finalFragment, rsv, fromText(text), null);
setRsv(rsv);
setText(text);
} }
/** /**
* Returns the text data in this frame * Returns the text data in this frame
*/ */
public String getText() { public String text() {
if (getBinaryData() == null) { return data().toString(CharsetUtil.UTF_8);
return null;
}
return getBinaryData().toString(CharsetUtil.UTF_8);
} }
/** /**
@ -113,28 +104,24 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* @param text * @param text
* text to store * text to store
*/ */
public void setText(String text) { private static ByteBuf fromText(String text) {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
setBinaryData(Unpooled.EMPTY_BUFFER); return Unpooled.EMPTY_BUFFER;
} else { } else {
setBinaryData(Unpooled.copiedBuffer(text, CharsetUtil.UTF_8)); return Unpooled.copiedBuffer(text, CharsetUtil.UTF_8);
} }
} }
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
/** /**
* Aggregated text returned by decoder on the final continuation frame of a fragmented text message * Aggregated text returned by decoder on the final continuation frame of a fragmented text message
*/ */
public String getAggregatedText() { public String aggregatedText() {
return aggregatedText; return aggregatedText;
} }
public void setAggregatedText(String aggregatedText) { @Override
this.aggregatedText = aggregatedText; public ContinuationWebSocketFrame copy() {
return new ContinuationWebSocketFrame(isFinalFragment(), rsv(), data().copy(), aggregatedText());
} }
} }

View File

@ -27,8 +27,7 @@ public class PingWebSocketFrame extends WebSocketFrame {
* Creates a new empty ping frame. * Creates a new empty ping frame.
*/ */
public PingWebSocketFrame() { public PingWebSocketFrame() {
setFinalFragment(true); super(true, 0, Unpooled.buffer(0));
setBinaryData(Unpooled.EMPTY_BUFFER);
} }
/** /**
@ -38,7 +37,7 @@ public class PingWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PingWebSocketFrame(ByteBuf binaryData) { public PingWebSocketFrame(ByteBuf binaryData) {
setBinaryData(binaryData); super(binaryData);
} }
/** /**
@ -52,14 +51,11 @@ public class PingWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PingWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) { public PingWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
setBinaryData(binaryData);
} }
@Override @Override
public String toString() { public PingWebSocketFrame copy() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return new PingWebSocketFrame(isFinalFragment(), rsv(), data().copy());
} }
} }

View File

@ -27,7 +27,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
* Creates a new empty pong frame. * Creates a new empty pong frame.
*/ */
public PongWebSocketFrame() { public PongWebSocketFrame() {
setBinaryData(Unpooled.EMPTY_BUFFER); super(Unpooled.buffer(0));
} }
/** /**
@ -37,7 +37,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PongWebSocketFrame(ByteBuf binaryData) { public PongWebSocketFrame(ByteBuf binaryData) {
setBinaryData(binaryData); super(binaryData);
} }
/** /**
@ -51,14 +51,12 @@ public class PongWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PongWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) { public PongWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
setBinaryData(binaryData);
} }
@Override @Override
public String toString() { public PongWebSocketFrame copy() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')'; return new PongWebSocketFrame(isFinalFragment(), rsv(), data().copy());
} }
} }

View File

@ -28,7 +28,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* Creates a new empty text frame. * Creates a new empty text frame.
*/ */
public TextWebSocketFrame() { public TextWebSocketFrame() {
setBinaryData(Unpooled.EMPTY_BUFFER); super(Unpooled.buffer(0));
} }
/** /**
@ -38,11 +38,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* String to put in the frame * String to put in the frame
*/ */
public TextWebSocketFrame(String text) { public TextWebSocketFrame(String text) {
if (text == null || text.isEmpty()) { super(fromText(text));
setBinaryData(Unpooled.EMPTY_BUFFER);
} else {
setBinaryData(Unpooled.copiedBuffer(text, CharsetUtil.UTF_8));
}
} }
/** /**
@ -52,7 +48,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* the content of the frame. Must be UTF-8 encoded * the content of the frame. Must be UTF-8 encoded
*/ */
public TextWebSocketFrame(ByteBuf binaryData) { public TextWebSocketFrame(ByteBuf binaryData) {
setBinaryData(binaryData); super(binaryData);
} }
/** /**
@ -66,12 +62,14 @@ public class TextWebSocketFrame extends WebSocketFrame {
* String to put in the frame * String to put in the frame
*/ */
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) { public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
setFinalFragment(finalFragment); super(finalFragment, rsv, fromText(text));
setRsv(rsv); }
private static ByteBuf fromText(String text) {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
setBinaryData(Unpooled.EMPTY_BUFFER); return Unpooled.EMPTY_BUFFER;
} else { } else {
setBinaryData(Unpooled.copiedBuffer(text, CharsetUtil.UTF_8)); return Unpooled.copiedBuffer(text, CharsetUtil.UTF_8);
} }
} }
@ -86,36 +84,18 @@ public class TextWebSocketFrame extends WebSocketFrame {
* the content of the frame. Must be UTF-8 encoded * the content of the frame. Must be UTF-8 encoded
*/ */
public TextWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) { public TextWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
setFinalFragment(finalFragment); super(finalFragment, rsv, binaryData);
setRsv(rsv);
setBinaryData(binaryData);
} }
/** /**
* Returns the text data in this frame * Returns the text data in this frame
*/ */
public String getText() { public String text() {
if (getBinaryData() == null) { return data().toString(CharsetUtil.UTF_8);
return null;
}
return getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
* Sets the string for this frame
*
* @param text
* text to store
*/
public void setText(String text) {
if (text == null) {
throw new NullPointerException("text");
}
setBinaryData(Unpooled.copiedBuffer(text, CharsetUtil.UTF_8));
} }
@Override @Override
public String toString() { public TextWebSocketFrame copy() {
return getClass().getSimpleName() + "(text: " + getText() + ')'; return new TextWebSocketFrame(isFinalFragment(), rsv(), data().copy());
} }
} }

View File

@ -35,6 +35,8 @@
*/ */
package io.netty.handler.codec.http.websocketx; package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ByteBuf;
/** /**
* Checks UTF8 bytes for validity before converting it into a string * Checks UTF8 bytes for validity before converting it into a string
*/ */
@ -66,9 +68,15 @@ final class UTF8Output {
private final StringBuilder stringBuilder; private final StringBuilder stringBuilder;
UTF8Output(byte[] bytes) { UTF8Output(ByteBuf buffer) {
stringBuilder = new StringBuilder(bytes.length); stringBuilder = new StringBuilder(buffer.readableBytes());
write(bytes); write(buffer);
}
public void write(ByteBuf buffer) {
for (int i = buffer.readerIndex(); i < buffer.writerIndex(); i++) {
write(buffer.getByte(i));
}
} }
public void write(byte[] bytes) { public void write(byte[] bytes) {

View File

@ -63,14 +63,14 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<Void> {
byte type = in.readByte(); byte type = in.readByte();
if ((type & 0x80) == 0x80) { if ((type & 0x80) == 0x80) {
// If the MSB on type is set, decode the frame length // If the MSB on type is set, decode the frame length
return decodeBinaryFrame(type, in); return decodeBinaryFrame(ctx, type, in);
} else { } else {
// Decode a 0xff terminated UTF-8 string // Decode a 0xff terminated UTF-8 string
return decodeTextFrame(in); return decodeTextFrame(ctx, in);
} }
} }
private WebSocketFrame decodeBinaryFrame(byte type, ByteBuf buffer) { private WebSocketFrame decodeBinaryFrame(ChannelHandlerContext ctx, byte type, ByteBuf buffer) {
long frameSize = 0; long frameSize = 0;
int lengthFieldSize = 0; int lengthFieldSize = 0;
byte b; byte b;
@ -92,11 +92,12 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<Void> {
receivedClosingHandshake = true; receivedClosingHandshake = true;
return new CloseWebSocketFrame(); return new CloseWebSocketFrame();
} }
ByteBuf payload = ctx.alloc().buffer((int) frameSize);
return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize)); buffer.readBytes(payload);
return new BinaryWebSocketFrame(payload);
} }
private WebSocketFrame decodeTextFrame(ByteBuf buffer) { private WebSocketFrame decodeTextFrame(ChannelHandlerContext ctx, ByteBuf buffer) {
int ridx = buffer.readerIndex(); int ridx = buffer.readerIndex();
int rbytes = actualReadableBytes(); int rbytes = actualReadableBytes();
int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF); int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
@ -116,7 +117,8 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<Void> {
throw new TooLongFrameException(); throw new TooLongFrameException();
} }
ByteBuf binaryData = buffer.readBytes(frameSize); ByteBuf binaryData = ctx.alloc().buffer(frameSize);
buffer.readBytes(binaryData);
buffer.skipBytes(1); buffer.skipBytes(1);
int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF); int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF);

View File

@ -42,7 +42,7 @@ public class WebSocket00FrameEncoder extends MessageToByteEncoder<WebSocketFrame
WebSocketFrame msg, ByteBuf out) throws Exception { WebSocketFrame msg, ByteBuf out) throws Exception {
if (msg instanceof TextWebSocketFrame) { if (msg instanceof TextWebSocketFrame) {
// Text frame // Text frame
ByteBuf data = msg.getBinaryData(); ByteBuf data = msg.data();
out.writeByte((byte) 0x00); out.writeByte((byte) 0x00);
out.writeBytes(data, data.readerIndex(), data.readableBytes()); out.writeBytes(data, data.readerIndex(), data.readableBytes());
out.writeByte((byte) 0xFF); out.writeByte((byte) 0xFF);
@ -52,7 +52,7 @@ public class WebSocket00FrameEncoder extends MessageToByteEncoder<WebSocketFrame
out.writeByte((byte) 0x00); out.writeByte((byte) 0x00);
} else { } else {
// Binary frame // Binary frame
ByteBuf data = msg.getBinaryData(); ByteBuf data = msg.data();
int dataLen = data.readableBytes(); int dataLen = data.readableBytes();
out.ensureWritableBytes(dataLen + 5); out.ensureWritableBytes(dataLen + 5);

View File

@ -54,7 +54,6 @@
package io.netty.handler.codec.http.websocketx; package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.CorruptedFrameException;
@ -128,242 +127,246 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
} }
switch (state()) { switch (state()) {
case FRAME_START: case FRAME_START:
framePayloadBytesRead = 0; framePayloadBytesRead = 0;
framePayloadLength = -1; framePayloadLength = -1;
framePayload = null; framePayload = null;
// FIN, RSV, OPCODE // FIN, RSV, OPCODE
byte b = in.readByte(); byte b = in.readByte();
frameFinalFlag = (b & 0x80) != 0; frameFinalFlag = (b & 0x80) != 0;
frameRsv = (b & 0x70) >> 4; frameRsv = (b & 0x70) >> 4;
frameOpcode = b & 0x0F; frameOpcode = b & 0x0F;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode); logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode);
} }
// MASK, PAYLOAD LEN 1 // MASK, PAYLOAD LEN 1
b = in.readByte(); b = in.readByte();
boolean frameMasked = (b & 0x80) != 0; boolean frameMasked = (b & 0x80) != 0;
int framePayloadLen1 = b & 0x7F; int framePayloadLen1 = b & 0x7F;
if (frameRsv != 0 && !allowExtensions) { if (frameRsv != 0 && !allowExtensions) {
protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv); protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
return null;
}
if (maskedPayload && !frameMasked) {
protocolViolation(ctx, "unmasked client to server frame");
return null;
}
if (frameOpcode > 7) { // control frame (have MSB in opcode set)
// control frames MUST NOT be fragmented
if (!frameFinalFlag) {
protocolViolation(ctx, "fragmented control frame");
return null; return null;
} }
// control frames MUST have payload 125 octets or less if (maskedPayload && !frameMasked) {
if (framePayloadLen1 > 125) { protocolViolation(ctx, "unmasked client to server frame");
protocolViolation(ctx, "control frame with payload length > 125 octets");
return null; return null;
} }
if (frameOpcode > 7) { // control frame (have MSB in opcode set)
// check for reserved control frame opcodes // control frames MUST NOT be fragmented
if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) { if (!frameFinalFlag) {
protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode); protocolViolation(ctx, "fragmented control frame");
return null; return null;
} }
// close frame : if there is a body, the first two bytes of the // control frames MUST have payload 125 octets or less
// body MUST be a 2-byte unsigned integer (in network byte if (framePayloadLen1 > 125) {
// order) representing a status code protocolViolation(ctx, "control frame with payload length > 125 octets");
if (frameOpcode == 8 && framePayloadLen1 == 1) { return null;
protocolViolation(ctx, "received close control frame with payload len 1"); }
return null;
}
} else { // data frame
// check for reserved data frame opcodes
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) {
protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
return null;
}
// check opcode vs message fragmentation state 1/2 // check for reserved control frame opcodes
if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) { if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) {
protocolViolation(ctx, "received continuation data frame outside fragmented message"); protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode);
return null; return null;
} }
// check opcode vs message fragmentation state 2/2 // close frame : if there is a body, the first two bytes of the
if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) { // body MUST be a 2-byte unsigned integer (in network byte
protocolViolation(ctx, "received non-continuation data frame while inside fragmented message"); // order) representing a status code
return null; if (frameOpcode == 8 && framePayloadLen1 == 1) {
} protocolViolation(ctx, "received close control frame with payload len 1");
} return null;
}
} else { // data frame
// check for reserved data frame opcodes
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) {
protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
return null;
}
// Read frame payload length // check opcode vs message fragmentation state 1/2
if (framePayloadLen1 == 126) { if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
framePayloadLength = in.readUnsignedShort(); protocolViolation(ctx, "received continuation data frame outside fragmented message");
if (framePayloadLength < 126) { return null;
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)"); }
return null;
}
} else if (framePayloadLen1 == 127) {
framePayloadLength = in.readLong();
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
// just check if it's negative?
if (framePayloadLength < 65536) { // check opcode vs message fragmentation state 2/2
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)"); if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
return null; protocolViolation(ctx, "received non-continuation data frame while inside fragmented message");
} return null;
} else {
framePayloadLength = framePayloadLen1;
}
if (framePayloadLength > maxFramePayloadLength) {
protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("Decoding WebSocket Frame length=" + framePayloadLength);
}
checkpoint(State.MASKING_KEY);
case MASKING_KEY:
if (maskedPayload) {
maskingKey = in.readBytes(4);
}
checkpoint(State.PAYLOAD);
case PAYLOAD:
// Sometimes, the payload may not be delivered in 1 nice packet
// We need to accumulate the data until we have it all
int rbytes = actualReadableBytes();
ByteBuf payloadBuffer = null;
long willHaveReadByteCount = framePayloadBytesRead + rbytes;
// logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount="
// + willHaveReadByteCount + " framePayloadLength=" +
// framePayloadLength);
if (willHaveReadByteCount == framePayloadLength) {
// We have all our content so proceed to process
payloadBuffer = in.readBytes(rbytes);
} else if (willHaveReadByteCount < framePayloadLength) {
// We don't have all our content so accumulate payload.
// Returning null means we will get called back
payloadBuffer = in.readBytes(rbytes);
if (framePayload == null) {
framePayload = Unpooled.buffer(toFrameLength(framePayloadLength));
}
framePayload.writeBytes(payloadBuffer);
framePayloadBytesRead += rbytes;
// Return null to wait for more bytes to arrive
return null;
} else if (willHaveReadByteCount > framePayloadLength) {
// We have more than what we need so read up to the end of frame
// Leave the remainder in the buffer for next frame
payloadBuffer = in.readBytes(toFrameLength(framePayloadLength - framePayloadBytesRead));
}
// Now we have all the data, the next checkpoint must be the next
// frame
checkpoint(State.FRAME_START);
// Take the data that we have in this packet
if (framePayload == null) {
framePayload = payloadBuffer;
} else {
framePayload.writeBytes(payloadBuffer);
}
// Unmask data if needed
if (maskedPayload) {
unmask(framePayload);
}
// Processing ping/pong/close frames because they cannot be
// fragmented
if (frameOpcode == OPCODE_PING) {
return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
if (frameOpcode == OPCODE_PONG) {
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
if (frameOpcode == OPCODE_CLOSE) {
checkCloseFrameBody(ctx, framePayload);
receivedClosingHandshake = true;
return new CloseWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
// Processing for possible fragmented messages for text and binary
// frames
String aggregatedText = null;
if (frameFinalFlag) {
// Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message
if (frameOpcode != OPCODE_PING) {
fragmentedFramesCount = 0;
// Check text for UTF8 correctness
if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) {
// Check UTF-8 correctness for this payload
checkUTF8String(ctx, framePayload.array());
// This does a second check to make sure UTF-8
// correctness for entire text message
aggregatedText = fragmentedFramesText.toString();
fragmentedFramesText = null;
} }
} }
} else {
// Not final frame so we can expect more frames in the // Read frame payload length
// fragmented sequence if (framePayloadLen1 == 126) {
if (fragmentedFramesCount == 0) { framePayloadLength = in.readUnsignedShort();
// First text or binary frame for a fragmented set if (framePayloadLength < 126) {
fragmentedFramesText = null; protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
if (frameOpcode == OPCODE_TEXT) { return null;
checkUTF8String(ctx, framePayload.array()); }
} else if (framePayloadLen1 == 127) {
framePayloadLength = in.readLong();
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
// just check if it's negative?
if (framePayloadLength < 65536) {
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
return null;
} }
} else { } else {
// Subsequent frames - only check if init frame is text framePayloadLength = framePayloadLen1;
if (fragmentedFramesText != null) {
checkUTF8String(ctx, framePayload.array());
}
} }
// Increment counter if (framePayloadLength > maxFramePayloadLength) {
fragmentedFramesCount++; protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
} return null;
}
// Return the frame if (logger.isDebugEnabled()) {
if (frameOpcode == OPCODE_TEXT) { logger.debug("Decoding WebSocket Frame length=" + framePayloadLength);
return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload); }
} else if (frameOpcode == OPCODE_BINARY) {
return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload); checkpoint(State.MASKING_KEY);
} else if (frameOpcode == OPCODE_CONT) { case MASKING_KEY:
return new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload, aggregatedText); if (maskedPayload) {
} else { maskingKey = in.readBytes(4);
throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: " + frameOpcode); }
} checkpoint(State.PAYLOAD);
case CORRUPT: case PAYLOAD:
// If we don't keep reading Netty will throw an exception saying // Sometimes, the payload may not be delivered in 1 nice packet
// we can't return null if no bytes read and state not changed. // We need to accumulate the data until we have it all
in.readByte(); int rbytes = actualReadableBytes();
return null; ByteBuf payloadBuffer = null;
default:
throw new Error("Shouldn't reach here."); long willHaveReadByteCount = framePayloadBytesRead + rbytes;
// logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount="
// + willHaveReadByteCount + " framePayloadLength=" +
// framePayloadLength);
if (willHaveReadByteCount == framePayloadLength) {
// We have all our content so proceed to process
payloadBuffer = ctx.alloc().buffer(rbytes);
payloadBuffer.writeBytes(in, rbytes);
} else if (willHaveReadByteCount < framePayloadLength) {
// We don't have all our content so accumulate payload.
// Returning null means we will get called back
if (framePayload == null) {
framePayload = ctx.alloc().buffer(toFrameLength(framePayloadLength));
}
framePayload.writeBytes(in, rbytes);
framePayloadBytesRead += rbytes;
// Return null to wait for more bytes to arrive
return null;
} else if (willHaveReadByteCount > framePayloadLength) {
// We have more than what we need so read up to the end of frame
// Leave the remainder in the buffer for next frame
if (framePayload == null) {
framePayload = ctx.alloc().buffer(toFrameLength(framePayloadLength));
}
framePayload.writeBytes(in, toFrameLength(framePayloadLength - framePayloadBytesRead));
}
// Now we have all the data, the next checkpoint must be the next
// frame
checkpoint(State.FRAME_START);
// Take the data that we have in this packet
if (framePayload == null) {
framePayload = payloadBuffer;
} else if (payloadBuffer != null) {
framePayload.writeBytes(payloadBuffer);
}
// Unmask data if needed
if (maskedPayload) {
unmask(framePayload);
}
// Processing ping/pong/close frames because they cannot be
// fragmented
if (frameOpcode == OPCODE_PING) {
return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
if (frameOpcode == OPCODE_PONG) {
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
if (frameOpcode == OPCODE_CLOSE) {
checkCloseFrameBody(ctx, framePayload);
receivedClosingHandshake = true;
return new CloseWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
// Processing for possible fragmented messages for text and binary
// frames
String aggregatedText = null;
if (frameFinalFlag) {
// Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message
if (frameOpcode != OPCODE_PING) {
fragmentedFramesCount = 0;
// Check text for UTF8 correctness
if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) {
// Check UTF-8 correctness for this payload
checkUTF8String(ctx, framePayload);
// This does a second check to make sure UTF-8
// correctness for entire text message
aggregatedText = fragmentedFramesText.toString();
fragmentedFramesText = null;
}
}
} else {
// Not final frame so we can expect more frames in the
// fragmented sequence
if (fragmentedFramesCount == 0) {
// First text or binary frame for a fragmented set
fragmentedFramesText = null;
if (frameOpcode == OPCODE_TEXT) {
checkUTF8String(ctx, framePayload);
}
} else {
// Subsequent frames - only check if init frame is text
if (fragmentedFramesText != null) {
checkUTF8String(ctx, framePayload);
}
}
// Increment counter
fragmentedFramesCount++;
}
// Return the frame
if (frameOpcode == OPCODE_TEXT) {
return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_BINARY) {
return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_CONT) {
return new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload, aggregatedText);
} else {
throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: "
+ frameOpcode);
}
case CORRUPT:
// If we don't keep reading Netty will throw an exception saying
// we can't return null if no bytes read and state not changed.
in.readByte();
return null;
default:
throw new Error("Shouldn't reach here.");
} }
} }
private void unmask(ByteBuf frame) { private void unmask(ByteBuf frame) {
byte[] bytes = frame.array(); for (int i = frame.readerIndex(); i < frame.writerIndex(); i++) {
for (int i = 0; i < bytes.length; i++) {
frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4)); frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4));
} }
} }
@ -384,12 +387,12 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
} }
} }
private void checkUTF8String(ChannelHandlerContext ctx, byte[] bytes) { private void checkUTF8String(ChannelHandlerContext ctx, ByteBuf buffer) {
try { try {
if (fragmentedFramesText == null) { if (fragmentedFramesText == null) {
fragmentedFramesText = new UTF8Output(bytes); fragmentedFramesText = new UTF8Output(buffer);
} else { } else {
fragmentedFramesText.write(bytes); fragmentedFramesText.write(buffer);
} }
} catch (UTF8Exception ex) { } catch (UTF8Exception ex) {
protocolViolation(ctx, "invalid UTF-8 bytes"); protocolViolation(ctx, "invalid UTF-8 bytes");
@ -418,11 +421,10 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
} }
// May have UTF-8 message // May have UTF-8 message
if (buffer.readableBytes() > 0) { if (buffer.readable()) {
byte[] b = new byte[buffer.readableBytes()];
buffer.readBytes(b);
try { try {
new UTF8Output(b); new UTF8Output(buffer);
} catch (UTF8Exception ex) { } catch (UTF8Exception ex) {
protocolViolation(ctx, "Invalid close frame reason text. Invalid UTF-8 bytes"); protocolViolation(ctx, "Invalid close frame reason text. Invalid UTF-8 bytes");
} }

View File

@ -100,7 +100,7 @@ public class WebSocket08FrameEncoder extends MessageToByteEncoder<WebSocketFrame
byte[] mask; byte[] mask;
ByteBuf data = msg.getBinaryData(); ByteBuf data = msg.data();
if (data == null) { if (data == null) {
data = Unpooled.EMPTY_BUFFER; data = Unpooled.EMPTY_BUFFER;
} }
@ -132,7 +132,7 @@ public class WebSocket08FrameEncoder extends MessageToByteEncoder<WebSocketFrame
if (msg.isFinalFragment()) { if (msg.isFinalFragment()) {
b0 |= 1 << 7; b0 |= 1 << 7;
} }
b0 |= msg.getRsv() % 8 << 4; b0 |= msg.rsv() % 8 << 4;
b0 |= opcode % 128; b0 |= opcode % 128;
if (opcode == OPCODE_PING && length > 125) { if (opcode == OPCODE_PING && length > 125) {

View File

@ -16,40 +16,32 @@
package io.netty.handler.codec.http.websocketx; package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
/** /**
* Base class for web socket frames * Base class for web socket frames
*/ */
public abstract class WebSocketFrame { public abstract class WebSocketFrame extends DefaultByteBufHolder {
/** /**
* Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the * Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
* final fragment. * final fragment.
*/ */
private boolean finalFragment = true; private final boolean finalFragment;
/** /**
* RSV1, RSV2, RSV3 used for extensions * RSV1, RSV2, RSV3 used for extensions
*/ */
private int rsv; private final int rsv;
/** protected WebSocketFrame(ByteBuf binaryData) {
* Contents of this frame this(true, 0, binaryData);
*/
private ByteBuf binaryData;
/**
* Returns binary data
*/
public ByteBuf getBinaryData() {
return binaryData;
} }
/** protected WebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
* Sets the binary data for this frame super(binaryData);
*/ this.finalFragment = finalFragment;
public void setBinaryData(ByteBuf binaryData) { this.rsv = rsv;
this.binaryData = binaryData;
} }
/** /**
@ -60,19 +52,19 @@ public abstract class WebSocketFrame {
return finalFragment; return finalFragment;
} }
public void setFinalFragment(boolean finalFragment) {
this.finalFragment = finalFragment;
}
/** /**
* Bits used for extensions to the standard. * Bits used for extensions to the standard.
*/ */
public int getRsv() { public int rsv() {
return rsv; return rsv;
} }
public void setRsv(int rsv) { @Override
this.rsv = rsv; public abstract WebSocketFrame copy();
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + data().toString() + ')';
} }
} }

View File

@ -81,7 +81,7 @@ public class WebSocketServerProtocolHandler extends ChannelInboundMessageHandler
return; return;
} }
if (frame instanceof PingWebSocketFrame) { if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.channel().write(new PongWebSocketFrame(frame.data()));
return; return;
} }
@ -100,6 +100,15 @@ public class WebSocketServerProtocolHandler extends ChannelInboundMessageHandler
} }
} }
@Override
protected void freeInboundMessage(WebSocketFrame msg) throws Exception {
if (msg instanceof PingWebSocketFrame || msg instanceof CloseWebSocketFrame) {
// Will be freed once wrote back
return;
}
super.freeInboundMessage(msg);
}
static WebSocketServerHandshaker getHandshaker(ChannelHandlerContext ctx) { static WebSocketServerHandshaker getHandshaker(ChannelHandlerContext ctx) {
return ctx.attr(HANDSHAKER_ATTR_KEY).get(); return ctx.attr(HANDSHAKER_ATTR_KEY).get();
} }

View File

@ -16,17 +16,17 @@
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
* The default {@link SpdyDataFrame} implementation. * The default {@link SpdyDataFrame} implementation.
*/ */
public class DefaultSpdyDataFrame implements SpdyDataFrame { public class DefaultSpdyDataFrame extends DefaultByteBufHolder implements SpdyDataFrame {
private int streamId; private int streamId;
private boolean last; private boolean last;
private ByteBuf data = Unpooled.EMPTY_BUFFER;
/** /**
* Creates a new instance. * Creates a new instance.
@ -34,9 +34,28 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame {
* @param streamId the Stream-ID of this frame * @param streamId the Stream-ID of this frame
*/ */
public DefaultSpdyDataFrame(int streamId) { public DefaultSpdyDataFrame(int streamId) {
this(streamId, Unpooled.buffer(0));
}
/**
* Creates a new instance.
*
* @param streamId the Stream-ID of this frame
* @param data the payload of the frame. Can not exceed {@link SpdyCodecUtil#SPDY_MAX_LENGTH}
*/
public DefaultSpdyDataFrame(int streamId, ByteBuf data) {
super(validate(data));
setStreamId(streamId); setStreamId(streamId);
} }
private static ByteBuf validate(ByteBuf data) {
if (data.readableBytes() > SpdyCodecUtil.SPDY_MAX_LENGTH) {
throw new IllegalArgumentException("data payload cannot exceed "
+ SpdyCodecUtil.SPDY_MAX_LENGTH + " bytes");
}
return data;
}
@Override @Override
public int getStreamId() { public int getStreamId() {
return streamId; return streamId;
@ -62,20 +81,10 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame {
} }
@Override @Override
public ByteBuf getData() { public DefaultSpdyDataFrame copy() {
return data; DefaultSpdyDataFrame frame = new DefaultSpdyDataFrame(getStreamId(), data().copy());
} frame.setLast(isLast());
return frame;
@Override
public void setData(ByteBuf data) {
if (data == null) {
data = Unpooled.EMPTY_BUFFER;
}
if (data.readableBytes() > SpdyCodecUtil.SPDY_MAX_LENGTH) {
throw new IllegalArgumentException("data payload cannot exceed "
+ SpdyCodecUtil.SPDY_MAX_LENGTH + " bytes");
}
this.data = data;
} }
@Override @Override
@ -90,7 +99,11 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame {
buf.append(streamId); buf.append(streamId);
buf.append(StringUtil.NEWLINE); buf.append(StringUtil.NEWLINE);
buf.append("--> Size = "); buf.append("--> Size = ");
buf.append(data.readableBytes()); if (isFreed()) {
buf.append("(freed)");
} else {
buf.append(data().readableBytes());
}
return buf.toString(); return buf.toString();
} }
} }

View File

@ -16,12 +16,13 @@
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
/** /**
* A SPDY Protocol Data Frame * A SPDY Protocol Data Frame
*/ */
public interface SpdyDataFrame { public interface SpdyDataFrame extends ByteBufHolder {
/** /**
* Returns the Stream-ID of this frame. * Returns the Stream-ID of this frame.
@ -47,13 +48,12 @@ public interface SpdyDataFrame {
/** /**
* Returns the data payload of this frame. If there is no data payload * Returns the data payload of this frame. If there is no data payload
* {@link Unpooled#EMPTY_BUFFER} is returned. * {@link Unpooled#EMPTY_BUFFER} is returned.
*/ *
ByteBuf getData();
/**
* Sets the data payload of this frame. If {@code null} is specified,
* the data payload will be set to {@link Unpooled#EMPTY_BUFFER}.
* The data payload cannot exceed 16777215 bytes. * The data payload cannot exceed 16777215 bytes.
*/ */
void setData(ByteBuf data); @Override
ByteBuf data();
@Override
SpdyDataFrame copy();
} }

View File

@ -281,8 +281,9 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return null; return null;
} }
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); ByteBuf data = ctx.alloc().buffer(dataLength);
spdyDataFrame.setData(buffer.readBytes(dataLength)); data.writeBytes(buffer, dataLength);
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID, data);
length -= dataLength; length -= dataLength;
if (length == 0) { if (length == 0) {

View File

@ -82,7 +82,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<Object> {
if (msg instanceof SpdyDataFrame) { if (msg instanceof SpdyDataFrame) {
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
ByteBuf data = spdyDataFrame.getData(); ByteBuf data = spdyDataFrame.data();
byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0; byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
out.ensureWritableBytes(SPDY_HEADER_SIZE + data.readableBytes()); out.ensureWritableBytes(SPDY_HEADER_SIZE + data.readableBytes());
out.writeInt(spdyDataFrame.getStreamId() & 0x7FFFFFFF); out.writeInt(spdyDataFrame.getStreamId() & 0x7FFFFFFF);

View File

@ -199,13 +199,13 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<Object> {
} }
ByteBuf content = fullHttpMessage.data(); ByteBuf content = fullHttpMessage.data();
if (content.readableBytes() > maxContentLength - spdyDataFrame.getData().readableBytes()) { if (content.readableBytes() > maxContentLength - spdyDataFrame.data().readableBytes()) {
messageMap.remove(streamID); messageMap.remove(streamID);
throw new TooLongFrameException( throw new TooLongFrameException(
"HTTP content length exceeded " + maxContentLength + " bytes."); "HTTP content length exceeded " + maxContentLength + " bytes.");
} }
ByteBuf spdyDataFrameData = spdyDataFrame.getData(); ByteBuf spdyDataFrameData = spdyDataFrame.data();
int spdyDataFrameDataLen = spdyDataFrameData.readableBytes(); int spdyDataFrameDataLen = spdyDataFrameData.readableBytes();
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen); content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);

View File

@ -164,10 +164,8 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
if (msg instanceof HttpContent) { if (msg instanceof HttpContent) {
HttpContent chunk = (HttpContent) msg; HttpContent chunk = (HttpContent) msg;
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.data());
spdyDataFrame.setData(chunk.data());
spdyDataFrame.setLast(chunk instanceof LastHttpContent); spdyDataFrame.setLast(chunk instanceof LastHttpContent);
if (chunk instanceof LastHttpContent) { if (chunk instanceof LastHttpContent) {
LastHttpContent trailer = (LastHttpContent) chunk; LastHttpContent trailer = (LastHttpContent) chunk;
List<Map.Entry<String, String>> trailers = trailer.trailingHeaders().entries(); List<Map.Entry<String, String>> trailers = trailer.trailingHeaders().entries();
@ -288,4 +286,13 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object> {
return spdySynReplyFrame; return spdySynReplyFrame;
} }
@Override
protected void freeOutboundMessage(Object msg) throws Exception {
if (msg instanceof HttpContent) {
// Will be freed later as the content of them is just reused here
return;
}
super.freeOutboundMessage(msg);
}
} }

View File

@ -61,4 +61,13 @@ public class SpdyHttpResponseStreamIdHandler extends
return msg; return msg;
} }
@Override
protected void freeInboundMessage(Object msg) throws Exception {
// just pass through so no free
}
@Override
protected void freeOutboundMessage(HttpMessage msg) throws Exception {
// just pass through so no free
}
} }

View File

@ -190,7 +190,7 @@ public class SpdySessionHandler
if (flowControl) { if (flowControl) {
// Update receive window size // Update receive window size
int deltaWindowSize = -1 * spdyDataFrame.getData().readableBytes(); int deltaWindowSize = -1 * spdyDataFrame.data().readableBytes();
int newWindowSize = spdySession.updateReceiveWindowSize(streamID, deltaWindowSize); int newWindowSize = spdySession.updateReceiveWindowSize(streamID, deltaWindowSize);
// Window size can become negative if we sent a SETTINGS frame that reduces the // Window size can become negative if we sent a SETTINGS frame that reduces the
@ -206,9 +206,9 @@ public class SpdySessionHandler
// Window size became negative due to sender writing frame before receiving SETTINGS // Window size became negative due to sender writing frame before receiving SETTINGS
// Send data frames upstream in initialReceiveWindowSize chunks // Send data frames upstream in initialReceiveWindowSize chunks
if (newWindowSize < 0) { if (newWindowSize < 0) {
while (spdyDataFrame.getData().readableBytes() > initialReceiveWindowSize) { while (spdyDataFrame.data().readableBytes() > initialReceiveWindowSize) {
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID); SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID,
partialDataFrame.setData(spdyDataFrame.getData().readSlice(initialReceiveWindowSize)); spdyDataFrame.data().readSlice(initialReceiveWindowSize));
ctx.nextOutboundMessageBuffer().add(partialDataFrame); ctx.nextOutboundMessageBuffer().add(partialDataFrame);
ctx.flush(); ctx.flush();
} }
@ -496,7 +496,7 @@ public class SpdySessionHandler
if (flowControl) { if (flowControl) {
synchronized (flowControlLock) { synchronized (flowControlLock) {
int dataLength = spdyDataFrame.getData().readableBytes(); int dataLength = spdyDataFrame.data().readableBytes();
int sendWindowSize = spdySession.getSendWindowSize(streamID); int sendWindowSize = spdySession.getSendWindowSize(streamID);
if (sendWindowSize >= dataLength) { if (sendWindowSize >= dataLength) {
@ -524,8 +524,8 @@ public class SpdySessionHandler
spdySession.updateSendWindowSize(streamID, -1 * sendWindowSize); spdySession.updateSendWindowSize(streamID, -1 * sendWindowSize);
// Create a partial data frame whose length is the current window size // Create a partial data frame whose length is the current window size
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID); SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID,
partialDataFrame.setData(spdyDataFrame.getData().readSlice(sendWindowSize)); spdyDataFrame.data().readSlice(sendWindowSize));
// Enqueue the remaining data (will be the first frame queued) // Enqueue the remaining data (will be the first frame queued)
spdySession.putPendingWrite(streamID, spdyDataFrame); spdySession.putPendingWrite(streamID, spdyDataFrame);
@ -814,7 +814,7 @@ public class SpdySessionHandler
break; break;
} }
int dataFrameSize = spdyDataFrame.getData().readableBytes(); int dataFrameSize = spdyDataFrame.data().readableBytes();
if (newWindowSize >= dataFrameSize) { if (newWindowSize >= dataFrameSize) {
// Window size is large enough to send entire data frame // Window size is large enough to send entire data frame
@ -848,8 +848,8 @@ public class SpdySessionHandler
spdySession.updateSendWindowSize(streamID, -1 * newWindowSize); spdySession.updateSendWindowSize(streamID, -1 * newWindowSize);
// Create a partial data frame whose length is the current window size // Create a partial data frame whose length is the current window size
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID); SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID,
partialDataFrame.setData(spdyDataFrame.getData().readSlice(newWindowSize)); spdyDataFrame.data().readSlice(newWindowSize));
// The transfer window size is pre-decremented when sending a data frame downstream. // The transfer window size is pre-decremented when sending a data frame downstream.
// Close the stream on write failures that leaves the transfer window in a corrupt state. // Close the stream on write failures that leaves the transfer window in a corrupt state.

View File

@ -150,7 +150,7 @@ public class WebSocketServerProtocolHandlerTest {
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { public void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
content = "processed: " + msg.getText(); content = "processed: " + msg.text();
} }
public String getContent() { public String getContent() {

View File

@ -145,7 +145,6 @@ public class HttpSnoopServerHandler extends ChannelInboundMessageHandlerAdapter<
private void writeResponse(ChannelHandlerContext ctx, HttpObject currentObj) { private void writeResponse(ChannelHandlerContext ctx, HttpObject currentObj) {
// Decide whether to close the connection or not. // Decide whether to close the connection or not.
boolean keepAlive = isKeepAlive(request); boolean keepAlive = isKeepAlive(request);
// Build the response object. // Build the response object.
FullHttpResponse response = new DefaultFullHttpResponse( FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST, HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,

View File

@ -93,17 +93,17 @@ public class AutobahnServerHandler extends ChannelInboundMessageHandlerAdapter<O
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame); handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
ctx.channel().write( ctx.channel().write(
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new PongWebSocketFrame(frame.isFinalFragment(), frame.rsv(), frame.data()));
} else if (frame instanceof TextWebSocketFrame) { } else if (frame instanceof TextWebSocketFrame) {
// String text = ((TextWebSocketFrame) frame).getText(); // String text = ((TextWebSocketFrame) frame).getText();
ctx.channel().write( ctx.channel().write(
new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new TextWebSocketFrame(frame.isFinalFragment(), frame.rsv(), frame.data()));
} else if (frame instanceof BinaryWebSocketFrame) { } else if (frame instanceof BinaryWebSocketFrame) {
ctx.channel().write( ctx.channel().write(
new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new BinaryWebSocketFrame(frame.isFinalFragment(), frame.rsv(), frame.data()));
} else if (frame instanceof ContinuationWebSocketFrame) { } else if (frame instanceof ContinuationWebSocketFrame) {
ctx.channel().write( ctx.channel().write(
new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.rsv(), frame.data()));
} else if (frame instanceof PongWebSocketFrame) { } else if (frame instanceof PongWebSocketFrame) {
// Ignore // Ignore
} else { } else {
@ -136,4 +136,14 @@ public class AutobahnServerHandler extends ChannelInboundMessageHandlerAdapter<O
private static String getWebSocketLocation(FullHttpRequest req) { private static String getWebSocketLocation(FullHttpRequest req) {
return "ws://" + req.headers().get(HttpHeaders.Names.HOST); return "ws://" + req.headers().get(HttpHeaders.Names.HOST);
} }
@Override
protected void freeInboundMessage(Object msg) throws Exception {
if (!(msg instanceof PongWebSocketFrame) && msg instanceof WebSocketFrame) {
// will be freed once written by the encoder
return;
}
super.freeInboundMessage(msg);
}
} }

View File

@ -97,7 +97,7 @@ public class WebSocketClientHandler extends ChannelInboundMessageHandlerAdapter<
WebSocketFrame frame = (WebSocketFrame) msg; WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) { if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println("WebSocket Client received message: " + textFrame.getText()); System.out.println("WebSocket Client received message: " + textFrame.text());
} else if (frame instanceof PongWebSocketFrame) { } else if (frame instanceof PongWebSocketFrame) {
System.out.println("WebSocket Client received pong"); System.out.println("WebSocket Client received pong");
} else if (frame instanceof CloseWebSocketFrame) { } else if (frame instanceof CloseWebSocketFrame) {

View File

@ -23,7 +23,7 @@ public class CustomTextFrameHandler extends ChannelInboundMessageHandlerAdapter<
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception { public void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception {
String request = frame.getText(); String request = frame.text();
ctx.channel().write(new TextWebSocketFrame(request.toUpperCase())); ctx.channel().write(new TextWebSocketFrame(request.toUpperCase()));
} }

View File

@ -109,7 +109,7 @@ public class WebSocketServerHandler extends ChannelInboundMessageHandlerAdapter<
return; return;
} }
if (frame instanceof PingWebSocketFrame) { if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.channel().write(new PongWebSocketFrame(frame.data()));
return; return;
} }
if (!(frame instanceof TextWebSocketFrame)) { if (!(frame instanceof TextWebSocketFrame)) {
@ -118,7 +118,7 @@ public class WebSocketServerHandler extends ChannelInboundMessageHandlerAdapter<
} }
// Send the uppercase string back. // Send the uppercase string back.
String request = ((TextWebSocketFrame) frame).getText(); String request = ((TextWebSocketFrame) frame).text();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s received %s", ctx.channel().id(), request)); logger.debug(String.format("Channel %s received %s", ctx.channel().id(), request));
} }
@ -146,6 +146,15 @@ public class WebSocketServerHandler extends ChannelInboundMessageHandlerAdapter<
ctx.close(); ctx.close();
} }
@Override
protected void freeInboundMessage(Object msg) throws Exception {
if (msg instanceof PingWebSocketFrame || msg instanceof CloseWebSocketFrame) {
// Will be freed once wrote back
return;
}
super.freeInboundMessage(msg);
}
private static String getWebSocketLocation(FullHttpRequest req) { private static String getWebSocketLocation(FullHttpRequest req) {
return "ws://" + req.headers().get(HOST) + WEBSOCKET_PATH; return "ws://" + req.headers().get(HOST) + WEBSOCKET_PATH;
} }

View File

@ -111,7 +111,7 @@ public class WebSocketSslServerHandler extends ChannelInboundMessageHandlerAdapt
return; return;
} }
if (frame instanceof PingWebSocketFrame) { if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.channel().write(new PongWebSocketFrame(frame.data()));
return; return;
} }
if (!(frame instanceof TextWebSocketFrame)) { if (!(frame instanceof TextWebSocketFrame)) {
@ -120,7 +120,7 @@ public class WebSocketSslServerHandler extends ChannelInboundMessageHandlerAdapt
} }
// Send the uppercase string back. // Send the uppercase string back.
String request = ((TextWebSocketFrame) frame).getText(); String request = ((TextWebSocketFrame) frame).text();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s received %s", ctx.channel().id(), request)); logger.debug(String.format("Channel %s received %s", ctx.channel().id(), request));
} }
@ -151,4 +151,13 @@ public class WebSocketSslServerHandler extends ChannelInboundMessageHandlerAdapt
private static String getWebSocketLocation(FullHttpRequest req) { private static String getWebSocketLocation(FullHttpRequest req) {
return "wss://" + req.headers().get(HOST) + WEBSOCKET_PATH; return "wss://" + req.headers().get(HOST) + WEBSOCKET_PATH;
} }
@Override
protected void freeInboundMessage(Object msg) throws Exception {
if (msg instanceof PingWebSocketFrame || msg instanceof CloseWebSocketFrame) {
// Will be freed once wrote back
return;
}
super.freeInboundMessage(msg);
}
} }