diff options
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java')
-rw-r--r-- | simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java | 162 |
1 files changed, 162 insertions, 0 deletions
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; + } +} |