summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java')
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java229
1 files changed, 229 insertions, 0 deletions
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;
+ }
+}