+ * Encodes a web socket frame into wire protocol version 8 format. This code was + * forked from webbit and + * modified. + *
+ * + * @author Aslak Hellesøy + * @author Vibul Imtarnasan + */ +public class WebSocket08FrameEncoder extends OneToOneEncoder { + + private static final Logger logger = LoggerFactory.getLogger(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; + + private boolean maskPayload = false; + + /** + * 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; + } + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + + byte[] mask = null; + + if (msg instanceof WebSocketFrame) { + WebSocketFrame frame = (WebSocketFrame) msg; + ChannelBuffer data = frame.getBinaryData(); + if (data == null) { + data = ChannelBuffers.EMPTY_BUFFER; + } + + byte opcode; + if (frame instanceof TextWebSocketFrame) { + opcode = OPCODE_TEXT; + } else if (frame instanceof PingWebSocketFrame) { + opcode = OPCODE_PING; + } else if (frame instanceof PongWebSocketFrame) { + opcode = OPCODE_PONG; + } else if (frame instanceof CloseWebSocketFrame) { + opcode = OPCODE_CLOSE; + } else if (frame instanceof BinaryWebSocketFrame) { + opcode = OPCODE_BINARY; + } else if (frame instanceof ContinuationWebSocketFrame) { + opcode = OPCODE_CONT; + } else { + throw new UnsupportedOperationException("Cannot encode frame of type: " + frame.getClass().getName()); + } + + int length = data.readableBytes(); + + logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length); + + int b0 = 0; + if (frame.isFinalFragment()) { + b0 |= (1 << 7); + } + b0 |= (frame.getRsv() % 8) << 4; + b0 |= opcode % 128; + + ChannelBuffer header; + ChannelBuffer body; + + if (opcode == OPCODE_PING && length > 125) { + throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length); + } + + int maskLength = this.maskPayload ? 4 : 0; + if (length <= 125) { + header = ChannelBuffers.buffer(2 + maskLength); + header.writeByte(b0); + byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length); + header.writeByte(b); + } else if (length <= 0xFFFF) { + header = ChannelBuffers.buffer(4 + maskLength); + header.writeByte(b0); + header.writeByte(this.maskPayload ? (0xFE) : 126); + header.writeByte((length >>> 8) & 0xFF); + header.writeByte((length) & 0xFF); + } else { + header = ChannelBuffers.buffer(10 + maskLength); + header.writeByte(b0); + header.writeByte(this.maskPayload ? (0xFF) : 127); + header.writeLong(length); + } + + // Write payload + if (this.maskPayload) { + Integer random = (int) (Math.random() * Integer.MAX_VALUE); + mask = ByteBuffer.allocate(4).putInt(random).array(); + header.writeBytes(mask); + + body = ChannelBuffers.buffer(length); + int counter = 0; + while (data.readableBytes() > 0) { + byte byteData = data.readByte(); + body.writeByte(byteData ^ mask[+counter++ % 4]); + } + } else { + body = data; + } + return ChannelBuffers.wrappedBuffer(header, body); + } + + // If not websocket, then just return the message + return msg; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ning/http/client/providers/netty/netty4/WebSocketFrame.java b/src/main/java/com/ning/http/client/providers/netty/netty4/WebSocketFrame.java new file mode 100644 index 0000000000..a6c3f90f49 --- /dev/null +++ b/src/main/java/com/ning/http/client/providers/netty/netty4/WebSocketFrame.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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. + */ +package com.ning.http.client.providers.netty.netty4; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * Base class for web socket frames + * + * @author The Netty Project + */ +public abstract class WebSocketFrame { + + /** + * Flag to indicate if this frame is the final fragment in a message. The + * first fragment (frame) may also be the final fragment. + */ + private boolean finalFragment = true; + + /** + * RSV1, RSV2, RSV3 used for extensions + */ + private int rsv = 0; + + /** + * Contents of this frame + */ + private ChannelBuffer binaryData; + + /** + * Returns binary data + */ + public ChannelBuffer getBinaryData() { + return binaryData; + } + + /** + * Sets the binary data for this frame + */ + public void setBinaryData(ChannelBuffer binaryData) { + this.binaryData = binaryData; + } + + /** + * Flag to indicate if this frame is the final fragment in a message. The + * first fragment (frame) may also be the final fragment. + */ + public boolean isFinalFragment() { + return finalFragment; + } + + public void setFinalFragment(boolean finalFragment) { + this.finalFragment = finalFragment; + } + + /** + * Bits used for extensions to the standard. + */ + public int getRsv() { + return rsv; + } + + public void setRsv(int rsv) { + this.rsv = rsv; + } + +} diff --git a/src/main/java/com/ning/http/client/providers/netty/netty4/WebSocketFrameType.java b/src/main/java/com/ning/http/client/providers/netty/netty4/WebSocketFrameType.java new file mode 100644 index 0000000000..449497aec6 --- /dev/null +++ b/src/main/java/com/ning/http/client/providers/netty/netty4/WebSocketFrameType.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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. + */ +package com.ning.http.client.providers.netty.netty4; + +/** + * Type of web socket frames + * + * @author The Netty Project + */ +public enum WebSocketFrameType { + TEXT, BINARY, PING, PONG, CLOSE, CONTINUATION +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocket.java b/src/main/java/com/ning/http/client/websocket/WebSocket.java new file mode 100644 index 0000000000..ee5ad42a29 --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocket.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +/** + * A Websocket client + */ +public interface WebSocket { + + /** + * Sen a byte message. + * @param message a byte message + * @return this + */ + WebSocket sendMessage(byte[] message); + + /** + * Send a text message + * @param message a text message + * @return this. + */ + WebSocket sendTextMessage(String message); + + /** + * Add a {@link WebSocketListener} + * @param l a {@link WebSocketListener} + * @return this + */ + WebSocket addMessageListener(WebSocketListener l); + + /** + * Close the WebSocket. + */ + void close(); +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocketByteListener.java b/src/main/java/com/ning/http/client/websocket/WebSocketByteListener.java new file mode 100644 index 0000000000..cafee7b0ea --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocketByteListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +/** + * A {@link WebSocketListener} for bytes + */ +public interface WebSocketByteListener extends WebSocketListener { + + /** + * Invoked when bytes are available. + * @param message a byte array. + */ + void onMessage(byte[] message); + +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocketListener.java b/src/main/java/com/ning/http/client/websocket/WebSocketListener.java new file mode 100644 index 0000000000..6149aeffca --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocketListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +/** + * A generic {@link WebSocketListener} for WebSocket events. Use the appropriate listener for receiving message bytes. + */ +public interface WebSocketListener { + + /** + * Invoked when the {@link WebSocket} is open. + * @param websocket + */ + void onOpen(WebSocket websocket); + + /** + * Invoked when the {@link WebSocket} is close. + * @param websocket + */ + void onClose(WebSocket websocket); + + /** + * Invoked when the {@link WebSocket} is open. + * @param t a {@link Throwable} + */ + void onError(Throwable t); + +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocketPingListener.java b/src/main/java/com/ning/http/client/websocket/WebSocketPingListener.java new file mode 100644 index 0000000000..adc3f82833 --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocketPingListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +/** + * A WebSocket's Ping Listener + */ +public interface WebSocketPingListener extends WebSocketListener { + + /** + * Invoked when a ping message is received + * @param message a byte array + */ + void onPing(byte[] message); + +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocketPongListener.java b/src/main/java/com/ning/http/client/websocket/WebSocketPongListener.java new file mode 100644 index 0000000000..6f398ce0ff --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocketPongListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +/** + * A WebSocket's Pong Listener + */ +public interface WebSocketPongListener extends WebSocketListener { + + /** + * Invoked when a pong message is received + * @param message a byte array + */ + void onPong(byte[] message); + +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocketTextListener.java b/src/main/java/com/ning/http/client/websocket/WebSocketTextListener.java new file mode 100644 index 0000000000..544c0027cd --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocketTextListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +/** + * A {@link WebSocketListener} for text message + */ +public interface WebSocketTextListener extends WebSocketListener { + + /** + * Invoked when WebSocket text message are received. + * @param message a {@link String} message + */ + void onMessage(String message); + +} diff --git a/src/main/java/com/ning/http/client/websocket/WebSocketUpgradeHandler.java b/src/main/java/com/ning/http/client/websocket/WebSocketUpgradeHandler.java new file mode 100644 index 0000000000..b5a0e4ef64 --- /dev/null +++ b/src/main/java/com/ning/http/client/websocket/WebSocketUpgradeHandler.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.ning.http.client.websocket; + +import com.ning.http.client.AsyncHandler; +import com.ning.http.client.HttpResponseBodyPart; +import com.ning.http.client.HttpResponseHeaders; +import com.ning.http.client.HttpResponseStatus; +import com.ning.http.client.UpgradeHandler; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An {@link AsyncHandler} which is able to execute WebSocket upgrade. Use the Builder for configuring WebSocket options. + */ +public class WebSocketUpgradeHandler implements UpgradeHandler