summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/socket
diff options
context:
space:
mode:
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/socket')
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java75
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java150
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java51
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java111
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java212
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java85
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java117
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java64
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java142
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java97
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java91
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java75
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java127
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java107
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java118
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java179
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java214
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java162
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java229
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java80
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java235
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java255
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java99
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java111
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java105
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java114
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java137
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java159
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java59
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java109
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java44
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java149
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java101
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java97
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java139
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java93
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java111
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java220
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java93
39 files changed, 4916 insertions, 0 deletions
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java
new file mode 100644
index 0000000..bea3c63
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java
@@ -0,0 +1,75 @@
+/*
+ * BinaryData.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>BinaryData</code> object represents a binary payload for
+ * a WebScoket frame. This can be used to send any type of data. If
+ * however it is used to send text data then it is decoded as UTF-8.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.DataFrame
+ */
+public class BinaryData implements Data {
+
+ /**
+ * This is used to convert the binary payload to text.
+ */
+ private final DataConverter converter;
+
+ /**
+ * This is the byte array that represents the binary payload.
+ */
+ private final byte[] data;
+
+ /**
+ * Constructor for the <code>BinaryData</code> object. It requires
+ * an array of binary data that will be send within a frame.
+ *
+ * @param data the byte array representing the frame payload
+ */
+ public BinaryData(byte[] data) {
+ this.converter = new DataConverter();
+ this.data = data;
+ }
+
+ /**
+ * This returns the binary payload that is to be sent with a frame.
+ * It contains no headers or other meta data. If the original data
+ * was text this converts it to UTF-8.
+ *
+ * @return the binary payload to be sent with the frame
+ */
+ public byte[] getBinary() {
+ return data;
+ }
+
+ /**
+ * This returns the text payload that is to be sent with a frame.
+ * It contains no header information or meta data. Caution should
+ * be used with this method as binary payloads will encode to
+ * garbage when decoded as UTF-8.
+ *
+ * @return the text payload to be sent with the frame
+ */
+ public String getText() {
+ return converter.convert(data);
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java
new file mode 100644
index 0000000..c64c605
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java
@@ -0,0 +1,150 @@
+/*
+ * CloseCode.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>CloseCode</code> enumerates the closure codes specified in
+ * RFC 6455. When closing an established connection an endpoint may
+ * indicate a reason for closure. The interpretation of this reason by
+ * an endpoint, and the action an endpoint should take given this reason,
+ * are left undefined by RFC 6455. The specification defines a set of
+ * status codes and specifies which ranges may be used by extensions,
+ * frameworks, and end applications. The status code and any associated
+ * textual message are optional components of a Close frame.
+ *
+ * @author niall.gallagher
+ */
+public enum CloseCode {
+
+ /**
+ * Indicates the purpose for the connection has been fulfilled.
+ */
+ NORMAL_CLOSURE(1000),
+
+ /**
+ * Indicates that the server is going down or the client browsed away.
+ */
+ GOING_AWAY(1001),
+
+ /**
+ * Indicates the connection is terminating due to a protocol error.
+ */
+ PROTOCOL_ERROR(1002),
+
+ /**
+ * Indicates the connection received a data type it cannot accept.
+ */
+ UNSUPPORTED_DATA(1003),
+
+ /**
+ * According to RFC 6455 this has been reserved for future use.
+ */
+ RESERVED(1004),
+
+ /**
+ * Indicates that no status code was present and should not be used.
+ */
+ NO_STATUS_CODE(1005),
+
+ /**
+ * Indicates an abnormal closure and should not be used.
+ */
+ ABNORMAL_CLOSURE(1006),
+
+ /**
+ * Indicates that a payload was not consistent with the message type.
+ */
+ INVALID_FRAME_DATA(1007),
+
+ /**
+ * Indicates an endpoint received a message that violates its policy.
+ */
+ POLICY_VIOLATION(1008),
+
+ /**
+ * Indicates that a payload is too big to be processed.
+ */
+ TOO_BIG(1009),
+
+ /**
+ * Indicates that the server did not negotiate an extension properly.
+ */
+ NO_EXTENSION(1010),
+
+ /**
+ * Indicates an unexpected error within the server.
+ */
+ INTERNAL_SERVER_ERROR(1011),
+
+ /**
+ * Indicates a validation failure for TLS and should not be used.
+ */
+ TLS_HANDSHAKE_FAILURE(1015);
+
+ /**
+ * This is the actual integer value representing the code.
+ */
+ public final int code;
+
+ /**
+ * This is the high order byte for the closure code.
+ */
+ public final int high;
+
+ /**
+ * This is the low order byte for the closure code.
+ */
+ public final int low;
+
+ /**
+ * Constructor for the <code>CloseCode</code> object. This is used
+ * to create a closure code using one of the pre-defined values
+ * within RFC 6455.
+ *
+ * @param code this is the code that is to be used
+ */
+ private CloseCode(int code) {
+ this.high = code & 0x0f;
+ this.low = code & 0xf0;
+ this.code = code;
+ }
+
+ /**
+ * This is the data that represents the closure code. The array
+ * contains the high order byte and the low order byte as taken
+ * from the pre-defined closure code.
+ *
+ * @return a byte array representing the closure code
+ */
+ public byte[] getData() {
+ return new byte[] { (byte)high, (byte)low };
+ }
+
+
+ public static CloseCode resolveCode(int high, int low) {
+ for(CloseCode code : values()) {
+ if(code.high == high) {
+ if(code.low == low) {
+ return code;
+ }
+ }
+ }
+ return NO_STATUS_CODE;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java
new file mode 100644
index 0000000..bb79830
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java
@@ -0,0 +1,51 @@
+/*
+ * Data.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>Data</code> interface represents a payload for a WebScoket
+ * frame. It can hold either binary data or text data. For performance
+ * binary frames are a better choice as all text frames need to be
+ * encoded as UTF-8 from the native UCS2 format.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.DataFrame
+ */
+public interface Data {
+
+ /**
+ * This returns the binary payload that is to be sent with a frame.
+ * It contains no headers or other meta data. If the original data
+ * was text this converts it to UTF-8.
+ *
+ * @return the binary payload to be sent with the frame
+ */
+ byte[] getBinary();
+
+ /**
+ * This returns the text payload that is to be sent with a frame.
+ * It contains no header information or meta data. Caution should
+ * be used with this method as binary payloads will encode to
+ * garbage when decoded as UTF-8.
+ *
+ * @return the text payload to be sent with the frame
+ */
+ String getText();
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java
new file mode 100644
index 0000000..5713fd6
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java
@@ -0,0 +1,111 @@
+/*
+ * DataConverter.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>DataConverter</code> object is used to convert binary data
+ * to text data and vice versa. According to RFC 6455 a particular text
+ * frame might include a partial UTF-8 sequence; however, the whole
+ * message MUST contain valid UTF-8.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.DataFrame
+ */
+public class DataConverter {
+
+ /**
+ * This is the character encoding used to convert the text data.
+ */
+ private final String charset;
+
+ /**
+ * Constructor for the <code>DataConverter</code> object. By default
+ * this uses UTF-8 character encoding to convert text data as this
+ * is what is required for RFC 6455 section 5.6.
+ */
+ public DataConverter() {
+ this("UTF-8");
+ }
+
+ /**
+ * Constructor for the <code>DataConverter</code> object. This can be
+ * used to specific a character encoding other than UTF-8. However it
+ * is not recommended as RFC 6455 section 5.6 suggests the frame must
+ * contain valid UTF-8 data.
+ *
+ * @param charset the character encoding to be used
+ */
+ public DataConverter(String charset) {
+ this.charset = charset;
+ }
+
+ /**
+ * This method is used to convert text using the character encoding
+ * specified when constructing the converter. Typically this will use
+ * UTF-8 as required by RFC 6455.
+ *
+ * @param text this is the string to convert to a byte array
+ *
+ * @return a byte array decoded using the specified encoding
+ */
+ public byte[] convert(String text) {
+ try {
+ return text.getBytes(charset);
+ } catch(Exception e) {
+ throw new IllegalStateException("Could not encode text as " + charset, e);
+ }
+ }
+
+ /**
+ * This method is used to convert data using the character encoding
+ * specified when constructing the converter. Typically this will use
+ * UTF-8 as required by RFC 6455.
+ *
+ * @param text this is the byte array to convert to a string
+ *
+ * @return a string encoded using the specified encoding
+ */
+ public String convert(byte[] binary) {
+ try {
+ return new String(binary, charset);
+ } catch(Exception e) {
+ throw new IllegalStateException("Could not decode data as " + charset, e);
+ }
+ }
+
+ /**
+ * This method is used to convert data using the character encoding
+ * specified when constructing the converter. Typically this will use
+ * UTF-8 as required by RFC 6455.
+ *
+ * @param text this is the byte array to convert to a string
+ * @param offset the is the offset to read the bytes from
+ * @param size this is the number of bytes to be used
+ *
+ * @return a string encoded using the specified encoding
+ */
+ public String convert(byte[] binary, int offset, int size) {
+ try {
+ return new String(binary, offset, size, charset);
+ } catch(Exception e) {
+ throw new IllegalStateException("Could not decode data as " + charset, e);
+ }
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java
new file mode 100644
index 0000000..b51cd2b
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java
@@ -0,0 +1,212 @@
+/*
+ * DataFrame.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>DataFrame</code> object represents a frame as defined in
+ * RFC 6455. A frame is a very lightweight envelope used to send
+ * control information and either text or binary user data. Typically
+ * a frame will represent a single message however, it is possible
+ * to fragment a single frame up in to several frames. A fragmented
+ * frame has a specific <code>FrameType</code> indicating that it
+ * is a continuation frame.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.Data
+ */
+public class DataFrame implements Frame {
+
+ /**
+ * This is the type used to determine the intent of the frame.
+ */
+ private final FrameType type;
+
+ /**
+ * This contains the payload to be sent with the frame.
+ */
+ private final Data data;
+
+ /**
+ * This determines if the frame is the last of a sequence.
+ */
+ private final boolean last;
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. A
+ * zero payload is created using this constructor and is suitable
+ * only for specific control frames such as connection termination.
+ *
+ * @param type this is the frame type used for this instance
+ */
+ public DataFrame(FrameType type) {
+ this(type, new byte[0]);
+ }
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. In
+ * some cases a control frame may require a zero length payload.
+ *
+ * @param type this is the frame type used for this instance
+ * @param data this is the payload for this frame
+ */
+ public DataFrame(FrameType type, byte[] data) {
+ this(type, data, true);
+ }
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. In
+ * some cases a control frame may require a zero length payload.
+ *
+ * @param type this is the frame type used for this instance
+ * @param data this is the payload for this frame
+ * @param last true if this is not a fragment in a sequence
+ */
+ public DataFrame(FrameType type, byte[] data, boolean last) {
+ this(type, new BinaryData(data), last);
+ }
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. In
+ * some cases a control frame may require a zero length payload.
+ *
+ * @param type this is the frame type used for this instance
+ * @param data this is the payload for this frame
+ */
+ public DataFrame(FrameType type, String text) {
+ this(type, text, true);
+ }
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. In
+ * some cases a control frame may require a zero length payload.
+ *
+ * @param type this is the frame type used for this instance
+ * @param data this is the payload for this frame
+ * @param last true if this is not a fragment in a sequence
+ */
+ public DataFrame(FrameType type, String text, boolean last) {
+ this(type, new TextData(text), last);
+ }
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. In
+ * some cases a control frame may require a zero length payload.
+ *
+ * @param type this is the frame type used for this instance
+ * @param data this is the payload for this frame
+ */
+ public DataFrame(FrameType type, Data data) {
+ this(type, data, true);
+ }
+
+ /**
+ * Constructor for the <code>DataFrame</code> object. This is used
+ * to create a frame using the specified data and frame type. In
+ * some cases a control frame may require a zero length payload.
+ *
+ * @param type this is the frame type used for this instance
+ * @param data this is the payload for this frame
+ * @param last true if this is not a fragment in a sequence
+ */
+ public DataFrame(FrameType type, Data data, boolean last) {
+ this.data = data;
+ this.type = type;
+ this.last = last;
+ }
+
+ /**
+ * This is used to determine if the frame is the final frame in
+ * a sequence of fragments or a whole frame. If this returns false
+ * then the frame is a continuation from from a sequence of
+ * fragments, otherwise it is a whole frame or the last fragment.
+ *
+ * @return this returns false if the frame is a fragment
+ */
+ public boolean isFinal() {
+ return last;
+ }
+
+ /**
+ * This returns the binary payload that is to be sent with the frame.
+ * It contains no headers or other meta data. If the original data
+ * was text this converts it to UTF-8.
+ *
+ * @return the binary payload to be sent with the frame
+ */
+ public byte[] getBinary() {
+ return data.getBinary();
+ }
+
+ /**
+ * This returns the text payload that is to be sent with the frame.
+ * It contains no header information or meta data. Caution should
+ * be used with this method as binary payloads will encode to
+ * garbage when decoded as UTF-8.
+ *
+ * @return the text payload to be sent with the frame
+ */
+ public String getText(){
+ return data.getText();
+ }
+
+ /**
+ * This method is used to convert from one frame type to another.
+ * Converting a frame type is useful in scenarios such as when a
+ * ping needs to respond to a pong or when it is more convenient
+ * to send a text frame as binary.
+ *
+ * @param type this is the frame type to convert to
+ *
+ * @return a new frame using the specified frame type
+ */
+ public Frame getFrame(FrameType type) {
+ return new DataFrame(type, data, last);
+ }
+
+ /**
+ * This is used to determine the type of frame. Interpretation of
+ * this type is outlined in RFC 6455 and can be loosely categorised
+ * as control frames and either data or binary frames.
+ *
+ * @return this returns the type of frame that this represents
+ */
+ public FrameType getType(){
+ return type;
+ }
+
+ /**
+ * This returns the text payload that is to be sent with the frame.
+ * It contains no header information or meta data. Caution should
+ * be used with this method as binary payloads will encode to
+ * garbage when decoded as UTF-8.
+ *
+ * @return the text payload to be sent with the frame
+ */
+ @Override
+ public String toString() {
+ return getText();
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java
new file mode 100644
index 0000000..7f5ad0f
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java
@@ -0,0 +1,85 @@
+/*
+ * Frame.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>Frame</code> interface represents a frame as defined in
+ * RFC 6455. A frame is a very lightweight envelope used to send
+ * control information and either text or binary user data. Typically
+ * a frame will represent a single message however, it is possible
+ * to fragment a single frame up in to several frames. A fragmented
+ * frame has a specific <code>FrameType</code> indicating that it
+ * is a continuation frame.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.DataFrame
+ */
+public interface Frame {
+
+ /**
+ * This is used to determine if the frame is the final frame in
+ * a sequence of fragments or a whole frame. If this returns false
+ * then the frame is a continuation from from a sequence of
+ * fragments, otherwise it is a whole frame or the last fragment.
+ *
+ * @return this returns false if the frame is a fragment
+ */
+ boolean isFinal();
+
+ /**
+ * This returns the binary payload that is to be sent with the frame.
+ * It contains no headers or other meta data. If the original data
+ * was text this converts it to UTF-8.
+ *
+ * @return the binary payload to be sent with the frame
+ */
+ byte[] getBinary();
+
+ /**
+ * This returns the text payload that is to be sent with the frame.
+ * It contains no header information or meta data. Caution should
+ * be used with this method as binary payloads will encode to
+ * garbage when decoded as UTF-8.
+ *
+ * @return the text payload to be sent with the frame
+ */
+ String getText();
+
+ /**
+ * This method is used to convert from one frame type to another.
+ * Converting a frame type is useful in scenarios such as when a
+ * ping needs to respond to a pong or when it is more convenient
+ * to send a text frame as binary.
+ *
+ * @param type this is the frame type to convert to
+ *
+ * @return a new frame using the specified frame type
+ */
+ Frame getFrame(FrameType type);
+
+ /**
+ * This is used to determine the type of frame. Interpretation of
+ * this type is outlined in RFC 6455 and can be loosely categorised
+ * as control frames and either data or binary frames.
+ *
+ * @return this returns the type of frame that this represents
+ */
+ FrameType getType();
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java
new file mode 100644
index 0000000..bcacc43
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java
@@ -0,0 +1,117 @@
+/*
+ * FrameChannel.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+import java.io.IOException;
+
+/**
+ * The <code>FrameChannel</code> represents a full duplex communication
+ * channel as defined by RFC 6455. Any instance of this will provide
+ * a means to perform asynchronous writes and reads to a remote client
+ * using a lightweight framing protocol. A frame is a finite length
+ * sequence of bytes that can hold either text or binary data. Also,
+ * control frames are used to perform heartbeat monitoring and closure.
+ * <p>
+ * For convenience frames can be consumed from the socket via a
+ * callback to a registered listener. This avoids having to poll each
+ * socket for data and provides a asynchronous event driven model of
+ * communication, which greatly reduces overhead and complication.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.FrameListener
+ * @see org.simpleframework.http.socket.Frame
+ */
+public interface FrameChannel {
+
+ /**
+ * This is used to send data to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param data this is the data that is to be sent
+ */
+ void send(byte[] data) throws IOException;
+
+ /**
+ * This is used to send text to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param text this is the text that is to be sent
+ */
+ void send(String text) throws IOException;
+
+ /**
+ * This is used to send data to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param frame this is the frame that is to be sent
+ */
+ void send(Frame frame) throws IOException;
+
+ /**
+ * This is used to register a <code>FrameListener</code> to this
+ * instance. The registered listener will receive all user frames
+ * and control frames sent from the client. Also, when the frame
+ * is closed or when an unexpected error occurs the listener is
+ * notified. Any number of listeners can be registered at any time.
+ *
+ * @param listener this is the listener that is to be registered
+ */
+ void register(FrameListener listener) throws IOException;
+
+ /**
+ * This is used to remove a <code>FrameListener</code> from this
+ * instance. After removal the listener will no longer receive
+ * any user frames or control messages from this specific instance.
+ *
+ * @param listener this is the listener to be removed
+ */
+ void remove(FrameListener listener) throws IOException;
+
+ /**
+ * This is used to close the connection with a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated.
+ *
+ * @param reason the reason for closing the connection
+ */
+ void close(Reason reason) throws IOException;
+
+ /**
+ * This is used to close the connection without a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated.
+ */
+ void close() throws IOException;
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java
new file mode 100644
index 0000000..6892e9c
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java
@@ -0,0 +1,64 @@
+/*
+ * FrameListener.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>FrameListener</code> is used to listen for incoming frames
+ * on a <code>WebSocket</code>. Any number of listeners can listen on
+ * a single web socket and it will receive all incoming events. For
+ * consistency this interface is modelled on the WebSocket API as
+ * defined by W3C Candidate Recommendation as of 20 September 2012.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.FrameChannel
+ */
+public interface FrameListener {
+
+ /**
+ * This is called when a new frame arrives on the WebSocket. It
+ * will receive control frames as well as binary and text user
+ * frames. Control frames should not be acted on or responded
+ * to as they are provided for informational purposes only.
+ *
+ * @param session this is the associated session
+ * @param frame this is the frame that has been received
+ */
+ void onFrame(Session session, Frame frame);
+
+ /**
+ * This is called when an error occurs on the WebSocket. After
+ * an error the connection it is closed with an opcode indicating
+ * an internal server error.
+ *
+ * @param session this is the associated session
+ * @param frame this is the exception that has been thrown
+ */
+ void onError(Session session, Exception cause);
+
+ /**
+ * This is called when the connection is closed from the other
+ * side. Typically a frame with an opcode of close is sent
+ * before the close callback is issued.
+ *
+ * @param session this is the associated session
+ * @param reason this is the reason the connection was closed
+ */
+ void onClose(Session session, Reason reason);
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java
new file mode 100644
index 0000000..8237701
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java
@@ -0,0 +1,142 @@
+/*
+ * FrameType.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>FrameType</code> represents the set of opcodes defined
+ * in RFC 6455. The base framing protocol uses a opcode to define the
+ * interpretation of the payload data for the frame.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.Frame
+ */
+public enum FrameType {
+
+ /**
+ * A continuation frame identifies a fragment from a larger message.
+ */
+ CONTINUATION(0x00),
+
+ /**
+ * A text frame identifies a message that contains UTF-8 text data.
+ */
+ TEXT(0x01),
+
+ /**
+ * A binary frame identifies a message that contains binary data.
+ */
+ BINARY(0x02),
+
+ /**
+ * A close frame identifies a frame used to terminate a connection.
+ */
+ CLOSE(0x08),
+
+ /**
+ * A ping frame is a heartbeat used to determine connection health.
+ */
+ PING(0x09),
+
+ /**
+ * A pong frame is sent is sent in response to a ping frame.
+ */
+ PONG(0x0a);
+
+ /**
+ * This is the integer value for the opcode.
+ */
+ public final int code;
+
+ /**
+ * Constructor for the <code>Frame</code> type enumeration. This is
+ * given the opcode that is used to identify a specific frame type.
+ *
+ * @param code this is the opcode representing the frame type
+ */
+ private FrameType(int code) {
+ this.code = code;
+ }
+
+ /**
+ * This is used to determine if a frame is a text frame. It can be
+ * useful to know if a frame is a user based frame as it reduces
+ * the need to convert from or to certain character sets.
+ *
+ * @return this returns true if the frame represents a text frame
+ */
+ public boolean isText() {
+ return this == TEXT;
+ }
+
+ /**
+ * This is used to determine if a frame is a close frame. A close
+ * frame contains an optional payload, which if present contains
+ * an error code in network byte order in the first two bytes,
+ * followed by an optional UTF-8 text reason of the closure.
+ *
+ * @return this returns true if the frame represents a close frame
+ */
+ public boolean isClose() {
+ return this == CLOSE;
+ }
+
+ /**
+ * This is used to determine if a frame is a pong frame. A pong
+ * frame is sent in response to a ping and is used to determine if
+ * a WebSocket connection is still active and healthy.
+ *
+ * @return this returns true if the frame represents a pong frame
+ */
+ public boolean isPong() {
+ return this == PONG;
+ }
+
+ /**
+ * This is used to determine if a frame is a ping frame. A ping
+ * frame is sent to check if a WebSocket connection is still healthy.
+ * A connection is determined healthy if it responds with a pong
+ * frame is a reasonable length of time.
+ *
+ * @return this returns true if the frame represents a ping frame
+ */
+ public boolean isPing() {
+ return this == PING;
+ }
+
+ /**
+ * This is used to acquire the frame type given an opcode. If no
+ * frame type can be determined from the opcode provided then this
+ * will return a null value.
+ *
+ * @param octet this is the octet representing the opcode
+ *
+ * @return this returns the frame type from the opcode
+ */
+ public static FrameType resolveType(int octet) {
+ int value = octet & 0xff;
+
+ for(FrameType code : values()) {
+ if(code.code == value) {
+ return code;
+ }
+ }
+ return null;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java
new file mode 100644
index 0000000..c7438e5
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java
@@ -0,0 +1,97 @@
+/*
+ * Reason.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>Reason</code> object is used to hold a textual reason
+ * for connection closure and an RFC 6455 defined code. When a
+ * connection is to be closed a control frame with an opcode of
+ * close is sent with the text reason, if one is provided.
+ *
+ * @author Niall Gallagher
+ */
+public class Reason {
+
+ /**
+ * This is the close code to be sent with a control frame.
+ */
+ private final CloseCode code;
+
+ /**
+ * This is the textual description of the close reason.
+ */
+ private final String text;
+
+ /**
+ * Constructor for the <code>Reason</code> object. This is used
+ * to create a reason and a textual description of that reason
+ * to be delivered as a control frame.
+ *
+ * @param code this is the code to be sent with the frame
+ */
+ public Reason(CloseCode code) {
+ this(code, null);
+ }
+
+ /**
+ * Constructor for the <code>Reason</code> object. This is used
+ * to create a reason and a textual description of that reason
+ * to be delivered as a control frame.
+ *
+ * @param code this is the code to be sent with the frame
+ * @param text this is textual description of the close reason
+ */
+ public Reason(CloseCode code, String text) {
+ this.code = code;
+ this.text = text;
+ }
+
+ /**
+ * This is used to get the RFC 6455 code describing the type
+ * of close event. It is the code that should be used by
+ * applications to determine why the connection was terminated.
+ *
+ * @return returns the close code for the connection
+ */
+ public CloseCode getCode() {
+ return code;
+ }
+
+ /**
+ * This is used to get the textual description for the closure.
+ * In many scenarios there will be no textual reason as it is
+ * an optional attribute.
+ *
+ * @return this returns the description for the closure
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * This is used to provide a textual representation of the reason.
+ * For consistency this will only return the enumerated value for
+ * the close code, or if none exists a "null" text string.
+ *
+ * @return this returns a string representation of the reason
+ */
+ public String toString() {
+ return String.valueOf(code);
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java
new file mode 100644
index 0000000..7c9a7db
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java
@@ -0,0 +1,91 @@
+/*
+ * Session.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+import java.util.Map;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+
+/**
+ * The <code>Session</code> object represents a simple WebSocket session
+ * that contains the connection handshake details and the actual socket.
+ * In order to determine how the session should be interacted with the
+ * protocol is conveniently exposed, however all attributes of the
+ * original HTTP request are available.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.FrameChannel
+ */
+public interface Session {
+
+ /**
+ * This can be used to retrieve the response attributes. These can
+ * be used to keep state with the response when it is passed to
+ * other systems for processing. Attributes act as a convenient
+ * model for storing objects associated with the response. This
+ * also inherits attributes associated with the client connection.
+ *
+ * @return the attributes of that have been set on the request
+ */
+ Map getAttributes();
+
+ /**
+ * This is used as a shortcut for acquiring attributes for the
+ * response. This avoids acquiring the attribute <code>Map</code>
+ * in order to retrieve the attribute directly from that object.
+ * The attributes contain data specific to the response.
+ *
+ * @param key this is the key of the attribute to acquire
+ *
+ * @return this returns the attribute for the specified name
+ */
+ Object getAttribute(Object key);
+
+ /**
+ * Provides a <code>FrameChannel</code> that can be used to communicate
+ * with the connected client. Communication is full duplex and also
+ * asynchronous through the use of a <code>FrameListener</code> that
+ * can be registered with the channel.
+ *
+ * @return a web socket for full duplex communication
+ */
+ FrameChannel getChannel();
+
+ /**
+ * Provides the <code>Request</code> used to initiate the session.
+ * This is useful in establishing the identity of the user, acquiring
+ * an security information and also for determining the request path
+ * that was used, which be used to establish context.
+ *
+ * @return the request used to initiate the session
+ */
+ Request getRequest();
+
+ /**
+ * Provides the <code>Response</code> used to establish the session
+ * with the remote client. This is useful in establishing the protocol
+ * used to create the session and also for determining various other
+ * useful contextual information.
+ *
+ * @return the response used to establish the session
+ */
+ Response getResponse();
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java
new file mode 100644
index 0000000..24ee97d
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java
@@ -0,0 +1,75 @@
+/*
+ * TextData.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket;
+
+/**
+ * The <code>TextData</code> object represents a text payload for
+ * a WebScoket frame. This can be used to send any type of data. If
+ * however it is used to send binary data then it is encoded as UTF-8.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.DataFrame
+ */
+public class TextData implements Data {
+
+ /**
+ * This is used to convert the text payload to a byte array.
+ */
+ private final DataConverter converter;
+
+ /**
+ * This is the text string representing a frame payload.
+ */
+ private final String data;
+
+ /**
+ * Constructor for the <code>TextData</code> object. It requires
+ * an text string that will be sent as UTF-8 within a frame.
+ *
+ * @param data the text string representing the frame payload
+ */
+ public TextData(String data) {
+ this.converter = new DataConverter();
+ this.data = data;
+ }
+
+ /**
+ * This returns the binary payload that is to be sent with a frame.
+ * It contains no headers or other meta data. If the original data
+ * was text this converts it to UTF-8.
+ *
+ * @return the binary payload to be sent with the frame
+ */
+ public byte[] getBinary() {
+ return converter.convert(data);
+ }
+
+ /**
+ * This returns the text payload that is to be sent with a frame.
+ * It contains no header information or meta data. Caution should
+ * be used with this method as binary payloads will encode to
+ * garbage when decoded as UTF-8.
+ *
+ * @return the text payload to be sent with the frame
+ */
+ public String getText() {
+ return data;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java
new file mode 100644
index 0000000..2fe2521
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java
@@ -0,0 +1,127 @@
+/*
+ * AcceptToken.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_KEY;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+
+import org.simpleframework.common.encode.Base64Encoder;
+import org.simpleframework.http.Request;
+
+/**
+ * The <code>AcceptToken</code> is used to create a unique token based
+ * on a random key sent by the client. This is used to prove that the
+ * handshake was received, the server has to take two pieces of
+ * information and combine them to form a response. The first piece
+ * of information comes from the <code>Sec-WebSocket-Key</code> header
+ * field in the client handshake, the second is the globally unique
+ * identifier <code>258EAFA5-E914-47DA-95CA-C5AB0DC85B11</code>. Both
+ * are concatenated and an SHA-1 has is generated and used in the
+ * session initiating response.
+ *
+ * @author Niall Gallagher
+ */
+class AcceptToken {
+
+ /**
+ * This is the globally unique identifier used in the handshake.
+ */
+ private static final byte[] MAGIC = {
+ '2', '5', '8', 'E', 'A', 'F', 'A', '5', '-',
+ 'E', '9', '1', '4', '-', '4', '7', 'D', 'A',
+ '-', '9', '5', 'C', 'A', '-', 'C', '5', 'A',
+ 'B', '0', 'D', 'C', '8', '5', 'B', '1', '1' };
+
+ /**
+ * This is used to generate the SHA-1 has from the user key.
+ */
+ private final MessageDigest digest;
+
+ /**
+ * This is the original request used to initiate the session.
+ */
+ private final Request request;
+
+ /**
+ * This is the character encoding to decode the key with.
+ */
+ private final String charset;
+
+ /**
+ * Constructor for the <code>AcceptToken</code> object. This is
+ * to create an object that can generate a token from the client
+ * key available from the <code>Sec-WebSocket-Key</code> header.
+ *
+ * @param request this is the session initiating request
+ */
+ public AcceptToken(Request request) throws Exception {
+ this(request, "SHA-1");
+ }
+
+ /**
+ * Constructor for the <code>AcceptToken</code> object. This is
+ * to create an object that can generate a token from the client
+ * key available from the <code>Sec-WebSocket-Key</code> header.
+ *
+ * @param request this is the session initiating request
+ * @param algorithm the algorithm used to create the token
+ */
+ public AcceptToken(Request request, String algorithm) throws Exception {
+ this(request, algorithm, "UTF-8");
+ }
+
+ /**
+ * Constructor for the <code>AcceptToken</code> object. This is
+ * to create an object that can generate a token from the client
+ * key available from the <code>Sec-WebSocket-Key</code> header.
+ *
+ * @param request this is the session initiating request
+ * @param algorithm the algorithm used to create the token
+ * @param charset the encoding used to decode the client key
+ */
+ public AcceptToken(Request request, String algorithm, String charset) throws Exception {
+ this.digest = MessageDigest.getInstance(algorithm);
+ this.request = request;
+ this.charset = charset;
+ }
+
+ /**
+ * This is used to create the required accept token for the session
+ * initiating response. The resulting token is a SHA-1 digest of
+ * the <code>Sec-WebSocket-Key</code> a globally unique identifier
+ * defined in RFC 6455 all encoded in base64.
+ *
+ * @return the accept token for the session initiating response
+ */
+ public String create() throws IOException {
+ String value = request.getValue(SEC_WEBSOCKET_KEY);
+ byte[] data = value.getBytes(charset);
+
+ if (data.length > 0) {
+ digest.update(data);
+ digest.update(MAGIC);
+ }
+ byte[] digested = digest.digest();
+ char[] text = Base64Encoder.encode(digested);
+
+ return new String(text);
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java
new file mode 100644
index 0000000..0c09063
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java
@@ -0,0 +1,107 @@
+/*
+ * DirectRouter.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_PROTOCOL;
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION;
+import static org.simpleframework.http.Protocol.UPGRADE;
+import static org.simpleframework.http.Protocol.WEBSOCKET;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+
+/**
+ * The <code>DirectRouter</code> object is used to create a router
+ * that uses a single service. Typically this is used by simpler
+ * servers that wish to expose a single sub-protocol to clients.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.RouterContainer
+ */
+public class DirectRouter implements Router {
+
+ /**
+ * The service used by this router instance.
+ */
+ private final Service service;
+
+ /**
+ * The protocol used or null if none was specified.
+ */
+ private final String protocol;
+
+ /**
+ * Constructor for the <code>DirectRouter</code> object. This
+ * is used to create an object that will select a single service.
+ * Creating an instance with this constructor means that the
+ * protocol header will not be set.
+ *
+ * @param service this is the service used by this instance
+ * @param protocol the protocol used by this router or null
+ */
+ public DirectRouter(Service service) {
+ this(service, null);
+ }
+
+ /**
+ * Constructor for the <code>DirectRouter</code> object. This
+ * is used to create an object that will select a single service.
+ * If the protocol specified is null then the response to the
+ * session initiation will contain null for the protocol header.
+ *
+ * @param service this is the service used by this instance
+ * @param protocol the protocol used by this router or null
+ */
+ public DirectRouter(Service service, String protocol) {
+ this.protocol = protocol;
+ this.service = service;
+ }
+
+ /**
+ * This is used to route an incoming request to a service if
+ * the request represents a WebSocket handshake as defined by
+ * RFC 6455. If the request is not a session initiating handshake
+ * then this will return a null value to allow it to be processed
+ * by some other part of the server.
+ *
+ * @param request this is the request to use for routing
+ * @param response this is the response to establish the session
+ *
+ * @return a service that can be used to process the session
+ */
+ public Service route(Request request, Response response) {
+ String token = request.getValue(UPGRADE);
+
+ if(token != null) {
+ if(token.equalsIgnoreCase(WEBSOCKET)) {
+ String version = request.getValue(SEC_WEBSOCKET_VERSION);
+
+ if(version != null) {
+ response.setValue(SEC_WEBSOCKET_VERSION, version);
+ }
+ if(protocol != null) {
+ response.setValue(SEC_WEBSOCKET_PROTOCOL, protocol);
+ }
+ return service;
+ }
+ }
+ return null;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java
new file mode 100644
index 0000000..6ab224a
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java
@@ -0,0 +1,118 @@
+/*
+ * FrameBuilder.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.util.Arrays;
+
+import org.simpleframework.http.socket.DataConverter;
+import org.simpleframework.http.socket.DataFrame;
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameType;
+
+/**
+ * The <code>FrameBuilder</code> object is used to create an object
+ * that interprets a frame header to produce frame objects. For
+ * efficiency this converts binary data to the native frame data
+ * type, which avoids memory churn.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.FrameConsumer
+ */
+class FrameBuilder {
+
+ /**
+ * This converts binary data to a UTF-8 string for text frames.
+ */
+ private final DataConverter converter;
+
+ /**
+ * This is used to determine the type of frames to create.
+ */
+ private final FrameHeader header;
+
+ /**
+ * Constructor for the <code>FrameBuilder</code> object. This acts
+ * as a factory for frame objects by using the provided header to
+ * determine the frame type to be created.
+ *
+ * @param header the header used to determine the frame type
+ */
+ public FrameBuilder(FrameHeader header) {
+ this.converter = new DataConverter();
+ this.header = header;
+ }
+
+ /**
+ * This is used to create a frame object to represent the data that
+ * has been consumed. The frame created will contain either a copy of
+ * the provided byte buffer or a text string encoded in UTF-8. To
+ * avoid memory churn this method should be used sparingly.
+ *
+ * @return this returns a frame created from the consumed bytes
+ */
+ public Frame create(byte[] data, int count) {
+ FrameType type = header.getType();
+
+ if(type.isText()) {
+ return createText(data, count);
+ }
+ return createBinary(data, count);
+ }
+
+ /**
+ * This is used to create a frame object from the provided data.
+ * The resulting frame will contain a UTF-8 encoding of the data
+ * to ensure that data conversion needs to be performed only once.
+ *
+ * @param data this is the data to convert to a new frame
+ * @param count this is the number of bytes in the frame
+ *
+ * @return a new frame containing the text
+ */
+ private Frame createText(byte[] data, int count) {
+ FrameType type = header.getType();
+ String text = converter.convert(data, 0, count);
+
+ if(header.isFinal()) {
+ return new DataFrame(type, text, true);
+ }
+ return new DataFrame(type, text, false);
+ }
+
+ /**
+ * This is used to create a frame object from the provided data.
+ * The resulting frame will contain a copy of the data to ensure
+ * that the frame is immutable.
+ *
+ * @param data this is the data to convert to a new frame
+ * @param count this is the number of bytes in the frame
+ *
+ * @return a new frame containing a copy of the provided data
+ */
+ private Frame createBinary(byte[] data, int count) {
+ FrameType type = header.getType();
+ byte[] copy = Arrays.copyOf(data, count);
+
+ if(header.isFinal()) {
+ return new DataFrame(type, copy, true);
+ }
+ return new DataFrame(type, copy, false);
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java
new file mode 100644
index 0000000..8987620
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java
@@ -0,0 +1,179 @@
+/*
+ * FrameCollector.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.service.ServiceEvent.ERROR;
+
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.socket.FrameListener;
+import org.simpleframework.http.socket.Session;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.ByteCursor;
+import org.simpleframework.transport.reactor.Operation;
+import org.simpleframework.transport.reactor.Reactor;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>FrameCollector</code> operation is used to collect frames
+ * from a channel and dispatch them to a <code>FrameListener</code>.
+ * To ensure that stale connections do not linger any connection that
+ * does not send a control ping or pong frame within two minutes will
+ * be terminated and the close control frame will be sent.
+ *
+ * @author Niall Gallagher
+ */
+class FrameCollector implements Operation {
+
+ /**
+ * This decodes the frame bytes from the channel and processes it.
+ */
+ private final FrameProcessor processor;
+
+ /**
+ * This is the cursor used to maintain a stream seek position.
+ */
+ private final ByteCursor cursor;
+
+ /**
+ * This is the underlying channel for this frame collector.
+ */
+ private final Channel channel;
+
+ /**
+ * This is the reactor used to schedule this operation for reads.
+ */
+ private final Reactor reactor;
+
+ /**
+ * This is the tracer that is used to trace the frame collection.
+ */
+ private final Trace trace;
+
+ /**
+ * Constructor for the <code>FrameCollector</code> object. This is
+ * used to create a collector that will process and dispatch web
+ * socket frames as defined by RFC 6455.
+ *
+ * @param encoder this is the encoder used to send messages
+ * @param session this is the web socket session
+ * @param channel this is the underlying TCP communication channel
+ * @param reactor this is the reactor used for read notifications
+ */
+ public FrameCollector(FrameEncoder encoder, Session session, Request request, Reactor reactor) {
+ this.processor = new FrameProcessor(encoder, session, request);
+ this.channel = request.getChannel();
+ this.cursor = channel.getCursor();
+ this.trace = channel.getTrace();
+ this.reactor = reactor;
+ }
+
+ /**
+ * This is used to acquire the trace object that is associated
+ * with the operation. A trace object is used to collection details
+ * on what operations are being performed. For instance it may
+ * contain information relating to I/O events or errors.
+ *
+ * @return this returns the trace associated with this operation
+ */
+ public Trace getTrace() {
+ return trace;
+ }
+
+ /**
+ * This is the channel associated with this collector. This is used
+ * to register for notification of read events. If at any time the
+ * remote endpoint is closed then this will cause the collector
+ * to perform a final execution before closing.
+ *
+ * @return this returns the selectable TCP channel
+ */
+ public SelectableChannel getChannel() {
+ return channel.getSocket();
+ }
+
+ /**
+ * This is used to register a <code>FrameListener</code> to this
+ * instance. The registered listener will receive all user frames
+ * and control frames sent from the client. Also, when the frame
+ * is closed or when an unexpected error occurs the listener is
+ * notified. Any number of listeners can be registered at any time.
+ *
+ * @param listener this is the listener that is to be registered
+ */
+ public void register(FrameListener listener) {
+ processor.register(listener);
+ }
+
+ /**
+ * This is used to remove a <code>FrameListener</code> from this
+ * instance. After removal the listener will no longer receive
+ * any user frames or control messages from this specific instance.
+ *
+ * @param listener this is the listener to be removed
+ */
+ public void remove(FrameListener listener) {
+ processor.remove(listener);
+ }
+
+ /**
+ * This is used to execute the collection operation. Collection is
+ * done by reading the frame header from the incoming data, once
+ * consumed the remainder of the frame is collected until such
+ * time as it has been fully consumed. When consumed it will be
+ * dispatched to the registered frame listeners.
+ */
+ public void run() {
+ try {
+ processor.process();
+
+ if(cursor.isOpen()) {
+ reactor.process(this, SelectionKey.OP_READ);
+ } else {
+ processor.close();
+ }
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+
+ try {
+ processor.failure(cause);
+ } catch(Exception fatal) {
+ trace.trace(ERROR, fatal);
+ } finally {
+ channel.close();
+ }
+ }
+ }
+
+ /**
+ * This is called when a read operation has timed out. To ensure
+ * that stale channels do not remain registered they are cleared
+ * out with this method and a close frame is sent if possible.
+ */
+ public void cancel() {
+ try{
+ processor.close();
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ channel.close();
+ }
+ }
+} \ No newline at end of file
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java
new file mode 100644
index 0000000..b904130
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java
@@ -0,0 +1,214 @@
+/*
+ * FrameConnection.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.CloseCode.NORMAL_CLOSURE;
+import static org.simpleframework.http.socket.service.ServiceEvent.OPEN_SOCKET;
+
+import java.io.IOException;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameListener;
+import org.simpleframework.http.socket.Reason;
+import org.simpleframework.http.socket.Session;
+import org.simpleframework.http.socket.FrameChannel;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.ByteWriter;
+import org.simpleframework.transport.reactor.Reactor;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>FrameConnection</code> represents a connection that can
+ * send and receivd WebSocket frames. Any instance of this will provide
+ * a means to perform asynchronous writes and reads to a remote client
+ * using a lightweight framing protocol. A frame is a finite length
+ * sequence of bytes that can hold either text or binary data. Also,
+ * control frames are used to perform heartbeat monitoring and closure.
+ * <p>
+ * For convenience frames can be consumed from the socket via a
+ * callback to a registered listener. This avoids having to poll each
+ * socket for data and provides a asynchronous event driven model of
+ * communication, which greatly reduces overhead and complication.
+ *
+ * @author Niall Gallagher
+ */
+class FrameConnection implements FrameChannel {
+
+ /**
+ * The collector is used to collect frames from the TCP channel.
+ */
+ private final FrameCollector operation;
+
+ /**
+ * This encoder is used to encode data as RFC 6455 frames.
+ */
+ private final FrameEncoder encoder;
+
+ /**
+ * This is the sender used to send frames over the channel.
+ */
+ private final ByteWriter writer;
+
+ /**
+ * This is the session object that has a synchronized channel.
+ */
+ private final Session session;
+
+ /**
+ * This is the underlying TCP channel that frames are sent over.
+ */
+ private final Channel channel;
+
+ /**
+ * The reason that is sent if at any time the channel is closed.
+ */
+ private final Reason reason;
+
+ /**
+ * This is used to trace all events that occur on the channel.
+ */
+ private final Trace trace;
+
+ /**
+ * Constructor for the <code>FrameConnection</code> object. This is used
+ * to create a channel that can read and write frames over a TCP
+ * channel. For asynchronous read and dispatch operations this will
+ * produce an operation to collect and process RFC 6455 frames.
+ *
+ * @param request this is the initiating request for the WebSocket
+ * @param response this is the initiating response for the WebSocket
+ * @param reactor this is the reactor used to process frames
+ */
+ public FrameConnection(Request request, Response response, Reactor reactor) {
+ this.encoder = new FrameEncoder(request);
+ this.session = new ServiceSession(this, request, response);
+ this.operation = new FrameCollector(encoder, session, request, reactor);
+ this.reason = new Reason(NORMAL_CLOSURE);
+ this.channel = request.getChannel();
+ this.writer = channel.getWriter();
+ this.trace = channel.getTrace();
+ }
+
+ /**
+ * This is used to open the channel and begin consuming frames. This
+ * will also return the session that contains the details for the
+ * created WebSocket such as the initiating request and response as
+ * well as the <code>FrameChannel</code> object.
+ *
+ * @return the session associated with the WebSocket
+ */
+ public Session open() throws IOException {
+ trace.trace(OPEN_SOCKET);
+ operation.run();
+ return session;
+ }
+
+ /**
+ * This is used to register a <code>FrameListener</code> to this
+ * instance. The registered listener will receive all user frames
+ * and control frames sent from the client. Also, when the frame
+ * is closed or when an unexpected error occurs the listener is
+ * notified. Any number of listeners can be registered at any time.
+ *
+ * @param listener this is the listener that is to be registered
+ */
+ public void register(FrameListener listener) throws IOException {
+ operation.register(listener);
+ }
+
+ /**
+ * This is used to remove a <code>FrameListener</code> from this
+ * instance. After removal the listener will no longer receive
+ * any user frames or control messages from this specific instance.
+ *
+ * @param listener this is the listener to be removed
+ */
+ public void remove(FrameListener listener) throws IOException {
+ operation.remove(listener);
+ }
+
+ /**
+ * This is used to send data to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param data this is the data that is to be sent
+ */
+ public void send(byte[] data) throws IOException {
+ encoder.encode(data);
+ }
+
+ /**
+ * This is used to send text to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param text this is the text that is to be sent
+ */
+ public void send(String text) throws IOException {
+ encoder.encode(text);
+ }
+
+ /**
+ * This is used to send data to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param frame this is the frame that is to be sent
+ */
+ public void send(Frame frame) throws IOException {
+ encoder.encode(frame);
+ }
+
+ /**
+ * This is used to close the connection with a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated.
+ *
+ * @param reason the reason for closing the connection
+ */
+ public void close(Reason reason) throws IOException {
+ encoder.encode(reason);
+ writer.close();
+ }
+
+ /**
+ * This is used to close the connection without a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated.
+ */
+ public void close() throws IOException {
+ encoder.encode(reason);
+ writer.close();
+ }
+} \ No newline at end of file
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java
new file mode 100644
index 0000000..579d6ef
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java
@@ -0,0 +1,162 @@
+/*
+ * FrameConsumer.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.io.IOException;
+
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameType;
+import org.simpleframework.transport.ByteCursor;
+
+/**
+ * The <code>FrameConsumer</code> object is used to read a WebSocket
+ * frame as defined by RFC 6455. This is a state machine that can read
+ * the data one byte at a time until the entire frame has been consumed.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.FrameCollector
+ */
+class FrameConsumer {
+
+ /**
+ * This is used to consume the header part of the frame.
+ */
+ private FrameHeaderConsumer header;
+
+ /**
+ * This is used to interpret the header and create a frame.
+ */
+ private FrameBuilder builder;
+
+ /**
+ * This is used to buffer the bytes that form the frame.
+ */
+ private byte[] buffer;
+
+ /**
+ * This is a count of the payload bytes currently consumed.
+ */
+ private int count;
+
+ /**
+ * Constructor for the <code>FrameConsumer</code> object. This is
+ * used to create a consumer to read the bytes that form the frame
+ * from an underlying TCP connection. Internally a buffer is created
+ * to allow bytes to be consumed and collected in chunks.
+ */
+ public FrameConsumer() {
+ this.header = new FrameHeaderConsumer();
+ this.builder = new FrameBuilder(header);
+ this.buffer = new byte[2048];
+ }
+
+ /**
+ * This is used to determine the type of frame. Interpretation of
+ * this type is outlined in RFC 6455 and can be loosely categorised
+ * as control frames and either data or binary frames.
+ *
+ * @return this returns the type of frame that this represents
+ */
+ public FrameType getType() {
+ return header.getType();
+ }
+
+ /**
+ * This is used to create a frame object to represent the data that
+ * has been consumed. The frame created will make a copy of the
+ * internal byte buffer so this method should be used sparingly.
+ *
+ * @return this returns a frame created from the consumed bytes
+ */
+ public Frame getFrame() {
+ return builder.create(buffer, count);
+ }
+
+ /**
+ * This consumes frame bytes using the provided cursor. The consumer
+ * acts as a state machine by consuming the data as that data
+ * becomes available, this allows it to consume data asynchronously
+ * and dispatch once the whole frame has been consumed.
+ *
+ * @param cursor the cursor to consume the frame data from
+ */
+ public void consume(ByteCursor cursor) throws IOException {
+ while (cursor.isReady()) {
+ if(!header.isFinished()) {
+ header.consume(cursor);
+ }
+ if(header.isFinished()) {
+ int length = header.getLength();
+
+ if(count <= length) {
+ if(buffer.length < length) {
+ buffer = new byte[length];
+ }
+ if(count < length) {
+ int size = cursor.read(buffer, count, length - count);
+
+ if(size == -1) {
+ throw new IOException("Could only read " + count + " of length " + length);
+ }
+ count += size;
+ }
+ if(count == length) {
+ if(header.isMasked()) {
+ byte[] mask = header.getMask();
+
+ for (int i = 0; i < count; i++) {
+ buffer[i] ^= mask[i % 4];
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This is used to determine if the collector has finished. If it
+ * is not finished the collector will be registered to listen for
+ * an I/O interrupt to read further bytes of the frame.
+ *
+ * @return true if the collector has finished consuming
+ */
+ public boolean isFinished() {
+ if(header.isFinished()) {
+ int length = header.getLength();
+
+ if(count == length) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This resets the collector to its original state so that it can
+ * be reused. Reusing the collector has obvious benefits as it will
+ * reduce the amount of memory churn for the server.
+ */
+ public void clear() {
+ header.clear();
+ count = 0;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java
new file mode 100644
index 0000000..1a99d30
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java
@@ -0,0 +1,229 @@
+/*
+ * FrameEncoder.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.FrameType.BINARY;
+import static org.simpleframework.http.socket.FrameType.CLOSE;
+import static org.simpleframework.http.socket.FrameType.TEXT;
+import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_FRAME;
+
+import java.io.IOException;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.socket.CloseCode;
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameType;
+import org.simpleframework.http.socket.Reason;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>FrameEncoder</code> is used to encode data as frames as
+ * defined by RFC 6455. This can encode binary, and text frames as
+ * well as control frames. All frames generated are written to the
+ * underlying channel but are not flushed so that multiple frames
+ * can be buffered before the final flush is made.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.FrameConnection
+ */
+class FrameEncoder {
+
+ /**
+ * This is the underlying sender used to send the frames.
+ */
+ private final OutputBarrier barrier;
+
+ /**
+ * This is the TCP channel the frames are delivered over.
+ */
+ private final Channel channel;
+
+ /**
+ * This is used to trace the traffic on the channel.
+ */
+ private final Trace trace;
+
+ /**
+ * This is the charset used to encode the text frames with.
+ */
+ private final String charset;
+
+ /**
+ * Constructor for the <code>FrameEncoder</code> object. This is
+ * used to create an encoder to sending frames over the provided
+ * channel. Frames send remain unflushed so they can be batched
+ * on a single output buffer.
+ *
+ * @param request contains the opening handshake information
+ */
+ public FrameEncoder(Request request) {
+ this(request, "UTF-8");
+ }
+
+ /**
+ * Constructor for the <code>FrameEncoder</code> object. This is
+ * used to create an encoder to sending frames over the provided
+ * channel. Frames send remain unflushed so they can be batched
+ * on a single output buffer.
+ *
+ * @param request contains the opening handshake information
+ * @param charset this is the character encoding to encode with
+ */
+ public FrameEncoder(Request request, String charset) {
+ this.barrier = new OutputBarrier(request, 5000);
+ this.channel = request.getChannel();
+ this.trace = channel.getTrace();
+ this.charset = charset;
+ }
+
+ /**
+ * This is used to encode the provided data as a WebSocket frame as
+ * of RFC 6455. The encoded data is written to the underlying socket
+ * and the number of bytes generated is returned.
+ *
+ * @param text this is the data used to encode the frame
+ *
+ * @return the size of the generated frame including the header
+ */
+ public int encode(String text) throws IOException {
+ byte[] data = text.getBytes(charset);
+ return encode(TEXT, data, true);
+ }
+
+ /**
+ * This is used to encode the provided data as a WebSocket frame as
+ * of RFC 6455. The encoded data is written to the underlying socket
+ * and the number of bytes generated is returned.
+ *
+ * @param data this is the data used to encode the frame
+ *
+ * @return the size of the generated frame including the header
+ */
+ public int encode(byte[] data) throws IOException {
+ return encode(BINARY, data, true);
+ }
+
+ /**
+ * This is used to encode the provided data as a WebSocket frame as
+ * of RFC 6455. The encoded data is written to the underlying socket
+ * and the number of bytes generated is returned. A close frame with
+ * a reason is similar to a text frame with the exception that the
+ * first two bytes of the frame payload contains the close code as
+ * a two byte integer in network byte order. The body of the close
+ * frame may contain UTF-8 encoded data with a reason, the
+ * interpretation of which is not defined by RFC 6455.
+ *
+ * @param reason this is the data used to encode the frame
+ *
+ * @return the size of the generated frame including the header
+ */
+ public int encode(Reason reason) throws IOException {
+ CloseCode code = reason.getCode();
+ String text = reason.getText();
+ byte[] header = code.getData();
+
+ if(text != null) {
+ byte[] data = text.getBytes(charset);
+ byte[] message = new byte[data.length + 2];
+
+ message[0] = header[0];
+ message[1] = header[1];
+
+ for(int i = 0; i < data.length; i++) {
+ message[i + 2] = data[i];
+ }
+ return encode(CLOSE, message, true);
+ }
+ return encode(CLOSE, header, true);
+ }
+
+ /**
+ * This is used to encode the provided frame as a WebSocket frame as
+ * of RFC 6455. The encoded data is written to the underlying socket
+ * and the number of bytes generated is returned.
+ *
+ * @param frame this is frame that is to be send over the channel
+ *
+ * @return the size of the generated frame including the header
+ */
+ public int encode(Frame frame) throws IOException {
+ FrameType code = frame.getType();
+ byte[] data = frame.getBinary();
+ boolean last = frame.isFinal();
+
+ return encode(code, data, last);
+ }
+
+ /**
+ * This is used to encode the provided frame as a WebSocket frame as
+ * of RFC 6455. The encoded data is written to the underlying socket
+ * and the number of bytes generated is returned.
+ *
+ * @param type this is the type of frame that is to be encoded
+ * @param data this is the data used to create the frame
+ * @param last determines if the is the last frame in a sequence
+ *
+ * @return the size of the generated frame including the header
+ */
+ private int encode(FrameType type, byte[] data, boolean last) throws IOException {
+ byte[] header = new byte[10];
+ long length = data.length;
+ int count = 0;
+
+ if (last) {
+ header[0] |= 1 << 7;
+ }
+ header[0] |= type.code % 128;
+
+ if (length <= 125) {
+ header[1] = (byte) length;
+ count = 2;
+ } else if (length >= 126 && length <= 65535) {
+ header[1] = (byte) 126;
+ header[2] = (byte) ((length >>> 8) & 0xff);
+ header[3] = (byte) (length & 0xff);
+ count = 4;
+ } else {
+ header[1] = (byte) 127;
+ header[2] = (byte) ((length >>> 56) & 0xff);
+ header[3] = (byte) ((length >>> 48) & 0xff);
+ header[4] = (byte) ((length >>> 40) & 0xff);
+ header[5] = (byte) ((length >>> 32) & 0xff);
+ header[6] = (byte) ((length >>> 24) & 0xff);
+ header[7] = (byte) ((length >>> 16) & 0xff);
+ header[8] = (byte) ((length >>> 8) & 0xff);
+ header[9] = (byte) (length & 0xff);
+ count = 10;
+ }
+ byte[] reply = new byte[count + data.length];
+
+ for (int i = 0; i < count; i++) {
+ reply[i] = header[i];
+ }
+ for (int i = 0; i < length; i++) {
+ reply[i + count] = data[i];
+ }
+ trace.trace(WRITE_FRAME, type);
+ barrier.send(reply);
+
+ return reply.length;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java
new file mode 100644
index 0000000..a246451
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java
@@ -0,0 +1,80 @@
+/*
+ * FrameHeader.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import org.simpleframework.http.socket.FrameType;
+
+/**
+ * The <code>FrameHeader</code> represents the variable length header
+ * used for a WebSocket frame. It is used to determine the number of
+ * bytes that need to be consumed to successfully process a frame
+ * from the connected client.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.FrameConsumer
+ */
+interface FrameHeader {
+
+ /**
+ * This is used to determine the type of frame. Interpretation of
+ * this type is outlined in RFC 6455 and can be loosely categorised
+ * as control frames and either data or binary frames.
+ *
+ * @return this returns the type of frame that this represents
+ */
+ FrameType getType();
+
+ /**
+ * This provides the client mask send with the request. The mask is
+ * a 32 bit value that is used as an XOR bitmask of the client
+ * payload. Masking applies only in the client to server direction.
+ *
+ * @return this returns the 32 bit mask used for this frame
+ */
+ byte[] getMask();
+
+ /**
+ * This provides the length of the payload within the frame. It
+ * is used to determine how much data to consume from the underlying
+ * TCP stream in order to recreate the frame to dispatch.
+ *
+ * @return the number of bytes used in the frame
+ */
+ int getLength();
+
+ /**
+ * This is used to determine if the frame is masked. All client
+ * frames should be masked according to RFC 6455. If masked the
+ * payload will have its contents bitmasked with a 32 bit value.
+ *
+ * @return this returns true if the payload has been masked
+ */
+ boolean isMasked();
+
+ /**
+ * This is used to determine if the frame is the final frame in
+ * a sequence of fragments or a whole frame. If this returns false
+ * then the frame is a continuation from from a sequence of
+ * fragments, otherwise it is a whole frame or the last fragment.
+ *
+ * @return this returns false if the frame is a fragment
+ */
+ boolean isFinal();
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java
new file mode 100644
index 0000000..d651ea9
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java
@@ -0,0 +1,235 @@
+/*
+ * FrameHeaderConsumer.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.io.IOException;
+
+import org.simpleframework.http.socket.FrameType;
+import org.simpleframework.transport.ByteCursor;
+
+/**
+ * The <code>FrameHeaderConsumer</code> is used to consume frames from
+ * a connected TCP channel. This is a state machine that can consume
+ * the data one byte at a time until the entire header has been consumed.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.FrameConsumer
+ */
+class FrameHeaderConsumer implements FrameHeader {
+
+ /**
+ * This is the frame type which represents the opcode.
+ */
+ private FrameType type;
+
+ /**
+ * If header consumed was from a client frame the data is masked.
+ */
+ private boolean masked;
+
+ /**
+ * Determines if this frame is part of a larger sequence.
+ */
+ private boolean last;
+
+ /**
+ * This is the mask that is used to obfuscate client frames.
+ */
+ private byte[] mask;
+
+ /**
+ * This is the octet that is used to read one byte at a time.
+ */
+ private byte[] octet;
+
+ /**
+ * Required number of bytes within the frame header.
+ */
+ private int required;
+
+ /**
+ * This represents the length of the frame payload.
+ */
+ private int length;
+
+ /**
+ * This determines the count of the mask bytes read.
+ */
+ private int count;
+
+ /**
+ * Constructor for the <code>FrameHeaderConsumer</code> object. This
+ * is used to create a consumer to read the bytes that form the
+ * frame header from an underlying TCP connection.
+ */
+ public FrameHeaderConsumer() {
+ this.octet = new byte[1];
+ this.mask = new byte[4];
+ this.length = -1;
+ }
+
+ /**
+ * This provides the length of the payload within the frame. It
+ * is used to determine how much data to consume from the underlying
+ * TCP stream in order to recreate the frame to dispatch.
+ *
+ * @return the number of bytes used in the frame
+ */
+ public int getLength() {
+ return length;
+ }
+
+ /**
+ * This provides the client mask send with the request. The mask is
+ * a 32 bit value that is used as an XOR bitmask of the client
+ * payload. Masking applies only in the client to server direction.
+ *
+ * @return this returns the 32 bit mask used for this frame
+ */
+ public byte[] getMask() {
+ return mask;
+ }
+
+ /**
+ * This is used to determine the type of frame. Interpretation of
+ * this type is outlined in RFC 6455 and can be loosely categorised
+ * as control frames and either data or binary frames.
+ *
+ * @return this returns the type of frame that this represents
+ */
+ public FrameType getType() {
+ return type;
+ }
+
+ /**
+ * This is used to determine if the frame is masked. All client
+ * frames should be masked according to RFC 6455. If masked the
+ * payload will have its contents bitmasked with a 32 bit value.
+ *
+ * @return this returns true if the payload has been masked
+ */
+ public boolean isMasked() {
+ return masked;
+ }
+
+ /**
+ * This is used to determine if the frame is the final frame in
+ * a sequence of fragments or a whole frame. If this returns false
+ * then the frame is a continuation from from a sequence of
+ * fragments, otherwise it is a whole frame or the last fragment.
+ *
+ * @return this returns false if the frame is a fragment
+ */
+ public boolean isFinal() {
+ return last;
+ }
+
+ /**
+ * This consumes frame bytes using the provided cursor. The consumer
+ * acts as a state machine by consuming the data as that data
+ * becomes available, this allows it to consume data asynchronously
+ * and dispatch once the whole frame has been consumed.
+ *
+ * @param cursor the cursor to consume the frame data from
+ */
+ public void consume(ByteCursor cursor) throws IOException {
+ if (cursor.isReady()) {
+ if (type == null) {
+ int count = cursor.read(octet);
+
+ if (count <= 0) {
+ throw new IOException("Ready cursor produced no data");
+ }
+ type = FrameType.resolveType(octet[0] & 0x0f);
+
+ if(type == null) {
+ throw new IOException("Frame type code not supported");
+ }
+ last = (octet[0] & 0x80) != 0;
+ } else {
+ if (length < 0) {
+ int count = cursor.read(octet);
+
+ if (count <= 0) {
+ throw new IOException("Ready cursor produced no data");
+ }
+ masked = (octet[0] & 0x80) != 0;
+ length = (octet[0] & 0x7F);
+
+ if (length == 0x7F) { // 8 byte extended payload length
+ required = 8;
+ length = 0;
+ } else if (length == 0x7E) { // 2 bytes extended payload length
+ required = 2;
+ length = 0;
+ }
+ } else if (required > 0) {
+ int count = cursor.read(octet);
+
+ if (count == -1) {
+ throw new IOException("Could not read length");
+ }
+ length |= (octet[0] & 0xFF) << (8 * --required);
+ } else {
+ if (masked && count < mask.length) {
+ int size = cursor.read(mask, count, mask.length - count);
+
+ if (size == -1) {
+ throw new IOException("Could not read mask");
+ }
+ count += size;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This is used to determine if the collector has finished. If it
+ * is not finished the collector will be registered to listen for
+ * an I/O intrrupt to read further bytes of the frame.
+ *
+ * @return true if the collector has finished consuming
+ */
+ public boolean isFinished() {
+ if(type != null) {
+ if(length >= 0 && required == 0) {
+ if(masked) {
+ return count == mask.length;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This resets the collector to its original state so that it can
+ * be reused. Reusing the collector has obvious benefits as it will
+ * reduce the amount of memory churn for the server.
+ */
+ public void clear() {
+ type = null;
+ length = -1;
+ required = 0;
+ masked = false;
+ count = 0;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java
new file mode 100644
index 0000000..c7528d4
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java
@@ -0,0 +1,255 @@
+/*
+ * FrameProcessor.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.CloseCode.NORMAL_CLOSURE;
+import static org.simpleframework.http.socket.service.ServiceEvent.ERROR;
+import static org.simpleframework.http.socket.service.ServiceEvent.READ_FRAME;
+import static org.simpleframework.http.socket.service.ServiceEvent.READ_PING;
+import static org.simpleframework.http.socket.service.ServiceEvent.READ_PONG;
+import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_PONG;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameListener;
+import org.simpleframework.http.socket.FrameType;
+import org.simpleframework.http.socket.Reason;
+import org.simpleframework.http.socket.Session;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.ByteCursor;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>FrameProcessor</code> object is used to process incoming
+ * data and dispatch that data as WebSocket frames. Dispatching of the
+ * frames is done by making a callback to <code>FrameListener</code>
+ * objects registered. In addition to frames this will also notify of
+ * any errors that occur or on connection closure.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.FrameConsumer
+ */
+class FrameProcessor {
+
+ /**
+ * This is the set of listeners to dispatch frames to.
+ */
+ private final Set<FrameListener> listeners;
+
+ /**
+ * This is used to extract the reason description from a frame.
+ */
+ private final ReasonExtractor extractor;
+
+ /**
+ * This is used to consume the frames from the underling channel.
+ */
+ private final FrameConsumer consumer;
+
+ /**
+ * This is the encoder that is used to send control messages.
+ */
+ private final FrameEncoder encoder;
+
+ /**
+ * This is used to determine if a close notification was sent.
+ */
+ private final AtomicBoolean closed;
+
+ /**
+ * This is the cursor used to maintain a read seek position.
+ */
+ private final ByteCursor cursor;
+
+ /**
+ * This is the session associated with the WebSocket connection.
+ */
+ private final Session session;
+
+ /**
+ * This is the underlying TCP channel this reads frames from.
+ */
+ private final Channel channel;
+
+ /**
+ * This is the reason message used for a normal closure.
+ */
+ private final Reason normal;
+
+ /**
+ * This is used to trace the events that occur on the channel.
+ */
+ private final Trace trace;
+
+ /**
+ * Constructor for the <code>FrameProcessor</code> object. This is
+ * used to create a processor that can consume and dispatch frames
+ * as defined by RFC 6455 to a set of registered listeners.
+ *
+ * @param encoder this is the encoder used to send control frames
+ * @param session this is the session associated with the channel
+ * @param channel this is the channel to read frames from
+ */
+ public FrameProcessor(FrameEncoder encoder, Session session, Request request) {
+ this.listeners = new CopyOnWriteArraySet<FrameListener>();
+ this.normal = new Reason(NORMAL_CLOSURE);
+ this.extractor = new ReasonExtractor();
+ this.consumer = new FrameConsumer();
+ this.closed = new AtomicBoolean();
+ this.channel = request.getChannel();
+ this.cursor = channel.getCursor();
+ this.trace = channel.getTrace();
+ this.encoder = encoder;
+ this.session = session;
+ }
+
+ /**
+ * This is used to register a <code>FrameListener</code> to this
+ * instance. The registered listener will receive all user frames
+ * and control frames sent from the client. Also, when the frame
+ * is closed or when an unexpected error occurs the listener is
+ * notified. Any number of listeners can be registered at any time.
+ *
+ * @param listener this is the listener that is to be registered
+ */
+ public void register(FrameListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * This is used to remove a <code>FrameListener</code> from this
+ * instance. After removal the listener will no longer receive
+ * any user frames or control messages from this specific instance.
+ *
+ * @param listener this is the listener to be removed
+ */
+ public void remove(FrameListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * This is used to process frames consumed from the underlying TCP
+ * connection. It will respond to control frames such as pings and
+ * will also handle close frames. Each frame, regardless of its
+ * type will be dispatched to any <code>FrameListener</code> objects
+ * that are registered with the processor. If an a close frame is
+ * received it will echo that close frame, with the same close code
+ * and back to the sender as suggested by RFC 6455 section 5.5.1.
+ */
+ public void process() throws IOException {
+ if(cursor.isReady()) {
+ consumer.consume(cursor);
+
+ if(consumer.isFinished()) {
+ Frame frame = consumer.getFrame();
+ FrameType type = frame.getType();
+
+ trace.trace(READ_FRAME, type);
+
+ if(type.isPong()) {
+ trace.trace(READ_PONG);
+ }
+ if(type.isPing()){
+ Frame response = frame.getFrame(FrameType.PONG);
+
+ trace.trace(READ_PING);
+ encoder.encode(response);
+ trace.trace(WRITE_PONG);
+ }
+ for(FrameListener listener : listeners) {
+ listener.onFrame(session, frame);
+ }
+ if(type.isClose()){
+ Reason reason = extractor.extract(frame);
+
+ if(reason != null) {
+ close(reason);
+ } else {
+ close();
+ }
+ }
+ consumer.clear();
+ }
+ }
+ }
+
+ /**
+ * This is used to report failures back to the client. Any I/O
+ * or frame processing exception is reported back to all of the
+ * registered listeners so that they can take action. The
+ * underlying TCP connection is closed after any failure.
+ *
+ * @param reason this is the cause of the failure
+ */
+ public void failure(Exception reason) throws IOException {
+ if(!closed.getAndSet(true)) {
+ for(FrameListener listener : listeners) {
+ try {
+ listener.onError(session, reason);
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is used to close the connection without a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated. All registered listeners will be
+ * notified of the close event.
+ *
+ * @param reason this is the reason for the connection closure
+ */
+ public void close(Reason reason) throws IOException{
+ if(!closed.getAndSet(true)) {
+ for(FrameListener listener : listeners) {
+ try {
+ listener.onClose(session, reason);
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is used to close the connection when it has not responded
+ * to any activity for a configured period of time. It may be
+ * possible to send up a control frame, however if the TCP channel
+ * is closed this will just notify the listeners.
+ */
+ public void close() throws IOException{
+ if(!closed.getAndSet(true)) {
+ try {
+ for(FrameListener listener : listeners) {
+ listener.onClose(session, normal);
+ }
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ }
+ }
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java
new file mode 100644
index 0000000..3da2635
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java
@@ -0,0 +1,99 @@
+/*
+ * OutputBarrier.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import java.io.IOException;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.ByteWriter;
+
+/**
+ * The <code>OutputBarrier</code> is used to ensure that control
+ * frames and data frames do not get sent at the same time. Sending
+ * both at the same time could lead to the status checking thread
+ * being blocked and this could eventually exhaust the thread pool.
+ *
+ * @author Niall Gallagher
+ */
+class OutputBarrier {
+
+ /**
+ * This is used to check if there is an operation in progress.
+ */
+ private final ReentrantLock lock;
+
+ /**
+ * This is the underlying sender used to send the frames.
+ */
+ private final ByteWriter writer;
+
+ /**
+ * This is the TCP channel the frames are delivered over.
+ */
+ private final Channel channel;
+
+ /**
+ * This is the length of time to wait before failing to lock.
+ */
+ private final long duration;
+
+ /**
+ * Constructor for the <code>OutputBarrier</code> object. This
+ * is used to ensure that if there is currently a blocking write
+ * in place that the <code>SessionChecker</code> will not end up
+ * being blocked if it attempts to send a control frame.
+ *
+ * @param request this is the request to get the TCP channel from
+ * @param duration this is the length of time to wait for the lock
+ */
+ public OutputBarrier(Request request, long duration) {
+ this.lock = new ReentrantLock();
+ this.channel = request.getChannel();
+ this.writer = channel.getWriter();
+ this.duration = duration;
+ }
+
+ /**
+ * This method is used to send all frames. It is important that
+ * a lock is used to protect this so that if there is an attempt
+ * to send out a control frame while the connection is blocked
+ * there is an exception thrown.
+ *
+ * @param frame this is the frame to send over the TCP channel
+ */
+ public void send(byte[] frame) throws IOException {
+ try {
+ if(!lock.tryLock(duration, MILLISECONDS)) {
+ throw new IOException("Transport lock could not be acquired");
+ }
+ try {
+ writer.write(frame);
+ writer.flush(); // less throughput, better latency
+ } finally {
+ lock.unlock();
+ }
+ } catch(Exception e) {
+ throw new IOException("Error writing to transport", e);
+ }
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java
new file mode 100644
index 0000000..9deb66a
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java
@@ -0,0 +1,111 @@
+/*
+ * PathRouter.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_PROTOCOL;
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION;
+import static org.simpleframework.http.Protocol.UPGRADE;
+import static org.simpleframework.http.Protocol.WEBSOCKET;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.simpleframework.http.Path;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+
+/**
+ * The <code>PathRouter</code> is used when there are multiple
+ * services that can be used. Each service is selected based on the
+ * path sent in the initiating request. If a match cannot be made
+ * based on the request then a default service us chosen.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.RouterContainer
+ */
+public class PathRouter implements Router {
+
+ /**
+ * This is the set of services that can be selected.
+ */
+ private final Map<String, Service> registry;
+
+ /**
+ * This is the default service chosen if there is no match.
+ */
+ private final Service primary;
+
+ /**
+ * Constructor for the <code>PathRouter</code> object. This is used
+ * to create a router using a selection of services that can be
+ * selected using the path provided in the initiating request.
+ *
+ * @param registry this is the registry of available services
+ * @param primary this is the default service to use
+ */
+ public PathRouter(Map<String, Service> registry, Service primary) throws IOException {
+ this.registry = registry;
+ this.primary = primary;
+ }
+
+ /**
+ * This is used to route an incoming request to a service if
+ * the request represents a WebSocket handshake as defined by
+ * RFC 6455. If the request is not a session initiating handshake
+ * then this will return a null value to allow it to be processed
+ * by some other part of the server.
+ *
+ * @param request this is the request to use for routing
+ * @param response this is the response to establish the session
+ *
+ * @return a service that can be used to process the session
+ */
+ public Service route(Request request, Response response) {
+ String token = request.getValue(UPGRADE);
+
+ if(token != null) {
+ if(token.equalsIgnoreCase(WEBSOCKET)) {
+ List<String> protocols = request.getValues(SEC_WEBSOCKET_PROTOCOL);
+ String version = request.getValue(SEC_WEBSOCKET_VERSION);
+ Path path = request.getPath();
+ String normal = path.getPath();
+
+ if(version != null) {
+ response.setValue(SEC_WEBSOCKET_VERSION, version);
+ }
+ for(String protocol : protocols) {
+ String original = response.getValue(SEC_WEBSOCKET_PROTOCOL);
+
+ if(original == null) {
+ response.setValue(SEC_WEBSOCKET_PROTOCOL, protocol);
+ }
+ }
+ Service service = registry.get(normal);
+
+ if(service != null) {
+ return service;
+ }
+ return primary;
+ }
+ }
+ return null;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java
new file mode 100644
index 0000000..54060c9
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java
@@ -0,0 +1,105 @@
+/*
+ * ProtocolRouter.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_PROTOCOL;
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION;
+import static org.simpleframework.http.Protocol.UPGRADE;
+import static org.simpleframework.http.Protocol.WEBSOCKET;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+
+/**
+ * The <code>ProtocolRouter</code> is used when there are multiple
+ * services that can be used. Each service is selected based on the
+ * protocol sent in the initiating request. If a match cannot be
+ * made based on the request then a default service us chosen.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.RouterContainer
+ */
+public class ProtocolRouter implements Router {
+
+ /**
+ * This is the set of services that can be selected.
+ */
+ private final Map<String, Service> registry;
+
+ /**
+ * This is the default service chosen if there is no match.
+ */
+ private final Service primary;
+
+ /**
+ * Constructor for the <code>ProtocolRouter</code> object. This is
+ * used to create a router using a selection of services that can
+ * be selected using the <code>Sec-WebSocket-Protocol</code> header
+ * sent in the initiating request by the client.
+ *
+ * @param registry this is the registry of available services
+ * @param primary this is the default service to use
+ */
+ public ProtocolRouter(Map<String, Service> registry, Service primary) throws IOException {
+ this.registry = registry;
+ this.primary = primary;
+ }
+
+ /**
+ * This is used to route an incoming request to a service if
+ * the request represents a WebSocket handshake as defined by
+ * RFC 6455. If the request is not a session initiating handshake
+ * then this will return a null value to allow it to be processed
+ * by some other part of the server.
+ *
+ * @param request this is the request to use for routing
+ * @param response this is the response to establish the session
+ *
+ * @return a service that can be used to process the session
+ */
+ public Service route(Request request, Response response) {
+ String token = request.getValue(UPGRADE);
+
+ if(token != null) {
+ if(token.equalsIgnoreCase(WEBSOCKET)) {
+ List<String> protocols = request.getValues(SEC_WEBSOCKET_PROTOCOL);
+ String version = request.getValue(SEC_WEBSOCKET_VERSION);
+
+ if(version != null) {
+ response.setValue(SEC_WEBSOCKET_VERSION, version);
+ }
+ for(String protocol : protocols) {
+ Service service = registry.get(protocol);
+
+ if(service != null) {
+ response.setValue(SEC_WEBSOCKET_PROTOCOL, protocol);
+ return service;
+ }
+ }
+ return primary;
+ }
+ }
+ return null;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java
new file mode 100644
index 0000000..fb6ce88
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java
@@ -0,0 +1,114 @@
+/*
+ * ReasonExtractor.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.CloseCode.NO_STATUS_CODE;
+
+import org.simpleframework.http.socket.CloseCode;
+import org.simpleframework.http.socket.DataConverter;
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.Reason;
+
+/**
+ * The <code>ReasonExtractor</code> object is used to extract the close
+ * reason from a frame payload. If their is no close reason this will
+ * return a <code>Reason</code> with just the close code. Finally in
+ * the event of a botched frame being sent with no close code then the
+ * close code 1005 is used to indicate no reason.
+ *
+ * @author Niall Gallagher
+ */
+class ReasonExtractor {
+
+ /**
+ * This is the data converter object used to convert data.
+ */
+ private final DataConverter converter;
+
+ /**
+ * Constructor for the <code>ReasonExtractor</code> object. This
+ * is used to create an extractor for close code and the close
+ * reason descriptions. All descriptions are decoded using the
+ * UTF-8 character encoding.
+ */
+ public ReasonExtractor() {
+ this.converter = new DataConverter();
+ }
+
+ /**
+ * This is used to extract a reason from the provided frame. The
+ * close reason is taken from the first two bytes of the frame
+ * payload and the UTF-8 string that follows is the description.
+ *
+ * @param frame this is the frame to extract the reason from
+ *
+ * @return a reason containing the close code and reason
+ */
+ public Reason extract(Frame frame) {
+ byte[] data = frame.getBinary();
+
+ if(data.length > 0) {
+ CloseCode code = extractCode(data);
+ String text = extractText(data);
+
+ return new Reason(code, text);
+ }
+ return new Reason(NO_STATUS_CODE);
+ }
+
+ /**
+ * This method is used to extract the UTF-8 description from the
+ * frame payload. If there are only two bytes within the payload
+ * then this will return null for the reason.
+ *
+ * @param data the frame payload to extract the description from
+ *
+ * @return returns the description within the payload
+ */
+ private String extractText(byte[] data) {
+ int length = data.length - 2;
+
+ if(length > 0) {
+ return converter.convert(data, 2, length);
+ }
+ return null;
+ }
+
+ /**
+ * This method is used to extract the close code. The close code
+ * is an two byte integer in network byte order at the start
+ * of the close frame payload. This code is required by RFC 6455
+ * however if not code is available code 1005 is returned.
+ *
+ * @param data the frame payload to extract the description from
+ *
+ * @return returns the description within the payload
+ */
+ private CloseCode extractCode(byte[] data) {
+ int length = data.length;
+
+ if(length > 0) {
+ int high = data[0];
+ int low = data[1];
+
+ return CloseCode.resolveCode(high, low);
+ }
+ return NO_STATUS_CODE;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java
new file mode 100644
index 0000000..7e47dc3
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java
@@ -0,0 +1,137 @@
+/*
+ * RequestValidator.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.Protocol.CONNECTION;
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_KEY;
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION;
+import static org.simpleframework.http.Protocol.UPGRADE;
+import static org.simpleframework.http.Protocol.WEBSOCKET;
+
+import java.util.List;
+
+import org.simpleframework.http.Request;
+
+/**
+ * The <code>RequestValidator</code> object is used to ensure requests
+ * for confirm to RFC 6455 section 4.2.1. The client opening handshake
+ * must consist of several parts, including a version of 13 referring
+ * to RFC 6455, a WebSocket key, and the required HTTP connection
+ * details. If any of these are missing the server is obliged to
+ * respond with a HTTP 400 response indicating a bad request.
+ *
+ * @author Niall Gallagher
+ */
+class RequestValidator {
+
+ /**
+ * This is the request forming the client part of the handshake.
+ */
+ private final Request request;
+
+ /**
+ * This is the version referring to the required client version.
+ */
+ private final String version;
+
+ /**
+ * Constructor for the <code>RequestValidator</code> object. This
+ * is used to create a plain vanilla validator that uses version
+ * 13 as dictated by RFC 6455 section 4.2.1.
+ *
+ * @param request this is the handshake request from the client
+ */
+ public RequestValidator(Request request) {
+ this(request, "13");
+ }
+
+ /**
+ * Constructor for the <code>RequestValidator</code> object. This
+ * is used to create a plain vanilla validator that uses version
+ * 13 as dictated by RFC 6455 section 4.2.1.
+ *
+ * @param request this is the handshake request from the client
+ * @param version a version other than 13 if desired
+ */
+ public RequestValidator(Request request, String version) {
+ this.request = request;
+ this.version = version;
+ }
+
+ /**
+ * This is used to determine if the client handshake request had
+ * all the required headers as dictated by RFC 6455 section 4.2.1.
+ * If the request does not contain any of these parts then this
+ * will return false, indicating a HTTP 400 response should be
+ * sent to the client.
+ *
+ * @return true if the request was a valid handshake
+ */
+ public boolean isValid() {
+ if(!isProtocol()) {
+ return false;
+ }
+ if(!isUpgrade()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This is used to determine if the request is a valid WebSocket
+ * handshake of the correct version. This also checks to see if
+ * the request contained the required handshake token.
+ *
+ * @return this returns true if the request is a valid handshake
+ */
+ private boolean isProtocol() {
+ String protocol = request.getValue(SEC_WEBSOCKET_VERSION);
+ String token = request.getValue(SEC_WEBSOCKET_KEY);
+
+ if(token != null) {
+ return version.equals(protocol);
+ }
+ return false;
+ }
+
+ /**
+ * Here we check to ensure that there is a HTTP connection header
+ * with the required upgrade token. The upgrade token may be
+ * one of many, so all must be checked. Finally to ensure that
+ * the upgrade is for a WebSocket the upgrade header is checked.
+ *
+ * @return this returns true if the request is an upgrade
+ */
+ private boolean isUpgrade() {
+ List<String> tokens = request.getValues(CONNECTION);
+
+ for(String token : tokens) {
+ if(token.equalsIgnoreCase(UPGRADE)) {
+ String upgrade = request.getValue(UPGRADE);
+
+ if(upgrade != null) {
+ return upgrade.equalsIgnoreCase(WEBSOCKET);
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java
new file mode 100644
index 0000000..0ba780e
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java
@@ -0,0 +1,159 @@
+/*
+ * ResponseBuilder.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.Protocol.CLOSE;
+import static org.simpleframework.http.Protocol.CONNECTION;
+import static org.simpleframework.http.Protocol.DATE;
+import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_ACCEPT;
+import static org.simpleframework.http.Protocol.UPGRADE;
+import static org.simpleframework.http.Protocol.WEBSOCKET;
+import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_HEADER;
+
+import java.io.IOException;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.Status;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.ByteWriter;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>ResponseBuilder</code> object is used to build a response
+ * to a WebSocket handshake. In order for a successful handshake to
+ * complete a HTTP request must have a version of 13 referring
+ * to RFC 6455, a WebSocket key, and the required HTTP connection
+ * details. If any of these are missing the server is obliged to
+ * respond with a HTTP 400 response indicating a bad request.
+ *
+ * @author Niall Gallagher
+ */
+class ResponseBuilder {
+
+ /**
+ * This is used to validate the initiating WebSocket request.
+ */
+ private final RequestValidator validator;
+
+ /**
+ * This is the accept token generated for the request.
+ */
+ private final AcceptToken token;
+
+ /**
+ * This is the sender used to send the WebSocket response.
+ */
+ private final ByteWriter writer;
+
+ /**
+ * This is the response to the WebSocket handshake.
+ */
+ private final Response response;
+
+ /**
+ * This is the underlying TCP channel for the request.
+ */
+ private final Channel channel;
+
+ /**
+ * This is used to trace the activity for the handshake.
+ */
+ private final Trace trace;
+
+ /**
+ * Constructor for the <code>ResponseBuilder</code> object. In order
+ * to process the WebSocket handshake this requires the original
+ * request and the response as well as the underlying TCP channel
+ * which forms the basis of the WebSocket connection.
+ *
+ * @param request this is the request that initiated the handshake
+ * @param response this is the response for the handshake
+ */
+ public ResponseBuilder(Request request, Response response) throws Exception {
+ this.validator = new RequestValidator(request);
+ this.token = new AcceptToken(request);
+ this.channel = request.getChannel();
+ this.writer = channel.getWriter();
+ this.trace = channel.getTrace();
+ this.response = response;
+ }
+
+ /**
+ * This is used to determine if the client handshake request had
+ * all the required headers as dictated by RFC 6455 section 4.2.1.
+ * If the request does not contain any of these parts then this
+ * will return false, indicating a HTTP 400 response is sent to
+ * the client, otherwise a HTTP 101 response is sent.
+ */
+ public void commit() throws IOException {
+ if(validator.isValid()) {
+ accept();
+ } else {
+ reject();
+ }
+ }
+
+ /**
+ * This is used to respond to the client with a HTTP 400 response
+ * indicating the WebSocket handshake failed. No response body is
+ * sent with the rejection message and the underlying TCP channel
+ * is closed to prevent further use of the connection.
+ */
+ private void reject() throws IOException {
+ long time = System.currentTimeMillis();
+
+ response.setStatus(Status.BAD_REQUEST);
+ response.setValue(CONNECTION, CLOSE);
+ response.setDate(DATE, time);
+
+ String header = response.toString();
+ byte[] message = header.getBytes("UTF-8");
+
+ trace.trace(WRITE_HEADER, header);
+ writer.write(message);
+ writer.flush();
+ writer.close();
+ }
+
+ /**
+ * This is used to respond to the client with a HTTP 101 response
+ * to indicate that the WebSocket handshake succeeeded. Once this
+ * response has been sent all traffic between the client and
+ * server will be with WebSocket frames as defined by RFC 6455.
+ */
+ private void accept() throws IOException {
+ long time = System.currentTimeMillis();
+ String accept = token.create();
+
+ response.setStatus(Status.SWITCHING_PROTOCOLS);
+ response.setDescription(UPGRADE);
+ response.setValue(CONNECTION, UPGRADE);
+ response.setDate(DATE, time);
+ response.setValue(SEC_WEBSOCKET_ACCEPT, accept);
+ response.setValue(UPGRADE, WEBSOCKET);
+
+ String header = response.toString();
+ byte[] message = header.getBytes("UTF-8");
+
+ trace.trace(WRITE_HEADER, header);
+ writer.write(message);
+ writer.flush();
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java
new file mode 100644
index 0000000..3b466f5
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java
@@ -0,0 +1,59 @@
+/*
+ * Router.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+
+/**
+ * The <code>Router</code> interface represents a means of routing
+ * a session initiating request to the correct service. Typically
+ * a service is chosen based on the sub-protocol provided in the
+ * initiating request, however it can be chosen on any criteria
+ * available in the request. An initiating request must contain
+ * a <code>Connection</code> header with the <code>websocket</code>
+ * token according to RFC 6455 section 4.2.1. If the request does
+ * not contain this token it is treated as a normal request and
+ * a <code>Service</code> will not be resolved.
+ * <p>
+ * If a service has been successfully chosen from the initiating
+ * request the the value of <code>Sec-WebSocket-Protocol</code> will
+ * contain either the chosen protocol if a match was made with the
+ * initiating request or null to indicate a default choice.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.RouterContainer
+ */
+public interface Router {
+
+ /**
+ * This is used to route an incoming request to a service if
+ * the request represents a WebSocket handshake as defined by
+ * RFC 6455. If the request is not a session initiating handshake
+ * then this must return a null value to allow it to be processed
+ * by some other part of the server.
+ *
+ * @param request this is the request to use for routing
+ * @param response this is the response to establish the session
+ *
+ * @return a service that can be used to process the session
+ */
+ Service route(Request request, Response response);
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java
new file mode 100644
index 0000000..3b018a9
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java
@@ -0,0 +1,109 @@
+/*
+ * RouterContainer.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.io.IOException;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.core.Container;
+
+/**
+ * The <code>RouterContainer</code> is used to route requests that
+ * satisfy a WebSocket opening handshake to a specific service. Each
+ * request intercepted by this <code>Container</code> implementation
+ * is examined for opening handshake criteria as specified by RFC 6455,
+ * and if it contains the required information it is router to a
+ * specific service using a <code>Router</code> implementation. If the
+ * request does not contain the required criteria it is handled by
+ * an internal container delegate.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.service.Router
+ */
+public class RouterContainer implements Container {
+
+ /**
+ * This is the service dispatcher used to dispatch requests.
+ */
+ private final ServiceDispatcher dispatcher;
+
+ /**
+ * This is the container used to handle traditional requests.
+ */
+ private final Container container;
+
+ /**
+ * This is the router used to select specific services.
+ */
+ private final Router router;
+
+ /**
+ * Constructor for the <code>RouterContainer</code> object. This
+ * requires a container to delegate traditional requests to and
+ * a <code>Router</code> implementation which can be used to
+ * select a service to dispatch a WebSocket session to.
+ *
+ * @param container this is the container to delegate to
+ * @param router this is the router used to select services
+ * @param threads this contains the number of threads to use
+ */
+ public RouterContainer(Container container, Router router, int threads) throws IOException {
+ this(container, router, threads, 10000);
+ }
+
+ /**
+ * Constructor for the <code>RouterContainer</code> object. This
+ * requires a container to delegate traditional requests to and
+ * a <code>Router</code> implementation which can be used to
+ * select a service to dispatch a WebSocket session to.
+ *
+ * @param container this is the container to delegate to
+ * @param router this is the router used to select services
+ * @param threads this contains the number of threads to use
+ * @param ping this is the frequency to send ping frames with
+ */
+ public RouterContainer(Container container, Router router, int threads, long ping) throws IOException {
+ this.dispatcher = new ServiceDispatcher(router, threads, ping);
+ this.container = container;
+ this.router = router;
+ }
+
+ /**
+ * This method is used to create a dispatch a <code>Session</code> to
+ * a specific service selected by a router. If the session initiating
+ * handshake fails for any reason this will close the underlying TCP
+ * connection and send a HTTP 400 response back to the client. All
+ * traditional requests that do not represent an WebSocket opening
+ * handshake are dispatched to the internal container.
+ *
+ * @param req the request that contains the client HTTP message
+ * @param resp the response used to deliver the server response
+ */
+ public void handle(Request req, Response resp) {
+ Service service = router.route(req, resp);
+
+ if(service != null) {
+ dispatcher.dispatch(req, resp);
+ } else {
+ container.handle(req, resp);
+ }
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java
new file mode 100644
index 0000000..d95c01f
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java
@@ -0,0 +1,44 @@
+/*
+ * Service.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import org.simpleframework.http.socket.Session;
+
+/**
+ * The <code>Service</code> interface represents a service that can be
+ * used to communicate with the WebSocket protocol defined in RFC 6455.
+ * Typically a service will implement a sub-protocol negotiated from
+ * the initiating HTTP request. The service should be considered a
+ * hand off point rather than an place to implement business logic.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.FrameChannel
+ */
+public interface Service {
+
+ /**
+ * This method connects a new session with a service implementation.
+ * Connecting a session with a service in this way should not block
+ * as it could cause starvation of the servicing thread pool.
+ *
+ * @param session the new session to connect to the service
+ */
+ void connect(Session session);
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java
new file mode 100644
index 0000000..ad5325c
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java
@@ -0,0 +1,149 @@
+/*
+ * ServiceChannel.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.io.IOException;
+
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameListener;
+import org.simpleframework.http.socket.Reason;
+import org.simpleframework.http.socket.FrameChannel;
+
+/**
+ * The <code>ServiceChannel</code> represents a full duplex communication
+ * channel as defined by RFC 6455. Any instance of this will provide
+ * a means to perform asynchronous writes and reads to a remote client
+ * using a lightweight framing protocol. A frame is a finite length
+ * sequence of bytes that can hold either text or binary data. Also,
+ * control frames are used to perform heartbeat monitoring and closure.
+ * <p>
+ * For convenience frames can be consumed from the socket via a
+ * callback to a registered listener. This avoids having to poll each
+ * socket for data and provides a asynchronous event driven model of
+ * communication, which greatly reduces overhead and complication.
+ *
+ * @author Niall Gallagher
+ */
+class ServiceChannel implements FrameChannel {
+
+ /**
+ * This is the internal channel for full duplex communication.
+ */
+ private final FrameChannel channel;
+
+ /**
+ * Constructor for the <code>ServiceChannel</code> object. This is
+ * used to create a channel that is given to the application. This
+ * is synchronized so only one frame can be dispatched at a time.
+ *
+ * @param channel this is the channel to delegate to
+ */
+ public ServiceChannel(FrameChannel channel) {
+ this.channel = channel;
+ }
+
+ /**
+ * This is used to send data to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param data this is the data that is to be sent
+ */
+ public synchronized void send(byte[] data) throws IOException {
+ channel.send(data);
+ }
+
+ /**
+ * This is used to send text to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param text this is the text that is to be sent
+ */
+ public synchronized void send(String text) throws IOException {
+ channel.send(text);
+ }
+
+ /**
+ * This is used to send data to the connected client. To prevent
+ * an application code from causing resource issues this will block
+ * as soon as a configured linked list of mapped memory buffers has
+ * been exhausted. Caution should be taken when writing a broadcast
+ * implementation that can write to multiple sockets as a badly
+ * behaving socket that has filled its output buffering capacity
+ * can cause congestion.
+ *
+ * @param frame this is the frame that is to be sent
+ */
+ public synchronized void send(Frame frame) throws IOException {
+ channel.send(frame);
+ }
+
+ /**
+ * This is used to register a <code>FrameListener</code> to this
+ * instance. The registered listener will receive all user frames
+ * and control frames sent from the client. Also, when the frame
+ * is closed or when an unexpected error occurs the listener is
+ * notified. Any number of listeners can be registered at any time.
+ *
+ * @param listener this is the listener that is to be registered
+ */
+ public synchronized void register(FrameListener listener) throws IOException {
+ channel.register(listener);
+ }
+
+ /**
+ * This is used to remove a <code>FrameListener</code> from this
+ * instance. After removal the listener will no longer receive
+ * any user frames or control messages from this specific instance.
+ *
+ * @param listener this is the listener to be removed
+ */
+ public synchronized void remove(FrameListener listener) throws IOException {
+ channel.remove(listener);
+ }
+
+ /**
+ * This is used to close the connection with a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated.
+ *
+ * @param reason the reason for closing the connection
+ */
+ public synchronized void close(Reason reason) throws IOException {
+ channel.close(reason);
+ }
+
+ /**
+ * This is used to close the connection without a specific reason.
+ * The close reason will be sent as a control frame before the
+ * TCP connection is terminated.
+ */
+ public void close() throws IOException {
+ channel.close();
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java
new file mode 100644
index 0000000..509495f
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java
@@ -0,0 +1,101 @@
+/*
+ * ServiceDispatcher.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.io.IOException;
+
+import org.simpleframework.common.thread.ConcurrentScheduler;
+import org.simpleframework.common.thread.Scheduler;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.transport.reactor.ExecutorReactor;
+import org.simpleframework.transport.reactor.Reactor;
+
+/**
+ * The <code>ServiceDispatcher</code> object is used to perform the
+ * opening handshake for a WebSocket session. Once the session has been
+ * established it is connected to a <code>Service</code> where frames
+ * can be sent and received. If for any reason the handshake fails
+ * this will terminated the connection with a HTTP 400 response.
+ *
+ * @author Niall Gallagher
+ */
+class ServiceDispatcher {
+
+ /**
+ * This is the session dispatcher used to dispatch the session.
+ */
+ private final SessionDispatcher dispatcher;
+
+ /**
+ * This is used to build the sessions from the handshake request.
+ */
+ private final SessionBuilder builder;
+
+ /**
+ * This is used asynchronously read frames from the TCP channel.
+ */
+ private final Scheduler scheduler;
+
+ /**
+ * This is used to notify of read events on the TCP channel.
+ */
+ private final Reactor reactor;
+
+ /**
+ * Constructor for the <code>ServiceDispatcher</code> object. The
+ * dispatcher created will dispatch WebSocket sessions to a service
+ * using the provided <code>Router</code> instance.
+ *
+ * @param router this is the router used to select a service
+ * @param threads this is the number of threads to use
+ */
+ public ServiceDispatcher(Router router, int threads) throws IOException {
+ this(router, threads, 10000);
+ }
+
+ /**
+ * Constructor for the <code>ServiceDispatcher</code> object. The
+ * dispatcher created will dispatch WebSocket sessions to a service
+ * using the provided <code>Router</code> instance.
+ *
+ * @param router this is the router used to select a service
+ * @param threads this is the number of threads to use
+ * @param ping this is the frequency used to send ping frames
+ */
+ public ServiceDispatcher(Router router, int threads, long ping) throws IOException {
+ this.scheduler = new ConcurrentScheduler(FrameCollector.class, threads);
+ this.reactor = new ExecutorReactor(scheduler);
+ this.builder = new SessionBuilder(scheduler, reactor, ping);
+ this.dispatcher = new SessionDispatcher(builder, router);
+ }
+
+ /**
+ * This method is used to create a dispatch a <code>Session</code> to
+ * a specific service selected by a router. If the session initiating
+ * handshake fails for any reason this will close the underlying TCP
+ * connection and send a HTTP 400 response back to the client.
+ *
+ * @param request this is the session initiating request
+ * @param response this is the session initiating response
+ */
+ public void dispatch(Request request, Response response) {
+ dispatcher.dispatch(request, response);
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java
new file mode 100644
index 0000000..a5d0079
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java
@@ -0,0 +1,97 @@
+/*
+ * ServiceEvent.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+/**
+ * The <code>ServiceEvent</code> enumeration contains the events that
+ * are dispatched processing a WebSocket. To see how a WebSocket is
+ * behaving and to gather performance statistics the service events
+ * can be intercepted using a custom <code>TraceAnalyzer</code> object.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.transport.trace.TraceAnalyzer
+ */
+public enum ServiceEvent {
+
+ /**
+ * This event is dispatched when a WebSocket is connected.
+ */
+ OPEN_SOCKET,
+
+ /**
+ * This event is dispatched when a WebSocket is dispatched.
+ */
+ DISPATCH_SOCKET,
+
+ /**
+ * This event is dispatched when a WebSocket channel is closed.
+ */
+ TERMINATE_SOCKET,
+
+ /**
+ * This event is dispatched when the response handshake is sent.
+ */
+ WRITE_HEADER,
+
+ /**
+ * This event is dispatched when the WebSocket receives a ping.
+ */
+ READ_PING,
+
+ /**
+ * This event is dispatched when a ping is sent over a WebSocket.
+ */
+ WRITE_PING,
+
+ /**
+ * This event is dispatched when the WebSocket receives a pong.
+ */
+ READ_PONG,
+
+ /**
+ * This event is dispatched when a pong is sent over a WebSocket.
+ */
+ WRITE_PONG,
+
+ /**
+ * This event is dispatched when a frame is read from a WebSocket.
+ */
+ READ_FRAME,
+
+ /**
+ * This event is dispatched when a frame is sent over a WebSocket.
+ */
+ WRITE_FRAME,
+
+ /**
+ * This indicates that there has been no response to a ping.
+ */
+ PING_EXPIRED,
+
+ /**
+ * This indicates that there has been no response to a ping.
+ */
+ PONG_RECEIVED,
+
+ /**
+ * This event is dispatched when an error occurs with a WebSocket.
+ */
+ ERROR;
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java
new file mode 100644
index 0000000..b8fc083
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java
@@ -0,0 +1,139 @@
+/*
+ * ServiceSession.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.util.Map;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.socket.FrameChannel;
+import org.simpleframework.http.socket.Session;
+
+/**
+ * The <code>ServiceSession</code> represents a simple WebSocket session
+ * that contains the connection handshake details and the actual socket.
+ * In order to determine how the session should be interacted with the
+ * protocol is conveniently exposed, however all attributes of the
+ * original HTTP request are available.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.socket.FrameChannel
+ */
+class ServiceSession implements Session {
+
+ /**
+ * The WebSocket used for asynchronous full duplex communication.
+ */
+ private final FrameChannel channel;
+
+ /**
+ * This is the initiating response associated with the session.
+ */
+ private final Response response;
+
+ /**
+ * This is the initiating request associated with the session.
+ */
+ private final Request request;
+
+ /**
+ * This is the bag of attributes used by this session.
+ */
+ private final Map attributes;
+
+ /**
+ * Constructor for the <code>ServiceSession</code> object. This is used
+ * to create the session that will be used by a <code>Service</code> to
+ * send and receive WebSocket frames.
+ *
+ * @param channel this is the actual WebSocket for the session
+ * @param request this is the session initiating request
+ * @param response this is the session initiating response
+ */
+ public ServiceSession(FrameChannel channel, Request request, Response response) {
+ this.channel = new ServiceChannel(channel);
+ this.attributes = request.getAttributes();
+ this.response = response;
+ this.request = request;
+ }
+
+ /**
+ * This can be used to retrieve the response attributes. These can
+ * be used to keep state with the response when it is passed to
+ * other systems for processing. Attributes act as a convenient
+ * model for storing objects associated with the response. This
+ * also inherits attributes associated with the client connection.
+ *
+ * @return the attributes of that have been set on the request
+ */
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * This is used as a shortcut for acquiring attributes for the
+ * response. This avoids acquiring the attribute <code>Map</code>
+ * in order to retrieve the attribute directly from that object.
+ * The attributes contain data specific to the response.
+ *
+ * @param key this is the key of the attribute to acquire
+ *
+ * @return this returns the attribute for the specified name
+ */
+ public Object getAttribute(Object key) {
+ return attributes.get(key);
+ }
+
+ /**
+ * Provides a <code>WebSocket</code> that can be used to communicate
+ * with the connected client. Communication is full duplex and also
+ * asynchronous through the use of a <code>FrameListener</code> that
+ * can be registered with the socket.
+ *
+ * @return a web socket for full duplex communication
+ */
+ public FrameChannel getChannel() {
+ return channel;
+ }
+
+ /**
+ * Provides the <code>Request</code> used to initiate the session.
+ * This is useful in establishing the identity of the user, acquiring
+ * an security information and also for determining the request path
+ * that was used, which be used to establish context.
+ *
+ * @return the request used to initiate the session
+ */
+ public Request getRequest() {
+ return request;
+ }
+
+ /**
+ * Provides the <code>Response</code> used to establish the session
+ * with the remote client. This is useful in establishing the protocol
+ * used to create the session and also for determining various other
+ * useful contextual information.
+ *
+ * @return the response used to establish the session
+ */
+ public Response getResponse() {
+ return response;
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java
new file mode 100644
index 0000000..59864ee
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java
@@ -0,0 +1,93 @@
+/*
+ * SessionBuilder.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import java.io.IOException;
+
+import org.simpleframework.common.thread.Scheduler;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.socket.Session;
+import org.simpleframework.transport.reactor.Reactor;
+
+/**
+ * The <code>SessionBuilder</code> object is used to create sessions
+ * for connected WebSockets. Before the session is created a response
+ * is sent back to the connected client. If for some reason the session
+ * is not valid or does not conform to the requirements of RFC 6455
+ * then a HTTP 400 response code is sent and the TCP channel is closed.
+ *
+ * @author Niall Gallagher
+ */
+class SessionBuilder {
+
+ /**
+ * This is the scheduler that is used to ping WebSocket sessions.
+ */
+ private final Scheduler scheduler;
+
+ /**
+ * This is the reactor used to register for I/O notifications.
+ */
+ private final Reactor reactor;
+
+ /**
+ * This is the frequency the server should send out ping frames.
+ */
+ private final long ping;
+
+ /**
+ * Constructor for the <code>SessionBuilder</code> object. This is
+ * used to create sessions using the request and response associated
+ * with the WebSocket opening handshake.
+ *
+ * @param scheduler this is the shared thread pool used for pinging
+ * @param reactor this is used to check for I/O notifications
+ * @param ping this is the frequency to send out ping frames
+ */
+ public SessionBuilder(Scheduler scheduler, Reactor reactor, long ping) {
+ this.scheduler = scheduler;
+ this.reactor = reactor;
+ this.ping = ping;
+ }
+
+ /**
+ * This is used to create a WebSocket session. If at any point there
+ * is an error creating the session the underlying TCP connection is
+ * closed and a <code>Session</code> is returned regardless.
+ *
+ * @param request this is the request associated with this session
+ * @param response this is the response associated with this session
+ *
+ * @return this returns the session associated with the WebSocket
+ */
+ public Session create(Request request, Response response) throws Exception {
+ FrameConnection connection = new FrameConnection(request, response, reactor);
+ ResponseBuilder builder = new ResponseBuilder(request, response);
+ StatusChecker checker = new StatusChecker(connection, request, scheduler, ping);
+
+ try {
+ builder.commit();
+ checker.start();
+ } catch(Exception e) {
+ throw new IOException("Could not send response", e);
+ }
+ return connection.open();
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java
new file mode 100644
index 0000000..6543be0
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java
@@ -0,0 +1,111 @@
+/*
+ * SessionDispatcher.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.service.ServiceEvent.DISPATCH_SOCKET;
+import static org.simpleframework.http.socket.service.ServiceEvent.ERROR;
+import static org.simpleframework.http.socket.service.ServiceEvent.TERMINATE_SOCKET;
+
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.socket.Session;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>SessionDispatcher</code> object is used to perform the
+ * opening handshake for a WebSocket session. Once the session has been
+ * established it is connected to a <code>Service</code> where frames
+ * can be sent and received. If for any reason the handshake fails
+ * this will terminated the connection with a HTTP 400 response.
+ *
+ * @author Niall Gallagher
+ */
+class SessionDispatcher {
+
+ /**
+ * This is used to create the session for the WebSocket.
+ */
+ private final SessionBuilder builder;
+
+ /**
+ * This is used to select the service to dispatch to.
+ */
+ private final Router router;
+
+ /**
+ * Constructor for the <code>SessionDispatcher</code> object. The
+ * dispatcher created will dispatch WebSocket sessions to a service
+ * using the provided <code>Router</code> instance.
+ *
+ * @param builder this is used to build the WebSocket session
+ * @param router this is used to select the service
+ */
+ public SessionDispatcher(SessionBuilder builder, Router router) {
+ this.builder = builder;
+ this.router = router;
+ }
+
+ /**
+ * This method is used to create a dispatch a <code>Session</code> to
+ * a specific service selected by a router. If the session initiating
+ * handshake fails for any reason this will close the underlying TCP
+ * connection and send a HTTP 400 response back to the client.
+ *
+ * @param request this is the session initiating request
+ * @param response this is the session initiating response
+ */
+ public void dispatch(Request request, Response response) {
+ Channel channel = request.getChannel();
+ Trace trace = channel.getTrace();
+
+ try {
+ Service service = router.route(request, response);
+ Session session = builder.create(request, response);
+
+ trace.trace(DISPATCH_SOCKET);
+ service.connect(session);
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ terminate(request, response);
+ }
+ }
+
+ /**
+ * This method is used to terminate the connection and commit the
+ * response. Terminating the session before it has been dispatched
+ * is done when there is a protocol or an unexpected I/O error with
+ * the underlying TCP channel.
+ *
+ * @param request this is the session initiating request
+ * @param response this is the session initiating response
+ */
+ public void terminate(Request request, Response response) {
+ Channel channel = request.getChannel();
+ Trace trace = channel.getTrace();
+
+ try {
+ response.close();
+ channel.close();
+ trace.trace(TERMINATE_SOCKET);
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ }
+ }
+}
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java
new file mode 100644
index 0000000..1f4f0d7
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java
@@ -0,0 +1,220 @@
+/*
+ * StatusChecker.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import static org.simpleframework.http.socket.CloseCode.INTERNAL_SERVER_ERROR;
+import static org.simpleframework.http.socket.CloseCode.NORMAL_CLOSURE;
+import static org.simpleframework.http.socket.FrameType.PING;
+import static org.simpleframework.http.socket.service.ServiceEvent.ERROR;
+import static org.simpleframework.http.socket.service.ServiceEvent.PING_EXPIRED;
+import static org.simpleframework.http.socket.service.ServiceEvent.PONG_RECEIVED;
+import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_PING;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.simpleframework.common.thread.Scheduler;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.socket.DataFrame;
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.Reason;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>StatusChecker</code> object is used to perform health
+ * checks on connected sessions. Health is determined using the ping
+ * pong protocol defined in RFC 6455. The ping pong protocol requires
+ * that any endpoint must respond to a ping control frame with a pong
+ * control frame containing the same payload. This session checker
+ * will send out out ping controls frames and wait for a pong frame.
+ * If it does not receive a pong frame after a configured expiry time
+ * then it will close the associated session.
+ *
+ * @author Niall Gallagher
+ */
+class StatusChecker implements Runnable{
+
+ /**
+ * This is used to perform the monitoring of the sessions.
+ */
+ private final StatusResultListener listener;
+
+ /**
+ * This is the WebSocket this this pinger will be monitoring.
+ */
+ private final FrameConnection connection;
+
+ /**
+ * This is the shared scheduler used to execute this checker.
+ */
+ private final Scheduler scheduler;
+
+ /**
+ * This is a count of the number of unacknowledged ping frames.
+ */
+ private final AtomicLong counter;
+
+ /**
+ * This is the underling TCP channel that is being checked.
+ */
+ private final Channel channel;
+
+ /**
+ * The only reason for a close is for an unexpected error.
+ */
+ private final Reason normal;
+
+ /**
+ * The only reason for a close is for an unexpected error.
+ */
+ private final Reason error;
+
+ /**
+ * This is used to trace various events for this pinger.
+ */
+ private final Trace trace;
+
+ /**
+ * This is the frame that contains the ping to send.
+ */
+ private final Frame frame;
+
+ /**
+ * This is the frequency with which the checker should run.
+ */
+ private final long frequency;
+
+ /**
+ * Constructor for the <code>StatusChecker</code> object. This
+ * is used to create a pinger that will send out ping frames at
+ * a specified interval. If a session does not respond within
+ * three times the duration of the ping the connection is reset.
+ *
+ * @param connection this is the WebSocket to send the frames
+ * @param request this is the associated request
+ * @param scheduler this is the scheduler used to execute this
+ * @param frequency this is the frequency with which to ping
+ */
+ public StatusChecker(FrameConnection connection, Request request, Scheduler scheduler, long frequency) {
+ this.listener = new StatusResultListener(this);
+ this.error = new Reason(INTERNAL_SERVER_ERROR);
+ this.normal = new Reason(NORMAL_CLOSURE);
+ this.frame = new DataFrame(PING);
+ this.counter = new AtomicLong();
+ this.channel = request.getChannel();
+ this.trace = channel.getTrace();
+ this.connection = connection;
+ this.scheduler = scheduler;
+ this.frequency = frequency;
+ }
+
+ /**
+ * This is used to kick of the status checking. Here an initial
+ * ping is sent over the socket and the task is then scheduled to
+ * check the result after the frequency period has expired. If
+ * this method fails for any reason the TCP channel is closed.
+ */
+ public void start() {
+ try {
+ connection.register(listener);
+ trace.trace(WRITE_PING);
+ connection.send(frame);
+ counter.getAndIncrement();
+ scheduler.execute(this, frequency);
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ channel.close();
+ }
+ }
+
+ /**
+ * This method is used to check to see if a session has expired.
+ * If there have been three unacknowledged ping events then this
+ * will force a closure of the WebSocket connection. This is done
+ * to ensure only healthy connections are maintained within the
+ * server, also RFC 6455 recommends using the ping pong protocol.
+ */
+ public void run() {
+ long count = counter.get();
+
+ try {
+ if(count < 3) {
+ trace.trace(WRITE_PING);
+ connection.send(frame);
+ counter.getAndIncrement();
+ scheduler.execute(this, frequency); // schedule the next one
+ } else {
+ trace.trace(PING_EXPIRED);
+ connection.close(normal);
+ }
+ } catch (Exception cause) {
+ trace.trace(ERROR, cause);
+ channel.close();
+ }
+ }
+
+ /**
+ * If the connection gets a response to its ping message then this
+ * will reset the internal counter. This ensure that the connection
+ * does not time out. If after three pings there is not response
+ * from the other side then the connection will be terminated.
+ */
+ public void refresh() {
+ try {
+ trace.trace(PONG_RECEIVED);
+ counter.set(0);
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ channel.close();
+ }
+ }
+
+ /**
+ * This is used to close the session and send a 1011 close code
+ * to the client indicating an internal server error. Closing
+ * of the session in this manner only occurs if there is an
+ * expiry of the session or an I/O error, both of which are
+ * unexpected and violate the behaviour as defined in RFC 6455.
+ */
+ public void failure() {
+ try {
+ connection.close(error);
+ channel.close();
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ channel.close();
+ }
+ }
+
+ /**
+ * This is used to close the session and send a 1000 close code
+ * to the client indicating a normal closure. This will be called
+ * when there is a close notification dispatched to the status
+ * listener. Typically here a graceful closure is best.
+ */
+ public void close() {
+ try {
+ connection.close(normal);
+ channel.close();
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ channel.close();
+ }
+ }
+} \ No newline at end of file
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java
new file mode 100644
index 0000000..2b2a049
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java
@@ -0,0 +1,93 @@
+/*
+ * StatusResultListener.java February 2014
+ *
+ * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
+ *
+ * Licensed 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 org.simpleframework.http.socket.service;
+
+import org.simpleframework.http.socket.Frame;
+import org.simpleframework.http.socket.FrameListener;
+import org.simpleframework.http.socket.FrameType;
+import org.simpleframework.http.socket.Reason;
+import org.simpleframework.http.socket.Session;
+
+/**
+ * The <code>StatusResultListener</code> is used to listen for responses
+ * to ping frames sent out by the server. A response to the ping frame
+ * is a pong frame. When a pong is received it allows the session to
+ * be scheduled to receive another ping.
+ *
+ * @author Niall Gallagher
+ */
+class StatusResultListener implements FrameListener {
+
+ /**
+ * This is used to ping sessions to check for health.
+ */
+ private final StatusChecker checker;
+
+ /**
+ * Constructor for the <code>StatusResultListener</code> object.
+ * This requires the session health checker that performs the pings
+ * so that it can reschedule the session for multiple pings if
+ * the connection responds with a pong.
+ *
+ * @param checker this is the session health checker
+ */
+ public StatusResultListener(StatusChecker checker) {
+ this.checker = checker;
+ }
+
+ /**
+ * This is called when a new frame arrives on the WebSocket. If
+ * the frame is a pong then this will reschedule the the session
+ * to receive another ping frame.
+ *
+ * @param session this is the associated session
+ * @param frame this is the frame that has been received
+ */
+ public void onFrame(Session session, Frame frame) {
+ FrameType type = frame.getType();
+
+ if(type.isPong()) {
+ checker.refresh();
+ }
+ }
+
+ /**
+ * This is called when there is an error with the connection.
+ * When called the session is removed from the checker and no
+ * more ping frames are sent.
+ *
+ * @param session this is the associated session
+ * @param cause this is the cause of the error
+ */
+ public void onError(Session session, Exception cause) {
+ checker.failure();
+ }
+
+ /**
+ * This is called when the connection is closed from the other
+ * side. When called the session is removed from the checker
+ * and no more ping frames are sent.
+ *
+ * @param session this is the associated session
+ * @param reason this is the reason the connection was closed
+ */
+ public void onClose(Session session, Reason reason) {
+ checker.close();
+ }
+} \ No newline at end of file