+ *
+ * As a workaround to the HashDOS
+ * vulnerability, the decoder limits the maximum number of decoded key-value
+ * parameter pairs, up to {@literal 1024} by default, and you can configure it
+ * when you construct the decoder by passing an additional integer parameter.
+ *
* @see QueryStringEncoder
*
* @apiviz.stereotype utility
@@ -43,10 +58,15 @@ import io.netty.util.CharsetUtil;
*/
public class QueryStringDecoder {
+ private static final int DEFAULT_MAX_PARAMS = 1024;
+
private final Charset charset;
private final String uri;
+ private final boolean hasPath;
+ private final int maxParams;
private String path;
private Map> params;
+ private int nParams;
/**
* Creates a new decoder that decodes the specified URI. The decoder will
@@ -56,21 +76,51 @@ public class QueryStringDecoder {
this(uri, HttpCodecUtil.DEFAULT_CHARSET);
}
+ /**
+ * Creates a new decoder that decodes the specified URI encoded in the
+ * specified charset.
+ */
+ public QueryStringDecoder(String uri, boolean hasPath) {
+ this(uri, HttpCodecUtil.DEFAULT_CHARSET, hasPath);
+ }
+
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(String uri, Charset charset) {
+ this(uri, charset, true);
+ }
+
+ /**
+ * Creates a new decoder that decodes the specified URI encoded in the
+ * specified charset.
+ */
+ public QueryStringDecoder(String uri, Charset charset, boolean hasPath) {
+ this(uri, charset, hasPath, DEFAULT_MAX_PARAMS);
+ }
+
+ /**
+ * Creates a new decoder that decodes the specified URI encoded in the
+ * specified charset.
+ */
+ public QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams) {
if (uri == null) {
throw new NullPointerException("uri");
}
if (charset == null) {
throw new NullPointerException("charset");
}
+ if (maxParams <= 0) {
+ throw new IllegalArgumentException(
+ "maxParams: " + maxParams + " (expected: a positive integer)");
+ }
// http://en.wikipedia.org/wiki/Query_string
this.uri = uri.replace(';', '&');
this.charset = charset;
+ this.maxParams = maxParams;
+ this.hasPath = hasPath;
}
/**
@@ -85,17 +135,31 @@ public class QueryStringDecoder {
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
- public QueryStringDecoder(URI uri, Charset charset){
+ public QueryStringDecoder(URI uri, Charset charset) {
+ this(uri, charset, DEFAULT_MAX_PARAMS);
+ }
+
+ /**
+ * Creates a new decoder that decodes the specified URI encoded in the
+ * specified charset.
+ */
+ public QueryStringDecoder(URI uri, Charset charset, int maxParams) {
if (uri == null) {
throw new NullPointerException("uri");
}
if (charset == null) {
throw new NullPointerException("charset");
}
+ if (maxParams <= 0) {
+ throw new IllegalArgumentException(
+ "maxParams: " + maxParams + " (expected: a positive integer)");
+ }
// http://en.wikipedia.org/wiki/Query_string
this.uri = uri.toASCIIString().replace(';', '&');
this.charset = charset;
+ this.maxParams = maxParams;
+ hasPath = false;
}
/**
@@ -103,11 +167,14 @@ public class QueryStringDecoder {
*/
public String getPath() {
if (path == null) {
+ if (!hasPath) {
+ return path = "";
+ }
+
int pathEndPos = uri.indexOf('?');
if (pathEndPos < 0) {
path = uri;
- }
- else {
+ } else {
return path = uri.substring(0, pathEndPos);
}
}
@@ -119,17 +186,25 @@ public class QueryStringDecoder {
*/
public Map> getParameters() {
if (params == null) {
- int pathLength = getPath().length();
- if (uri.length() == pathLength) {
- return Collections.emptyMap();
+ if (hasPath) {
+ int pathLength = getPath().length();
+ if (uri.length() == pathLength) {
+ return Collections.emptyMap();
+ }
+ decodeParams(uri.substring(pathLength + 1));
+ } else {
+ if (uri.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ decodeParams(uri);
}
- params = decodeParams(uri.substring(pathLength + 1));
}
return params;
}
- private Map> decodeParams(String s) {
- Map> params = new LinkedHashMap>();
+ private void decodeParams(String s) {
+ Map> params = this.params = new LinkedHashMap>();
+ nParams = 0;
String name = null;
int pos = 0; // Beginning of the unprocessed region
int i; // End of the unprocessed region
@@ -146,9 +221,13 @@ public class QueryStringDecoder {
// We haven't seen an `=' so far but moved forward.
// Must be a param of the form '&a&' so add it with
// an empty value.
- addParam(params, decodeComponent(s.substring(pos, i), charset), "");
+ if (!addParam(params, decodeComponent(s.substring(pos, i), charset), "")) {
+ return;
+ }
} else if (name != null) {
- addParam(params, name, decodeComponent(s.substring(pos, i), charset));
+ if (!addParam(params, name, decodeComponent(s.substring(pos, i), charset))) {
+ return;
+ }
name = null;
}
pos = i + 1;
@@ -157,15 +236,34 @@ public class QueryStringDecoder {
if (pos != i) { // Are there characters we haven't dealt with?
if (name == null) { // Yes and we haven't seen any `='.
- addParam(params, decodeComponent(s.substring(pos, i), charset), "");
+ if (!addParam(params, decodeComponent(s.substring(pos, i), charset), "")) {
+ return;
+ }
} else { // Yes and this must be the last value.
- addParam(params, name, decodeComponent(s.substring(pos, i), charset));
+ if (!addParam(params, name, decodeComponent(s.substring(pos, i), charset))) {
+ return;
+ }
}
} else if (name != null) { // Have we seen a name without value?
- addParam(params, name, "");
+ if (!addParam(params, name, "")) {
+ return;
+ }
+ }
+ }
+
+ private boolean addParam(Map> params, String name, String value) {
+ if (nParams >= maxParams) {
+ return false;
}
- return params;
+ List values = params.get(name);
+ if (values == null) {
+ values = new ArrayList(1); // Often there's only 1 value.
+ params.put(name, values);
+ }
+ values.add(value);
+ nParams ++;
+ return true;
}
/**
@@ -284,13 +382,4 @@ public class QueryStringDecoder {
return Character.MAX_VALUE;
}
}
-
- private static void addParam(Map> params, String name, String value) {
- List values = params.get(name);
- if (values == null) {
- values = new ArrayList(1); // Often there's only 1 value.
- params.put(name, values);
- }
- values.add(value);
- }
}
diff --git a/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java
similarity index 97%
rename from src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java
index e96f6e6cdf..cf1211c806 100644
--- a/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java
@@ -29,7 +29,7 @@ import java.util.List;
* This encoder is for one time use only. Create a new instance for each URI.
*
*
@@ -106,7 +106,7 @@ public class QueryStringEncoder {
sb.append(encodeComponent(param.name, charset));
sb.append("=");
sb.append(encodeComponent(param.value, charset));
- if(i != params.size() - 1) {
+ if (i != params.size() - 1) {
sb.append("&");
}
}
diff --git a/src/main/java/io/netty/handler/codec/http/package-info.java b/codec-http/src/main/java/io/netty/handler/codec/http/package-info.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/http/package-info.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/package-info.java
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
similarity index 85%
rename from src/main/java/io/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
index a05669647a..983879e980 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/BinaryWebSocketFrame.java
@@ -27,23 +27,21 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* Creates a new empty binary frame.
*/
public BinaryWebSocketFrame() {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
- * Creates a new binary frame with the specified binary data. The final
- * fragment flag is set to true.
+ * Creates a new binary frame with the specified binary data. The final fragment flag is set to true.
*
* @param binaryData
* the content of the frame.
*/
public BinaryWebSocketFrame(ChannelBuffer binaryData) {
- this.setBinaryData(binaryData);
+ setBinaryData(binaryData);
}
/**
- * Creates a new binary frame with the specified binary data and the final
- * fragment flag.
+ * Creates a new binary frame with the specified binary data and the final fragment flag.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
@@ -53,9 +51,9 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* the content of the frame.
*/
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setBinaryData(binaryData);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setBinaryData(binaryData);
}
@Override
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
similarity index 91%
rename from src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
index 67a61f8199..1f8ed46028 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java
@@ -26,7 +26,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* Creates a new empty close frame.
*/
public CloseWebSocketFrame() {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
@@ -38,8 +38,8 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* reserved bits used for protocol extensions
*/
public CloseWebSocketFrame(boolean finalFragment, int rsv) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
}
@Override
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
similarity index 77%
rename from src/main/java/io/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
index 6e647e011b..cb373dabe6 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/ContinuationWebSocketFrame.java
@@ -20,30 +20,28 @@ import io.netty.buffer.ChannelBuffers;
import io.netty.util.CharsetUtil;
/**
- * Web Socket continuation frame containing continuation text or binary data.
- * This is used for fragmented messages where the contents of a messages is
- * contained more than 1 frame.
+ * Web Socket continuation frame containing continuation text or binary data. This is used for fragmented messages where
+ * the contents of a messages is contained more than 1 frame.
*/
public class ContinuationWebSocketFrame extends WebSocketFrame {
- private String aggregatedText = null;
+ private String aggregatedText;
/**
* Creates a new empty continuation frame.
*/
public ContinuationWebSocketFrame() {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
- * Creates a new continuation frame with the specified binary data. The
- * final fragment flag is set to true.
+ * Creates a new continuation frame with the specified binary data. The final fragment flag is set to true.
*
* @param binaryData
* the content of the frame.
*/
public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
- this.setBinaryData(binaryData);
+ setBinaryData(binaryData);
}
/**
@@ -57,9 +55,9 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* the content of the frame.
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setBinaryData(binaryData);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setBinaryData(binaryData);
}
/**
@@ -72,13 +70,12 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* @param binaryData
* the content of the frame.
* @param aggregatedText
- * Aggregated text set by decoder on the final continuation frame
- * of a fragmented text message
+ * Aggregated text set by decoder on the final continuation frame of a fragmented text message
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setBinaryData(binaryData);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setBinaryData(binaryData);
this.aggregatedText = aggregatedText;
}
@@ -93,19 +90,19 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* text content of the frame.
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setText(text);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setText(text);
}
/**
* Returns the text data in this frame
*/
public String getText() {
- if (this.getBinaryData() == null) {
+ if (getBinaryData() == null) {
return null;
}
- return this.getBinaryData().toString(CharsetUtil.UTF_8);
+ return getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
@@ -116,9 +113,9 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
*/
public void setText(String text) {
if (text == null || text.isEmpty()) {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
- this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
@@ -128,8 +125,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
}
/**
- * Aggregated text returned by decoder on the final continuation frame of a
- * fragmented text message
+ * Aggregated text returned by decoder on the final continuation frame of a fragmented text message
*/
public String getAggregatedText() {
return aggregatedText;
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/PingWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
similarity index 87%
rename from src/main/java/io/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
index 5815faad97..0923e1fb47 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/PingWebSocketFrame.java
@@ -27,8 +27,8 @@ public class PingWebSocketFrame extends WebSocketFrame {
* Creates a new empty ping frame.
*/
public PingWebSocketFrame() {
- this.setFinalFragment(true);
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setFinalFragment(true);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
@@ -38,7 +38,7 @@ public class PingWebSocketFrame extends WebSocketFrame {
* the content of the frame.
*/
public PingWebSocketFrame(ChannelBuffer binaryData) {
- this.setBinaryData(binaryData);
+ setBinaryData(binaryData);
}
/**
@@ -52,9 +52,9 @@ public class PingWebSocketFrame extends WebSocketFrame {
* the content of the frame.
*/
public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setBinaryData(binaryData);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setBinaryData(binaryData);
}
@Override
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/PongWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
similarity index 89%
rename from src/main/java/io/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
index ee3fd861b4..8cd3eb51ab 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/PongWebSocketFrame.java
@@ -27,7 +27,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
* Creates a new empty pong frame.
*/
public PongWebSocketFrame() {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
@@ -37,7 +37,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
* the content of the frame.
*/
public PongWebSocketFrame(ChannelBuffer binaryData) {
- this.setBinaryData(binaryData);
+ setBinaryData(binaryData);
}
/**
@@ -51,9 +51,9 @@ public class PongWebSocketFrame extends WebSocketFrame {
* the content of the frame.
*/
public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setBinaryData(binaryData);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setBinaryData(binaryData);
}
@Override
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
similarity index 75%
rename from src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
index 18aeabc66e..a9f89e6518 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
@@ -28,38 +28,35 @@ public class TextWebSocketFrame extends WebSocketFrame {
* Creates a new empty text frame.
*/
public TextWebSocketFrame() {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
- * Creates a new text frame with the specified text string. The final
- * fragment flag is set to true.
+ * Creates a new text frame with the specified text string. The final fragment flag is set to true.
*
* @param text
* String to put in the frame
*/
public TextWebSocketFrame(String text) {
if (text == null || text.isEmpty()) {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
- this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
/**
- * Creates a new text frame with the specified binary data. The final
- * fragment flag is set to true.
+ * Creates a new text frame with the specified binary data. The final fragment flag is set to true.
*
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
*/
public TextWebSocketFrame(ChannelBuffer binaryData) {
- this.setBinaryData(binaryData);
+ setBinaryData(binaryData);
}
/**
- * Creates a new text frame with the specified text string. The final
- * fragment flag is set to true.
+ * Creates a new text frame with the specified text string. The final fragment flag is set to true.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
@@ -69,18 +66,17 @@ public class TextWebSocketFrame extends WebSocketFrame {
* String to put in the frame
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
if (text == null || text.isEmpty()) {
- this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
+ setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
- this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
/**
- * Creates a new text frame with the specified binary data. The final
- * fragment flag is set to true.
+ * Creates a new text frame with the specified binary data. The final fragment flag is set to true.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
@@ -90,19 +86,19 @@ public class TextWebSocketFrame extends WebSocketFrame {
* the content of the frame. Must be UTF-8 encoded
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
- this.setFinalFragment(finalFragment);
- this.setRsv(rsv);
- this.setBinaryData(binaryData);
+ setFinalFragment(finalFragment);
+ setRsv(rsv);
+ setBinaryData(binaryData);
}
/**
* Returns the text data in this frame
*/
public String getText() {
- if (this.getBinaryData() == null) {
+ if (getBinaryData() == null) {
return null;
}
- return this.getBinaryData().toString(CharsetUtil.UTF_8);
+ return getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
@@ -115,7 +111,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
if (text == null) {
throw new NullPointerException("text");
}
- this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
+ setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
@Override
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Exception.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Exception.java
similarity index 70%
rename from src/main/java/io/netty/handler/codec/http/websocketx/UTF8Exception.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Exception.java
index 9b1eaae803..9ff63cb9a9 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Exception.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Exception.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
/*
* Adaptation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
*
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Output.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Output.java
similarity index 56%
rename from src/main/java/io/netty/handler/codec/http/websocketx/UTF8Output.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Output.java
index 443afdfc99..eabccdc6b8 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Output.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/UTF8Output.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
/*
* Adaptation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
*
@@ -26,15 +41,23 @@ final class UTF8Output {
private static final int UTF8_ACCEPT = 0;
private static final int UTF8_REJECT = 12;
- private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
+ private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11,
+ 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
- private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12,
- 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 };
+ private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12 };
private int state = UTF8_ACCEPT;
- private int codep = 0;
+ private int codep;
private final StringBuilder stringBuilder;
@@ -52,7 +75,7 @@ final class UTF8Output {
public void write(int b) {
byte type = TYPES[b & 0xFF];
- codep = (state != UTF8_ACCEPT) ? (b & 0x3f) | (codep << 6) : (0xff >> type) & (b);
+ codep = state != UTF8_ACCEPT ? b & 0x3f | codep << 6 : 0xff >> type & b;
state = STATES[state + type];
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java
similarity index 86%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java
index a0b37ffe9a..69ca9776cf 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java
@@ -25,15 +25,15 @@ import io.netty.handler.codec.replay.VoidEnum;
/**
* Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s.
*
- * For the detailed instruction on adding add Web Socket support to your HTTP
- * server, take a look into the WebSocketServer example located in the
- * {@code io.netty.example.http.websocket} package.
+ * For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
+ * WebSocketServer example located in the {@code io.netty.example.http.websocket} package.
+ *
* @apiviz.landmark
* @apiviz.uses io.netty.handler.codec.http.websocket.WebSocketFrame
*/
public class WebSocket00FrameDecoder extends ReplayingDecoder {
- public static final int DEFAULT_MAX_FRAME_SIZE = 16384;
+ private static final int DEFAULT_MAX_FRAME_SIZE = 16384;
private final int maxFrameSize;
private boolean receivedClosingHandshake;
@@ -43,9 +43,8 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder {
}
/**
- * Creates a new instance of {@code WebSocketFrameDecoder} with the
- * specified {@code maxFrameSize}. If the client sends a frame size larger
- * than {@code maxFrameSize}, the channel will be closed.
+ * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
+ * sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
*
* @param maxFrameSize
* the maximum frame size to decode
@@ -55,7 +54,8 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder {
}
@Override
- protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state) throws Exception {
+ protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state)
+ throws Exception {
// Discard all data received if closing handshake was received before.
if (receivedClosingHandshake) {
@@ -70,7 +70,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder {
return decodeBinaryFrame(type, buffer);
} else {
// Decode a 0xff terminated UTF-8 string
- return decodeTextFrame(type, buffer);
+ return decodeTextFrame(buffer);
}
}
@@ -92,7 +92,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder {
}
} while ((b & 0x80) == 0x80);
- if (type == ((byte) 0xFF) && frameSize == 0) {
+ if (type == (byte) 0xFF && frameSize == 0) {
receivedClosingHandshake = true;
return new CloseWebSocketFrame();
}
@@ -100,7 +100,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder {
return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize));
}
- private WebSocketFrame decodeTextFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException {
+ private WebSocketFrame decodeTextFrame(ChannelBuffer buffer) throws TooLongFrameException {
int ridx = buffer.readerIndex();
int rbytes = actualReadableBytes();
int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java
similarity index 94%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java
index ca49ea4f04..ea0d36feb6 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java
@@ -24,9 +24,9 @@ import io.netty.handler.codec.oneone.OneToOneEncoder;
/**
* Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
*
- * For the detailed instruction on adding add Web Socket support to your HTTP
- * server, take a look into the WebSocketServer example located in the
- * {@code io.netty.example.http.websocket} package.
+ * For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
+ * WebSocketServer example located in the {@code io.netty.example.http.websocket} package.
+ *
* @apiviz.landmark
* @apiviz.uses io.netty.handler.codec.http.websocket.WebSocketFrame
*/
@@ -40,7 +40,8 @@ public class WebSocket00FrameEncoder extends OneToOneEncoder {
if (frame instanceof TextWebSocketFrame) {
// Text frame
ChannelBuffer data = frame.getBinaryData();
- ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), data.readableBytes() + 2);
+ ChannelBuffer encoded = channel.getConfig().getBufferFactory()
+ .getBuffer(data.order(), data.readableBytes() + 2);
encoded.writeByte((byte) 0x00);
encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
encoded.writeByte((byte) 0xFF);
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
similarity index 91%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
index 78fcd0a2c6..5bd7ea0038 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
// (BSD License: http://www.opensource.org/licenses/bsd-license)
//
// Copyright (c) 2011, Joe Walnes and contributors
@@ -50,9 +65,8 @@ import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
/**
- * Decodes a web socket frame from wire protocol version 8 format. This code was
- * forked from webbit and
- * modified.
+ * Decodes a web socket frame from wire protocol version 8 format. This code was forked from webbit and modified.
*/
public class WebSocket08FrameDecoder extends ReplayingDecoder {
@@ -65,20 +79,20 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder> 4;
- frameOpcode = (b & 0x0F);
+ frameOpcode = b & 0x0F;
if (logger.isDebugEnabled()) {
logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode);
@@ -128,14 +142,14 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder
- * Encodes a web socket frame into wire protocol version 8 format. This code was
- * forked from webbit and
- * modified.
+ * Encodes a web socket frame into wire protocol version 8 format. This code was forked from webbit and modified.
*
*/
public class WebSocket08FrameEncoder extends OneToOneEncoder {
@@ -67,14 +81,14 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA;
- private boolean maskPayload = false;
+ private final boolean maskPayload;
/**
* Constructor
*
* @param maskPayload
- * Web socket clients must set this to true to mask payload.
- * Server implementations must set this to false.
+ * Web socket clients must set this to true to mask payload. Server implementations must set this to
+ * false.
*/
public WebSocket08FrameEncoder(boolean maskPayload) {
this.maskPayload = maskPayload;
@@ -83,7 +97,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
- byte[] mask = null;
+ byte[] mask;
if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg;
@@ -112,44 +126,45 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
int length = data.readableBytes();
if (logger.isDebugEnabled()) {
- logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length);
+ logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length);
}
-
+
int b0 = 0;
if (frame.isFinalFragment()) {
- b0 |= (1 << 7);
+ b0 |= 1 << 7;
}
- b0 |= (frame.getRsv() % 8) << 4;
+ b0 |= frame.getRsv() % 8 << 4;
b0 |= opcode % 128;
ChannelBuffer header;
ChannelBuffer body;
if (opcode == OPCODE_PING && length > 125) {
- throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length);
+ throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
+ + length);
}
- int maskLength = this.maskPayload ? 4 : 0;
+ int maskLength = maskPayload ? 4 : 0;
if (length <= 125) {
header = ChannelBuffers.buffer(2 + maskLength);
header.writeByte(b0);
- byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length);
+ byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length);
header.writeByte(b);
} else if (length <= 0xFFFF) {
header = ChannelBuffers.buffer(4 + maskLength);
header.writeByte(b0);
- header.writeByte(this.maskPayload ? (0xFE) : 126);
- header.writeByte((length >>> 8) & 0xFF);
- header.writeByte((length) & 0xFF);
+ header.writeByte(maskPayload ? 0xFE : 126);
+ header.writeByte(length >>> 8 & 0xFF);
+ header.writeByte(length & 0xFF);
} else {
header = ChannelBuffers.buffer(10 + maskLength);
header.writeByte(b0);
- header.writeByte(this.maskPayload ? (0xFF) : 127);
+ header.writeByte(maskPayload ? 0xFF : 127);
header.writeLong(length);
}
// Write payload
- if (this.maskPayload) {
+ if (maskPayload) {
Integer random = (int) (Math.random() * Integer.MAX_VALUE);
mask = ByteBuffer.allocate(4).putInt(random).array();
header.writeBytes(mask);
@@ -170,4 +185,4 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
return msg;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java
similarity index 72%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java
index b98eb58fa0..6c592c686e 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameDecoder.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
// (BSD License: http://www.opensource.org/licenses/bsd-license)
//
// Copyright (c) 2011, Joe Walnes and contributors
@@ -39,8 +54,7 @@
package io.netty.handler.codec.http.websocketx;
/**
- * Decodes a web socket frame from wire protocol version 13 format.
- * V13 is essentially the same as V8.
+ * Decodes a web socket frame from wire protocol version 13 format. V13 is essentially the same as V8.
*/
public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
@@ -48,12 +62,12 @@ public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
* Constructor
*
* @param maskedPayload
- * Web socket servers must set this to true processed incoming
- * masked payload. Client implementations must set this to false.
+ * Web socket servers must set this to true processed incoming masked payload. Client implementations
+ * must set this to false.
* @param allowExtensions
* Flag to allow reserved extension bits to be used or not
*/
public WebSocket13FrameDecoder(boolean maskedPayload, boolean allowExtensions) {
- super(maskedPayload, allowExtensions);
+ super(maskedPayload, allowExtensions);
}
}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameEncoder.java
similarity index 73%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameEncoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameEncoder.java
index a435136639..f0328dc91e 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameEncoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket13FrameEncoder.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
// (BSD License: http://www.opensource.org/licenses/bsd-license)
//
// Copyright (c) 2011, Joe Walnes and contributors
@@ -38,11 +53,9 @@
package io.netty.handler.codec.http.websocketx;
-
/**
*
- * Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same
- * as V8.
+ * Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same as V8.
*
*/
public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder {
@@ -51,8 +64,8 @@ public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder {
* Constructor
*
* @param maskPayload
- * Web socket clients must set this to true to mask payload.
- * Server implementations must set this to false.
+ * Web socket clients must set this to true to mask payload. Server implementations must set this to
+ * false.
*/
public WebSocket13FrameEncoder(boolean maskPayload) {
super(maskPayload);
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java
new file mode 100644
index 0000000000..18ad9ec051
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import java.net.URI;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.handler.codec.base64.Base64;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.util.CharsetUtil;
+
+/**
+ * Base class for web socket client handshake implementations
+ */
+public abstract class WebSocketClientHandshaker {
+
+ private final URI webSocketUrl;
+
+ private final WebSocketVersion version;
+
+ private boolean handshakeComplete;
+
+ private final String expectedSubprotocol;
+
+ private String actualSubprotocol;
+
+ protected final Map customHeaders;
+
+ /**
+ * Base constructor
+ *
+ * @param webSocketUrl
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param version
+ * Version of web socket specification to use to connect to the server
+ * @param subprotocol
+ * Sub protocol request sent to the server.
+ * @param customHeaders
+ * Map of custom headers to add to the client request
+ */
+ public WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol,
+ Map customHeaders) {
+ this.webSocketUrl = webSocketUrl;
+ this.version = version;
+ expectedSubprotocol = subprotocol;
+ this.customHeaders = customHeaders;
+ }
+
+ /**
+ * Returns the URI to the web socket. e.g. "ws://myhost.com/path"
+ */
+ public URI getWebSocketUrl() {
+ return webSocketUrl;
+ }
+
+ /**
+ * Version of the web socket specification that is being used
+ */
+ public WebSocketVersion getVersion() {
+ return version;
+ }
+
+ /**
+ * Flag to indicate if the opening handshake is complete
+ */
+ public boolean isHandshakeComplete() {
+ return handshakeComplete;
+ }
+
+ protected void setHandshakeComplete() {
+ handshakeComplete = true;
+ }
+
+ /**
+ * Returns the sub protocol request sent to the server as specified in the constructor
+ */
+ public String getExpectedSubprotocol() {
+ return expectedSubprotocol;
+ }
+
+ /**
+ * Returns the sub protocol response and sent by the server. Only available after end of handshake.
+ */
+ public String getActualSubprotocol() {
+ return actualSubprotocol;
+ }
+
+ protected void setActualSubprotocol(String actualSubprotocol) {
+ this.actualSubprotocol = actualSubprotocol;
+ }
+
+ /**
+ * Begins the opening handshake
+ *
+ * @param channel
+ * Channel
+ */
+ public abstract ChannelFuture handshake(Channel channel);
+
+ /**
+ * Validates and finishes the opening handshake initiated by {@link #handshake}}.
+ *
+ * @param channel
+ * Channel
+ * @param response
+ * HTTP response containing the closing handshake details
+ */
+ public abstract void finishHandshake(Channel channel, HttpResponse response);
+}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
similarity index 75%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
index 3abbf8c201..d4d26e45a6 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
@@ -18,9 +18,11 @@ package io.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
+import java.util.Map;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
@@ -34,10 +36,9 @@ import io.netty.handler.codec.http.HttpVersion;
/**
*
*/
-public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
+public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
- private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker17.class);
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- private String expectedChallengeResponseString = null;
+ private String expectedChallengeResponseString;
private static final String protocol = null;
- private boolean allowExtensions = false;
+ private final boolean allowExtensions;
/**
- * Constructor specifying the destination web socket location and version to
- * initiate
+ * Constructor specifying the destination web socket location and version to initiate
*
* @param webSocketURL
- * URL for web socket communications. e.g
- * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
- * Version of web socket specification to use to connect to the
- * server
- * @param subProtocol
+ * Version of web socket specification to use to connect to the server
+ * @param subprotocol
* Sub protocol request sent to the server.
* @param allowExtensions
- * Allow extensions to be used in the reserved bits of the web
- * socket frame
+ * Allow extensions to be used in the reserved bits of the web socket frame
+ * @param customHeaders
+ * Map of custom headers to add to the client request
*/
- public WebSocketClientHandshaker17(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions) {
- super(webSocketURL, version, subProtocol);
+ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
+ boolean allowExtensions, Map customHeaders) {
+ super(webSocketURL, version, subprotocol, customHeaders);
this.allowExtensions = allowExtensions;
}
@@ -95,24 +95,25 @@ public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
* Channel into which we can write our request
*/
@Override
- public void performOpeningHandshake(Channel channel) {
+ public ChannelFuture handshake(Channel channel) {
// Get path
- URI wsURL = this.getWebSocketURL();
+ URI wsURL = getWebSocketUrl();
String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery();
}
// Get 16 bit nonce and base 64 encode it
- byte[] nonce = createRandomBytes(16);
- String key = base64Encode(nonce);
+ byte[] nonce = WebSocketUtil.randomBytes(16);
+ String key = WebSocketUtil.base64(nonce);
String acceptSeed = key + MAGIC_GUID;
- byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
- this.expectedChallengeResponseString = base64Encode(sha1);
+ byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
+ expectedChallengeResponseString = WebSocketUtil.base64(sha1);
if (logger.isDebugEnabled()) {
- logger.debug(String.format("HyBi17 Client Handshake key: %s. Expected response: %s.", key, this.expectedChallengeResponseString));
+ logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key,
+ expectedChallengeResponseString));
}
// Format request
@@ -127,9 +128,17 @@ public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
}
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
- channel.write(request);
+ if (customHeaders != null) {
+ for (String header : customHeaders.keySet()) {
+ request.addHeader(header, customHeaders.get(header));
+ }
+ }
+
+ ChannelFuture future = channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
+
+ return future;
}
/**
@@ -148,13 +157,12 @@ public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
* @param channel
* Channel
* @param response
- * HTTP response returned from the server for the request sent by
- * beginOpeningHandshake00().
+ * HTTP response returned from the server for the request sent by beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
- public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
- final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
+ public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
+ final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
@@ -162,22 +170,25 @@ public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
String upgrade = response.getHeader(Names.UPGRADE);
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
- throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE));
+ throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ + response.getHeader(Names.UPGRADE));
}
String connection = response.getHeader(Names.CONNECTION);
if (connection == null || !connection.equals(Values.UPGRADE)) {
- throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION));
+ throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ + response.getHeader(Names.CONNECTION));
}
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
- if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
- throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, this.expectedChallengeResponseString));
+ if (accept == null || !accept.equals(expectedChallengeResponseString)) {
+ throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
+ expectedChallengeResponseString));
}
- channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket13FrameDecoder(false, this.allowExtensions));
+ channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
+ new WebSocket13FrameDecoder(false, allowExtensions));
- this.setOpenningHandshakeCompleted(true);
+ setHandshakeComplete();
}
-
}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
similarity index 61%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
index e8738b5204..a199299411 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
@@ -16,6 +16,7 @@
package io.netty.handler.codec.http.websocketx;
import java.net.URI;
+import java.util.Map;
/**
* Instances the appropriate handshake class to use for clients
@@ -26,29 +27,28 @@ public class WebSocketClientHandshakerFactory {
* Instances a new handshaker
*
* @param webSocketURL
- * URL for web socket communications. e.g
- * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
- * Version of web socket specification to use to connect to the
- * server
- * @param subProtocol
- * Sub protocol request sent to the server. Null if no
- * sub-protocol support is required.
+ * Version of web socket specification to use to connect to the server
+ * @param subprotocol
+ * Sub protocol request sent to the server. Null if no sub-protocol support is required.
* @param allowExtensions
- * Allow extensions to be used in the reserved bits of the web
- * socket frame
+ * Allow extensions to be used in the reserved bits of the web socket frame
+ * @param customHeaders
+ * Custom HTTP headers to send during the handshake
* @throws WebSocketHandshakeException
*/
- public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
- if (version == WebSocketSpecificationVersion.V17) {
- return new WebSocketClientHandshaker17(webSocketURL, version, subProtocol, allowExtensions);
+ public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol,
+ boolean allowExtensions, Map customHeaders) throws WebSocketHandshakeException {
+ if (version == WebSocketVersion.V13) {
+ return new WebSocketClientHandshaker13(webSocketURL, version, subprotocol, allowExtensions, customHeaders);
}
- if (version == WebSocketSpecificationVersion.V10) {
- return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions);
+ if (version == WebSocketVersion.V08) {
+ return new WebSocketClientHandshaker08(webSocketURL, version, subprotocol, allowExtensions, customHeaders);
}
- if (version == WebSocketSpecificationVersion.V00) {
- return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol);
+ if (version == WebSocketVersion.V00) {
+ return new WebSocketClientHandshaker00(webSocketURL, version, subprotocol, customHeaders);
}
throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrame.java
similarity index 91%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrame.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrame.java
index fd48834cd7..9bad30d5c7 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrame.java
@@ -23,15 +23,15 @@ import io.netty.buffer.ChannelBuffer;
public abstract class WebSocketFrame {
/**
- * Flag to indicate if this frame is the final fragment in a message. The
- * first fragment (frame) may also be the final fragment.
+ * Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
+ * final fragment.
*/
private boolean finalFragment = true;
/**
* RSV1, RSV2, RSV3 used for extensions
*/
- private int rsv = 0;
+ private int rsv;
/**
* Contents of this frame
@@ -53,8 +53,8 @@ public abstract class WebSocketFrame {
}
/**
- * Flag to indicate if this frame is the final fragment in a message. The
- * first fragment (frame) may also be the final fragment.
+ * Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
+ * final fragment.
*/
public boolean isFinalFragment() {
return finalFragment;
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrameType.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrameType.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrameType.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketFrameType.java
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
similarity index 93%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
index 815d330fb1..459aa2dae6 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketHandshakeException.java
@@ -18,7 +18,7 @@ package io.netty.handler.codec.http.websocketx;
/**
* Exception during handshaking process
*/
-public class WebSocketHandshakeException extends Exception {
+public class WebSocketHandshakeException extends RuntimeException {
private static final long serialVersionUID = 1L;
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java
new file mode 100644
index 0000000000..5cbe843429
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.handler.codec.base64.Base64;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.util.CharsetUtil;
+
+/**
+ * Base class for server side web socket opening and closing handshakes
+ */
+public abstract class WebSocketServerHandshaker {
+
+ private final String webSocketUrl;
+
+ private final String[] subprotocols;
+
+ private final WebSocketVersion version;
+
+ /**
+ * Constructor specifying the destination web socket location
+ *
+ * @param version
+ * the protocol version
+ * @param webSocketUrl
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param subprotocols
+ * CSV of supported protocols. Null if sub protocols not supported.
+ */
+ protected WebSocketServerHandshaker(
+ WebSocketVersion version, String webSocketUrl, String subprotocols) {
+ this.version = version;
+ this.webSocketUrl = webSocketUrl;
+ if (subprotocols != null) {
+ String[] subprotocolArray = subprotocols.split(",");
+ for (int i = 0; i < subprotocolArray.length; i++) {
+ subprotocolArray[i] = subprotocolArray[i].trim();
+ }
+ this.subprotocols = subprotocolArray;
+ } else {
+ this.subprotocols = new String[0];
+ }
+ }
+
+ /**
+ * Returns the URL of the web socket
+ */
+ public String getWebSocketUrl() {
+ return webSocketUrl;
+ }
+
+ /**
+ * Returns the CSV of supported sub protocols
+ */
+ public Set getSubprotocols() {
+ Set ret = new LinkedHashSet();
+ for (String p: this.subprotocols) {
+ ret.add(p);
+ }
+ return ret;
+ }
+
+ /**
+ * Returns the version of the specification being supported
+ */
+ public WebSocketVersion getVersion() {
+ return version;
+ }
+
+ /**
+ * Performs the opening handshake
+ *
+ * @param channel
+ * Channel
+ * @param req
+ * HTTP Request
+ */
+ public abstract ChannelFuture handshake(Channel channel, HttpRequest req);
+
+ /**
+ * Performs the closing handshake
+ *
+ * @param channel
+ * Channel
+ * @param frame
+ * Closing Frame that was received
+ */
+ public abstract ChannelFuture close(Channel channel, CloseWebSocketFrame frame);
+
+ /**
+ * Selects the first matching supported sub protocol
+ *
+ * @param requestedSubprotocols
+ * CSV of protocols to be supported. e.g. "chat, superchat"
+ * @return First matching supported sub protocol. Null if not found.
+ */
+ protected String selectSubprotocol(String requestedSubprotocols) {
+ if (requestedSubprotocols == null || subprotocols.length == 0) {
+ return null;
+ }
+
+ String[] requesteSubprotocolArray = requestedSubprotocols.split(",");
+ for (String p: requesteSubprotocolArray) {
+ String requestedSubprotocol = p.trim();
+
+ for (String supportedSubprotocol: subprotocols) {
+ if (requestedSubprotocol.equals(supportedSubprotocol)) {
+ return requestedSubprotocol;
+ }
+ }
+ }
+
+ // No match found
+ return null;
+ }
+}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
similarity index 67%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
index 646037c732..4af7445fd5 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java
@@ -15,43 +15,32 @@
*/
package io.netty.handler.codec.http.websocketx;
-import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
-import static io.netty.handler.codec.http.HttpHeaders.Names.ORIGIN;
-import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1;
-import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
-import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
-import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
-import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
-import static io.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION;
-import static io.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN;
-import static io.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL;
-import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
-import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
-
-import java.security.NoSuchAlgorithmException;
+import static io.netty.handler.codec.http.HttpHeaders.Names.*;
+import static io.netty.handler.codec.http.HttpHeaders.Values.*;
+import static io.netty.handler.codec.http.HttpVersion.*;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
-import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.DefaultHttpResponse;
+import io.netty.handler.codec.http.HttpChunkAggregator;
+import io.netty.handler.codec.http.HttpHeaders.Names;
+import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpHeaders.Names;
-import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
/**
*
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
@@ -63,49 +52,46 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
/**
* Constructor specifying the destination web socket location
- *
+ *
* @param webSocketURL
- * URL for web socket communications. e.g
- * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
- * @param subProtocols
+ * @param subprotocols
* CSV of supported protocols
*/
- public WebSocketServerHandshaker00(String webSocketURL, String subProtocols) {
- super(webSocketURL, subProtocols);
+ public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) {
+ super(WebSocketVersion.V00, webSocketURL, subprotocols);
}
/**
*
* Handle the web socket handshake for the web socket specification HyBi
- * version 0 and lower. This standard is really a rehash of hixie-76 and hixie-75.
+ * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0 and lower. This standard
+ * is really a rehash of hixie-76 and
+ * hixie-75.
*
*/
-public class WebSocketServerHandshaker10 extends WebSocketServerHandshaker {
+public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
- private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker10.class);
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker08.class);
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- private boolean allowExtensions = false;
+ private final boolean allowExtensions;
/**
* Constructor specifying the destination web socket location
- *
+ *
* @param webSocketURL
- * URL for web socket communications. e.g
- * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
- * @param subProtocols
+ * @param subprotocols
* CSV of supported protocols
* @param allowExtensions
- * Allow extensions to be used in the reserved bits of the web
- * socket frame
+ * Allow extensions to be used in the reserved bits of the web socket frame
*/
- public WebSocketServerHandshaker10(String webSocketURL, String subProtocols, boolean allowExtensions) {
- super(webSocketURL, subProtocols);
+ public WebSocketServerHandshaker08(String webSocketURL, String subprotocols, boolean allowExtensions) {
+ super(WebSocketVersion.V08, webSocketURL, subprotocols);
this.allowExtensions = allowExtensions;
}
/**
*
* Handle the web socket handshake for the web socket specification HyBi
- * version 8 to 10. Version 8, 9 and 10 share the same wire protocol.
+ * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi version 8 to 10. Version 8, 9 and
+ * 10 share the same wire protocol.
*
- *
+ *
*
* Browser request to the server:
*
- *
+ *
*
* GET /chat HTTP/1.1
* Host: server.example.com
@@ -91,11 +86,11 @@ public class WebSocketServerHandshaker10 extends WebSocketServerHandshaker {
* Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 8
*
*/
-public class WebSocketServerHandshaker17 extends WebSocketServerHandshaker {
+public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
- private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker17.class);
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker13.class);
- public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- private boolean allowExtensions = false;
+ private final boolean allowExtensions;
/**
* Constructor specifying the destination web socket location
- *
+ *
* @param webSocketURL
- * URL for web socket communications. e.g
- * "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
- * @param subProtocols
+ * @param subprotocols
* CSV of supported protocols
* @param allowExtensions
- * Allow extensions to be used in the reserved bits of the web
- * socket frame
+ * Allow extensions to be used in the reserved bits of the web socket frame
*/
- public WebSocketServerHandshaker17(String webSocketURL, String subProtocols, boolean allowExtensions) {
- super(webSocketURL, subProtocols);
+ public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions) {
+ super(WebSocketVersion.V13, webSocketURL, subprotocols);
this.allowExtensions = allowExtensions;
}
/**
*
* Handle the web socket handshake for the web socket specification HyBi
- * versions 13-17. Versions 13-17 share the same wire protocol.
+ * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17. Versions 13-17
+ * share the same wire protocol.
*
- *
+ *
*
* Browser request to the server:
*
- *
+ *
*
* GET /chat HTTP/1.1
* Host: server.example.com
@@ -91,11 +87,11 @@ public class WebSocketServerHandshaker17 extends WebSocketServerHandshaker {
* Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 13
*
- *
+ *
* @param channel
* Channel
* @param req
* HTTP request
- * @throws NoSuchAlgorithmException
*/
@Override
- public void performOpeningHandshake(Channel channel, HttpRequest req) {
+ public ChannelFuture handshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Channel %s web socket spec version 17 handshake", channel.getId()));
+ logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.getId()));
}
- HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
- this.setVersion(WebSocketSpecificationVersion.V17);
+ HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) {
- res.setStatus(HttpResponseStatus.BAD_REQUEST);
- return;
+ throw new WebSocketHandshakeException("not a WebSocket request: missing key");
}
- String acceptSeed = key + WEBSOCKET_17_ACCEPT_GUID;
- byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
- String accept = base64Encode(sha1);
+ String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
+ byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
+ String accept = WebSocketUtil.base64(sha1);
if (logger.isDebugEnabled()) {
- logger.debug(String.format("HyBi17 Server Handshake key: %s. Response: %s.", key, accept));
+ logger.debug(String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept));
}
- res.setStatus(new HttpResponseStatus(101, "Switching Protocols"));
+ res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS);
res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
res.addHeader(Names.CONNECTION, Names.UPGRADE);
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) {
- res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
+ res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
}
- channel.write(res);
+ ChannelFuture future = channel.write(res);
// Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline();
- p.remove(HttpChunkAggregator.class);
- p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, this.allowExtensions));
+ if (p.get(HttpChunkAggregator.class) != null) {
+ p.remove(HttpChunkAggregator.class);
+ }
+
+ p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
+ return future;
}
/**
* Echo back the closing frame and close the connection
- *
+ *
* @param channel
* Channel
* @param frame
* Web Socket frame that was received
*/
@Override
- public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
+ public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE);
+ return f;
}
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java
new file mode 100644
index 0000000000..4428bc5ea0
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.DefaultHttpResponse;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.HttpHeaders.Names;
+
+/**
+ * Instances the appropriate handshake class to use for servers
+ */
+public class WebSocketServerHandshakerFactory {
+
+ private final String webSocketURL;
+
+ private final String subprotocols;
+
+ private final boolean allowExtensions;
+
+ /**
+ * Constructor specifying the destination web socket location
+ *
+ * @param webSocketURL
+ * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+ * sent to this URL.
+ * @param subprotocols
+ * CSV of supported protocols. Null if sub protocols not supported.
+ * @param allowExtensions
+ * Allow extensions to be used in the reserved bits of the web socket frame
+ */
+ public WebSocketServerHandshakerFactory(String webSocketURL, String subprotocols, boolean allowExtensions) {
+ this.webSocketURL = webSocketURL;
+ this.subprotocols = subprotocols;
+ this.allowExtensions = allowExtensions;
+ }
+
+ /**
+ * Instances a new handshaker
+ *
+ * @return A new WebSocketServerHandshaker for the requested web socket version. Null if web socket version is not
+ * supported.
+ */
+ public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
+
+ String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
+ if (version != null) {
+ if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
+ // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
+ return new WebSocketServerHandshaker13(webSocketURL, subprotocols, allowExtensions);
+ } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
+ // Version 8 of the wire protocol - version 10 of the draft hybi specification.
+ return new WebSocketServerHandshaker08(webSocketURL, subprotocols, allowExtensions);
+ } else {
+ return null;
+ }
+ } else {
+ // Assume version 00 where version header was not specified
+ return new WebSocketServerHandshaker00(webSocketURL, subprotocols);
+ }
+ }
+
+ /**
+ * Return that we need cannot not support the web socket version
+ *
+ * @param channel
+ * Channel
+ */
+ public void sendUnsupportedWebSocketVersionResponse(Channel channel) {
+ HttpResponse res = new DefaultHttpResponse(
+ HttpVersion.HTTP_1_1,
+ HttpResponseStatus.SWITCHING_PROTOCOLS);
+ res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
+ res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue());
+ channel.write(res);
+ }
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java
new file mode 100644
index 0000000000..ffe7efcfbd
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.handler.codec.base64.Base64;
+import io.netty.util.CharsetUtil;
+
+/**
+ * TODO Document me.
+ */
+final class WebSocketUtil {
+
+ /**
+ * Performs an MD5 hash
+ *
+ * @param bytes
+ * Data to hash
+ * @return Hashed data
+ */
+ static byte[] md5(byte[] bytes) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ return md.digest(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("MD5 not supported on this platform");
+ }
+ }
+
+ /**
+ * Performs an SHA-1 hash
+ *
+ * @param bytes
+ * Data to hash
+ * @return Hashed data
+ */
+ static byte[] sha1(byte[] bytes) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA1");
+ return md.digest(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("SHA-1 not supported on this platform");
+ }
+ }
+
+ /**
+ * Base 64 encoding
+ *
+ * @param bytes
+ * Bytes to encode
+ * @return encoded string
+ */
+ static String base64(byte[] bytes) {
+ ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
+ return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
+ }
+
+ /**
+ * Creates some random bytes
+ *
+ * @param size
+ * Number of random bytes to create
+ * @return random bytes
+ */
+ static byte[] randomBytes(int size) {
+ byte[] bytes = new byte[size];
+
+ for (int i = 0; i < size; i++) {
+ bytes[i] = (byte) randomNumber(0, 255);
+ }
+
+ return bytes;
+ }
+
+ /**
+ * Generates a random number
+ *
+ * @param min
+ * Minimum value
+ * @param max
+ * Maximum value
+ * @return Random number
+ */
+ static int randomNumber(int min, int max) {
+ return (int) (Math.random() * max + min);
+ }
+
+
+ private WebSocketUtil() {
+ // Unused
+ }
+}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketSpecificationVersion.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketVersion.java
similarity index 56%
rename from src/main/java/io/netty/handler/codec/http/websocketx/WebSocketSpecificationVersion.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketVersion.java
index 65f2d03538..b538e7e805 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketSpecificationVersion.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketVersion.java
@@ -20,32 +20,43 @@ package io.netty.handler.codec.http.websocketx;
* Versions of the web socket specification.
*
*
- * A specification is tied to one wire protocol version but a protocol version
- * may have use by more than 1 version of the specification.
+ * A specification is tied to one wire protocol version but a protocol version may have use by more than 1 version of
+ * the specification.
*
*/
-public enum WebSocketSpecificationVersion {
+public enum WebSocketVersion {
UNKNOWN,
/**
- * draft-ietf-hybi-thewebsocketprotocol- 00.
*/
V00,
/**
- * draft-ietf-hybi-thewebsocketprotocol- 10
*/
- V10,
-
+ V08,
+
/**
- * draft-ietf-hybi-thewebsocketprotocol- 17
+ * RFC 6455. This was originally draft-ietf-hybi-thewebsocketprotocol-
+ * 17
*/
- V17
-
+ V13;
+
+ /**
+ * @return Value for HTTP Header 'Sec-WebSocket-Version'
+ */
+ public String toHttpHeaderValue() {
+ if (this == V00) {
+ return "0";
+ } else if (this == V08) {
+ return "8";
+ } else if (this == V13) {
+ return "13";
+ }
+ throw new IllegalStateException("Unknown web socket version: " + this);
+ }
}
diff --git a/src/main/java/io/netty/handler/codec/http/websocketx/package-info.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/package-info.java
similarity index 81%
rename from src/main/java/io/netty/handler/codec/http/websocketx/package-info.java
rename to codec-http/src/main/java/io/netty/handler/codec/http/websocketx/package-info.java
index 5790d0061f..c0f214d812 100644
--- a/src/main/java/io/netty/handler/codec/http/websocketx/package-info.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/package-info.java
@@ -23,15 +23,10 @@
*
- * In the future, as the specification develops, more versions will be supported.
- * This contrasts the io.netty.handler.codec.http.websocket package which only
- * supports draft-ietf-hybi-thewebsocketprotocol-00.
- *
- *
* For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the WebSocketServerX example located in the
* {@code io.netty.example.http.websocket} package.
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java
similarity index 99%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java
index 7e0e7e9159..77ae287deb 100644
--- a/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java
@@ -27,7 +27,7 @@ public final class RtspHeaders {
/**
* Standard RTSP header names.
- */
+ */
public static final class Names {
/**
* {@code "Accept"}
@@ -212,7 +212,7 @@ public final class RtspHeaders {
/**
* Standard RTSP header values.
- */
+ */
public static final class Values {
/**
* {@code "append"}
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspMessageDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMessageDecoder.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspMessageDecoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMessageDecoder.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspMessageEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMessageEncoder.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspMessageEncoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMessageEncoder.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspRequestDecoder.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspRequestDecoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspRequestDecoder.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspRequestEncoder.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspRequestEncoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspRequestEncoder.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspResponseDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspResponseDecoder.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspResponseDecoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspResponseDecoder.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspResponseEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspResponseEncoder.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspResponseEncoder.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspResponseEncoder.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspResponseStatuses.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspResponseStatuses.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspResponseStatuses.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspResponseStatuses.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/RtspVersions.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspVersions.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/RtspVersions.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspVersions.java
diff --git a/src/main/java/io/netty/handler/codec/rtsp/package-info.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/package-info.java
similarity index 100%
rename from src/main/java/io/netty/handler/codec/rtsp/package-info.java
rename to codec-http/src/main/java/io/netty/handler/codec/rtsp/package-info.java
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java
new file mode 100644
index 0000000000..8d2bfa2070
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdyDataFrame} implementation.
+ */
+public class DefaultSpdyDataFrame implements SpdyDataFrame {
+
+ private int streamID;
+ private boolean last;
+ private boolean compressed;
+ private ChannelBuffer data = ChannelBuffers.EMPTY_BUFFER;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param streamID the Stream-ID of this frame
+ */
+ public DefaultSpdyDataFrame(int streamID) {
+ setStreamID(streamID);
+ }
+
+ public int getStreamID() {
+ return streamID;
+ }
+
+ public void setStreamID(int streamID) {
+ if (streamID <= 0) {
+ throw new IllegalArgumentException(
+ "Stream-ID must be positive: " + streamID);
+ }
+ this.streamID = streamID;
+ }
+
+ public boolean isLast() {
+ return last;
+ }
+
+ public void setLast(boolean last) {
+ this.last = last;
+ }
+
+ public boolean isCompressed() {
+ return compressed;
+ }
+
+ public void setCompressed(boolean compressed) {
+ this.compressed = compressed;
+ }
+
+ public ChannelBuffer getData() {
+ return data;
+ }
+
+ public void setData(ChannelBuffer data) {
+ if (data == null) {
+ data = ChannelBuffers.EMPTY_BUFFER;
+ }
+ if (data.readableBytes() > SpdyCodecUtil.SPDY_MAX_LENGTH) {
+ throw new IllegalArgumentException("data payload cannot exceed "
+ + SpdyCodecUtil.SPDY_MAX_LENGTH + " bytes");
+ }
+ this.data = data;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append("(last: ");
+ buf.append(isLast());
+ buf.append("; compressed: ");
+ buf.append(isCompressed());
+ buf.append(')');
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Stream-ID = ");
+ buf.append(streamID);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Size = ");
+ buf.append(data.readableBytes());
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java
new file mode 100644
index 0000000000..386eb7f750
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdyGoAwayFrame} implementation.
+ */
+public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
+
+ private int lastGoodStreamID;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param lastGoodStreamID the Last-good-stream-ID of this frame
+ */
+ public DefaultSpdyGoAwayFrame(int lastGoodStreamID) {
+ setLastGoodStreamID(lastGoodStreamID);
+ }
+
+ public int getLastGoodStreamID() {
+ return lastGoodStreamID;
+ }
+
+ public void setLastGoodStreamID(int lastGoodStreamID) {
+ if (lastGoodStreamID < 0) {
+ throw new IllegalArgumentException("Last-good-stream-ID"
+ + " cannot be negative: " + lastGoodStreamID);
+ }
+ this.lastGoodStreamID = lastGoodStreamID;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Last-good-stream-ID = ");
+ buf.append(lastGoodStreamID);
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaderBlock.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaderBlock.java
new file mode 100644
index 0000000000..4049ef94a6
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaderBlock.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdyHeaderBlock} implementation.
+ */
+public class DefaultSpdyHeaderBlock implements SpdyHeaderBlock {
+
+ private boolean invalid;
+ private final SpdyHeaders headers = new SpdyHeaders();
+
+ /**
+ * Creates a new instance.
+ */
+ protected DefaultSpdyHeaderBlock() {
+ }
+
+ public boolean isInvalid() {
+ return invalid;
+ }
+
+ public void setInvalid() {
+ this.invalid = true;
+ }
+
+ public void addHeader(final String name, final Object value) {
+ headers.addHeader(name, value);
+ }
+
+ public void setHeader(final String name, final Object value) {
+ headers.setHeader(name, value);
+ }
+
+ public void setHeader(final String name, final Iterable> values) {
+ headers.setHeader(name, values);
+ }
+
+ public void removeHeader(final String name) {
+ headers.removeHeader(name);
+ }
+
+ public void clearHeaders() {
+ headers.clearHeaders();
+ }
+
+ public String getHeader(final String name) {
+ return headers.getHeader(name);
+ }
+
+ public List getHeaders(final String name) {
+ return headers.getHeaders(name);
+ }
+
+ public List> getHeaders() {
+ return headers.getHeaders();
+ }
+
+ public boolean containsHeader(final String name) {
+ return headers.containsHeader(name);
+ }
+
+ public Set getHeaderNames() {
+ return headers.getHeaderNames();
+ }
+
+ protected void appendHeaders(StringBuilder buf) {
+ for (Map.Entry e: getHeaders()) {
+ buf.append(" ");
+ buf.append(e.getKey());
+ buf.append(": ");
+ buf.append(e.getValue());
+ buf.append(StringUtil.NEWLINE);
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java
new file mode 100644
index 0000000000..f0e8bc9030
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdyHeadersFrame} implementation.
+ */
+public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
+ implements SpdyHeadersFrame {
+
+ private int streamID;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param streamID the Stream-ID of this frame
+ */
+ public DefaultSpdyHeadersFrame(int streamID) {
+ super();
+ setStreamID(streamID);
+ }
+
+ public int getStreamID() {
+ return streamID;
+ }
+
+ public void setStreamID(int streamID) {
+ if (streamID <= 0) {
+ throw new IllegalArgumentException(
+ "Stream-ID must be positive: " + streamID);
+ }
+ this.streamID = streamID;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Stream-ID = ");
+ buf.append(streamID);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Headers:");
+ buf.append(StringUtil.NEWLINE);
+ appendHeaders(buf);
+
+ // Remove the last newline.
+ buf.setLength(buf.length() - StringUtil.NEWLINE.length());
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java
new file mode 100644
index 0000000000..a976baf304
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * The default {@link SpdyNoOpFrame} implementation.
+ */
+public class DefaultSpdyNoOpFrame implements SpdyNoOpFrame {
+
+ /**
+ * Creates a new instance.
+ */
+ public DefaultSpdyNoOpFrame() {
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyPingFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyPingFrame.java
new file mode 100644
index 0000000000..ae0119dd68
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyPingFrame.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdyPingFrame} implementation.
+ */
+public class DefaultSpdyPingFrame implements SpdyPingFrame {
+
+ private int ID;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param ID the unique ID of this frame
+ */
+ public DefaultSpdyPingFrame(int ID) {
+ setID(ID);
+ }
+
+ public int getID() {
+ return ID;
+ }
+
+ public void setID(int ID) {
+ this.ID = ID;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> ID = ");
+ buf.append(ID);
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyRstStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyRstStreamFrame.java
new file mode 100644
index 0000000000..3273eed153
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyRstStreamFrame.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdyRstStreamFrame} implementation.
+ */
+public class DefaultSpdyRstStreamFrame implements SpdyRstStreamFrame {
+
+ private int streamID;
+ private SpdyStreamStatus status;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param streamID the Stream-ID of this frame
+ * @param statusCode the Status code of this frame
+ */
+ public DefaultSpdyRstStreamFrame(int streamID, int statusCode) {
+ this(streamID, SpdyStreamStatus.valueOf(statusCode));
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param streamID the Stream-ID of this frame
+ * @param status the status of this frame
+ */
+ public DefaultSpdyRstStreamFrame(int streamID, SpdyStreamStatus status) {
+ setStreamID(streamID);
+ setStatus(status);
+ }
+
+ public int getStreamID() {
+ return streamID;
+ }
+
+ public void setStreamID(int streamID) {
+ if (streamID <= 0) {
+ throw new IllegalArgumentException(
+ "Stream-ID must be positive: " + streamID);
+ }
+ this.streamID = streamID;
+ }
+
+ public SpdyStreamStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(SpdyStreamStatus status) {
+ this.status = status;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Stream-ID = ");
+ buf.append(streamID);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Status: ");
+ buf.append(status.toString());
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java
new file mode 100644
index 0000000000..1cb20818e4
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdySettingsFrame} implementation.
+ */
+public class DefaultSpdySettingsFrame implements SpdySettingsFrame {
+
+ private boolean clear;
+ private final Map settingsMap = new TreeMap();
+
+ /**
+ * Creates a new instance.
+ */
+ public DefaultSpdySettingsFrame() {
+ }
+
+ public Set getIDs() {
+ return settingsMap.keySet();
+ }
+
+ public boolean isSet(int ID) {
+ Integer key = new Integer(ID);
+ return settingsMap.containsKey(key);
+ }
+
+ public int getValue(int ID) {
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ return settingsMap.get(key).getValue();
+ } else {
+ return -1;
+ }
+ }
+
+ public void setValue(int ID, int value) {
+ setValue(ID, value, false, false);
+ }
+
+ public void setValue(int ID, int value, boolean persistValue, boolean persisted) {
+ if (ID <= 0 || ID > SpdyCodecUtil.SPDY_SETTINGS_MAX_ID) {
+ throw new IllegalArgumentException("Setting ID is not valid: " + ID);
+ }
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ Setting setting = settingsMap.get(key);
+ setting.setValue(value);
+ setting.setPersist(persistValue);
+ setting.setPersisted(persisted);
+ } else {
+ settingsMap.put(key, new Setting(value, persistValue, persisted));
+ }
+ }
+
+ public void removeValue(int ID) {
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ settingsMap.remove(key);
+ }
+ }
+
+ public boolean persistValue(int ID) {
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ return settingsMap.get(key).getPersist();
+ } else {
+ return false;
+ }
+ }
+
+ public void setPersistValue(int ID, boolean persistValue) {
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ settingsMap.get(key).setPersist(persistValue);
+ }
+ }
+
+ public boolean isPersisted(int ID) {
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ return settingsMap.get(key).getPersisted();
+ } else {
+ return false;
+ }
+ }
+
+ public void setPersisted(int ID, boolean persisted) {
+ Integer key = new Integer(ID);
+ if (settingsMap.containsKey(key)) {
+ settingsMap.get(key).setPersisted(persisted);
+ }
+ }
+
+ public boolean clearPreviouslyPersistedSettings() {
+ return clear;
+ }
+
+ public void setClearPreviouslyPersistedSettings(boolean clear) {
+ this.clear = clear;
+ }
+
+ private Set> getSettings() {
+ return settingsMap.entrySet();
+ }
+
+ private void appendSettings(StringBuilder buf) {
+ for (Map.Entry e: getSettings()) {
+ Setting setting = e.getValue();
+ buf.append("--> ");
+ buf.append(e.getKey().toString());
+ buf.append(":");
+ buf.append(setting.getValue());
+ buf.append(" (persist value: ");
+ buf.append(setting.getPersist());
+ buf.append("; persisted: ");
+ buf.append(setting.getPersisted());
+ buf.append(')');
+ buf.append(StringUtil.NEWLINE);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append(StringUtil.NEWLINE);
+ appendSettings(buf);
+ buf.setLength(buf.length() - StringUtil.NEWLINE.length());
+ return buf.toString();
+ }
+
+ private static final class Setting {
+
+ private int value;
+ private boolean persist;
+ private boolean persisted;
+
+ public Setting(int value, boolean persist, boolean persisted) {
+ this.value = value;
+ this.persist = persist;
+ this.persisted = persisted;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+
+ public boolean getPersist() {
+ return persist;
+ }
+
+ public void setPersist(boolean persist) {
+ this.persist = persist;
+ }
+
+ public boolean getPersisted() {
+ return persisted;
+ }
+
+ public void setPersisted(boolean persisted) {
+ this.persisted = persisted;
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java
new file mode 100644
index 0000000000..dba75a7f37
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdySynReplyFrame} implementation.
+ */
+public class DefaultSpdySynReplyFrame extends DefaultSpdyHeaderBlock
+ implements SpdySynReplyFrame {
+
+ private int streamID;
+ private boolean last;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param streamID the Stream-ID of this frame
+ */
+ public DefaultSpdySynReplyFrame(int streamID) {
+ super();
+ setStreamID(streamID);
+ }
+
+ public int getStreamID() {
+ return streamID;
+ }
+
+ public void setStreamID(int streamID) {
+ if (streamID <= 0) {
+ throw new IllegalArgumentException(
+ "Stream-ID must be positive: " + streamID);
+ }
+ this.streamID = streamID;
+ }
+
+ public boolean isLast() {
+ return last;
+ }
+
+ public void setLast(boolean last) {
+ this.last = last;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append("(last: ");
+ buf.append(isLast());
+ buf.append(')');
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Stream-ID = ");
+ buf.append(streamID);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Headers:");
+ buf.append(StringUtil.NEWLINE);
+ appendHeaders(buf);
+
+ // Remove the last newline.
+ buf.setLength(buf.length() - StringUtil.NEWLINE.length());
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java
new file mode 100644
index 0000000000..95d1d9fe1a
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.util.internal.StringUtil;
+
+/**
+ * The default {@link SpdySynStreamFrame} implementation.
+ */
+public class DefaultSpdySynStreamFrame extends DefaultSpdyHeaderBlock
+ implements SpdySynStreamFrame {
+
+ private int streamID;
+ private int associatedToStreamID;
+ private byte priority;
+ private boolean last;
+ private boolean unidirectional;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param streamID the Stream-ID of this frame
+ * @param associatedToStreamID the Associated-To-Stream-ID of this frame
+ * @param priority the priority of the stream
+ */
+ public DefaultSpdySynStreamFrame(
+ int streamID, int associatedToStreamID, byte priority) {
+ super();
+ setStreamID(streamID);
+ setAssociatedToStreamID(associatedToStreamID);
+ setPriority(priority);
+ }
+
+ public int getStreamID() {
+ return streamID;
+ }
+
+ public void setStreamID(int streamID) {
+ if (streamID <= 0) {
+ throw new IllegalArgumentException(
+ "Stream-ID must be positive: " + streamID);
+ }
+ this.streamID = streamID;
+ }
+
+ public int getAssociatedToStreamID() {
+ return associatedToStreamID;
+ }
+
+ public void setAssociatedToStreamID(int associatedToStreamID) {
+ if (associatedToStreamID < 0) {
+ throw new IllegalArgumentException(
+ "Associated-To-Stream-ID cannot be negative: " +
+ associatedToStreamID);
+ }
+ this.associatedToStreamID = associatedToStreamID;
+ }
+
+ public byte getPriority() {
+ return priority;
+ }
+
+ public void setPriority(byte priority) {
+ if (priority < 0 || priority > 3) {
+ throw new IllegalArgumentException(
+ "Priortiy must be between 0 and 3 inclusive: " + priority);
+ }
+ this.priority = priority;
+ }
+
+ public boolean isLast() {
+ return last;
+ }
+
+ public void setLast(boolean last) {
+ this.last = last;
+ }
+
+ public boolean isUnidirectional() {
+ return unidirectional;
+ }
+
+ public void setUnidirectional(boolean unidirectional) {
+ this.unidirectional = unidirectional;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getClass().getSimpleName());
+ buf.append("(last: ");
+ buf.append(isLast());
+ buf.append("; unidirectional: ");
+ buf.append(isUnidirectional());
+ buf.append(')');
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Stream-ID = ");
+ buf.append(streamID);
+ buf.append(StringUtil.NEWLINE);
+ if (associatedToStreamID != 0) {
+ buf.append("--> Associated-To-Stream-ID = ");
+ buf.append(associatedToStreamID);
+ buf.append(StringUtil.NEWLINE);
+ }
+ buf.append("--> Priority = ");
+ buf.append(priority);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("--> Headers:");
+ buf.append(StringUtil.NEWLINE);
+ appendHeaders(buf);
+
+ // Remove the last newline.
+ buf.setLength(buf.length() - StringUtil.NEWLINE.length());
+ return buf.toString();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java
new file mode 100644
index 0000000000..599cf9fb73
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.buffer.ChannelBuffer;
+
+final class SpdyCodecUtil {
+
+ static final int SPDY_VERSION = 2;
+
+ static final int SPDY_HEADER_TYPE_OFFSET = 2;
+ static final int SPDY_HEADER_FLAGS_OFFSET = 4;
+ static final int SPDY_HEADER_LENGTH_OFFSET = 5;
+ static final int SPDY_HEADER_SIZE = 8;
+
+ static final int SPDY_MAX_LENGTH = 0xFFFFFF; // Length is a 24-bit field
+
+ static final byte SPDY_DATA_FLAG_FIN = 0x01;
+ static final byte SPDY_DATA_FLAG_COMPRESS = 0x02;
+
+ static final int SPDY_SYN_STREAM_FRAME = 1;
+ static final int SPDY_SYN_REPLY_FRAME = 2;
+ static final int SPDY_RST_STREAM_FRAME = 3;
+ static final int SPDY_SETTINGS_FRAME = 4;
+ static final int SPDY_NOOP_FRAME = 5;
+ static final int SPDY_PING_FRAME = 6;
+ static final int SPDY_GOAWAY_FRAME = 7;
+ static final int SPDY_HEADERS_FRAME = 8;
+ static final int SPDY_WINDOW_UPDATE_FRAME = 9;
+
+ static final byte SPDY_FLAG_FIN = 0x01;
+ static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;
+
+ static final byte SPDY_SETTINGS_CLEAR = 0x01;
+ static final byte SPDY_SETTINGS_PERSIST_VALUE = 0x01;
+ static final byte SPDY_SETTINGS_PERSISTED = 0x02;
+
+ static final int SPDY_SETTINGS_MAX_ID = 0xFFFFFF; // ID is a 24-bit field
+
+ static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field
+
+ // Zlib Dictionary
+ private static final String SPDY_DICT_S =
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440" +
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" +
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" +
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" +
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" +
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" +
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" +
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
+ ".1statusversionurl ";
+ static final byte[] SPDY_DICT;
+ static {
+ byte[] SPDY_DICT_ = null;
+
+ try {
+ SPDY_DICT_ = SPDY_DICT_S.getBytes("US-ASCII");
+ // dictionary is null terminated
+ SPDY_DICT_[SPDY_DICT_.length - 1] = (byte) 0;
+ } catch (Exception e) {
+ SPDY_DICT_ = new byte[1];
+ }
+
+ SPDY_DICT = SPDY_DICT_;
+ }
+
+
+ private SpdyCodecUtil() {
+ }
+
+
+ /**
+ * Reads a big-endian unsigned short integer from the buffer.
+ */
+ static int getUnsignedShort(ChannelBuffer buf, int offset) {
+ return (int) ((buf.getByte(offset) & 0xFF) << 8 |
+ (buf.getByte(offset + 1) & 0xFF));
+ }
+
+ /**
+ * Reads a big-endian unsigned medium integer from the buffer.
+ */
+ static int getUnsignedMedium(ChannelBuffer buf, int offset) {
+ return (int) ((buf.getByte(offset) & 0xFF) << 16 |
+ (buf.getByte(offset + 1) & 0xFF) << 8 |
+ (buf.getByte(offset + 2) & 0xFF));
+ }
+
+ /**
+ * Reads a big-endian (31-bit) integer from the buffer.
+ */
+ static int getUnsignedInt(ChannelBuffer buf, int offset) {
+ return (int) ((buf.getByte(offset) & 0x7F) << 24 |
+ (buf.getByte(offset + 1) & 0xFF) << 16 |
+ (buf.getByte(offset + 2) & 0xFF) << 8 |
+ (buf.getByte(offset + 3) & 0xFF));
+ }
+
+ /**
+ * Reads a big-endian signed integer from the buffer.
+ */
+ static int getSignedInt(ChannelBuffer buf, int offset) {
+ return (int) ((buf.getByte(offset) & 0xFF) << 24 |
+ (buf.getByte(offset + 1) & 0xFF) << 16 |
+ (buf.getByte(offset + 2) & 0xFF) << 8 |
+ (buf.getByte(offset + 3) & 0xFF));
+ }
+
+ /**
+ * Validate a SPDY header name.
+ */
+ static void validateHeaderName(String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ if (name.length() == 0) {
+ throw new IllegalArgumentException(
+ "name cannot be length zero");
+ }
+ // Since name may only contain ascii characters, for valid names
+ // name.length() returns the number of bytes when UTF-8 encoded.
+ if (name.length() > SPDY_MAX_NV_LENGTH) {
+ throw new IllegalArgumentException(
+ "name exceeds allowable length: " + name);
+ }
+ for (int i = 0; i < name.length(); i ++) {
+ char c = name.charAt(i);
+ if (c == 0) {
+ throw new IllegalArgumentException(
+ "name contains null character: " + name);
+ }
+ if (c > 127) {
+ throw new IllegalArgumentException(
+ "name contains non-ascii character: " + name);
+ }
+ }
+ }
+
+ /**
+ * Validate a SPDY header value. Does not validate max length.
+ */
+ static void validateHeaderValue(String value) {
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+ if (value.length() == 0) {
+ throw new IllegalArgumentException(
+ "value cannot be length zero");
+ }
+ for (int i = 0; i < value.length(); i ++) {
+ char c = value.charAt(i);
+ if (c == 0) {
+ throw new IllegalArgumentException(
+ "value contains null character: " + value);
+ }
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java
new file mode 100644
index 0000000000..9754154786
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+
+/**
+ * A SPDY Protocol Data Frame
+ */
+public interface SpdyDataFrame {
+
+ /**
+ * Returns the Stream-ID of this frame.
+ */
+ int getStreamID();
+
+ /**
+ * Sets the Stream-ID of this frame. The Stream-ID must be positive.
+ */
+ void setStreamID(int streamID);
+
+ /**
+ * Returns {@code true} if this frame is the last frame to be transmitted
+ * on the stream.
+ */
+ boolean isLast();
+
+ /**
+ * Sets if this frame is the last frame to be transmitted on the stream.
+ */
+ void setLast(boolean last);
+
+ /**
+ * Returns {@code true} if the data in this frame has been compressed.
+ */
+ boolean isCompressed();
+
+ /**
+ * Sets if the data in this frame has been compressed.
+ */
+ void setCompressed(boolean compressed);
+
+ /**
+ * Returns the data payload of this frame. If there is no data payload
+ * {@link ChannelBuffers#EMPTY_BUFFER} is returned.
+ */
+ ChannelBuffer getData();
+
+ /**
+ * Sets the data payload of this frame. If {@code null} is specified,
+ * the data payload will be set to {@link ChannelBuffers#EMPTY_BUFFER}.
+ * The data payload cannot exceed 16777215 bytes.
+ */
+ void setData(ChannelBuffer data);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java
new file mode 100644
index 0000000000..fd424f5ef5
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.channel.ChannelDownstreamHandler;
+import io.netty.channel.ChannelEvent;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelUpstreamHandler;
+
+/**
+ * A combination of {@link SpdyFrameDecoder} and {@link SpdyFrameEncoder}.
+ * @apiviz.has io.netty.handler.codec.spdy.SpdyFrameDecoder
+ * @apiviz.has io.netty.handler.codec.spdy.SpdyFrameEncoder
+ */
+public class SpdyFrameCodec implements ChannelUpstreamHandler,
+ ChannelDownstreamHandler {
+
+ private final SpdyFrameDecoder decoder = new SpdyFrameDecoder();
+ private final SpdyFrameEncoder encoder = new SpdyFrameEncoder();
+
+ public SpdyFrameCodec() {
+ }
+
+ public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
+ throws Exception {
+ decoder.handleUpstream(ctx, e);
+ }
+
+ public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
+ throws Exception {
+ encoder.handleDownstream(ctx, e);
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java
new file mode 100644
index 0000000000..001d507c8f
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.compression.ZlibDecoder;
+import io.netty.handler.codec.embedder.DecoderEmbedder;
+import io.netty.handler.codec.frame.FrameDecoder;
+
+import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
+
+/**
+ * Decodes {@link ChannelBuffer}s into SPDY Data and Control Frames.
+ */
+public class SpdyFrameDecoder extends FrameDecoder {
+
+ private final DecoderEmbedder headerBlockDecompressor =
+ new DecoderEmbedder(new ZlibDecoder(SPDY_DICT));
+
+ public SpdyFrameDecoder() {
+ super();
+ }
+
+ @Override
+ protected Object decode(
+ ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
+ throws Exception {
+
+ // Must read common header to determine frame length
+ if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
+ return null;
+ }
+
+ // Get frame length from common header
+ int frameOffset = buffer.readerIndex();
+ int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
+ int dataLength = getUnsignedMedium(buffer, lengthOffset);
+ int frameLength = SPDY_HEADER_SIZE + dataLength;
+
+ // Wait until entire frame is readable
+ if (buffer.readableBytes() < frameLength) {
+ return null;
+ }
+
+ // Read common header fields
+ boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
+ int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
+ byte flags = buffer.getByte(flagsOffset);
+
+ if (control) {
+ // Decode control frame common header
+ int version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
+
+ // Spdy versioning spec is broken
+ if (version != SPDY_VERSION) {
+ buffer.skipBytes(frameLength);
+ throw new SpdyProtocolException(
+ "Unsupported version: " + version);
+ }
+
+ int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
+ int type = getUnsignedShort(buffer, typeOffset);
+ buffer.skipBytes(SPDY_HEADER_SIZE);
+
+ return decodeControlFrame(type, flags, buffer.readBytes(dataLength));
+ } else {
+ // Decode data frame common header
+ int streamID = getUnsignedInt(buffer, frameOffset);
+ buffer.skipBytes(SPDY_HEADER_SIZE);
+
+ SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
+ spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
+ spdyDataFrame.setCompressed((flags & SPDY_DATA_FLAG_COMPRESS) != 0);
+ spdyDataFrame.setData(buffer.readBytes(dataLength));
+
+ return spdyDataFrame;
+ }
+ }
+
+ private Object decodeControlFrame(int type, byte flags, ChannelBuffer data)
+ throws Exception {
+ int streamID;
+ boolean last;
+
+ switch (type) {
+ case SPDY_SYN_STREAM_FRAME:
+ if (data.readableBytes() < 12) {
+ throw new SpdyProtocolException(
+ "Received invalid SYN_STREAM control frame");
+ }
+ streamID = getUnsignedInt(data, data.readerIndex());
+ int associatedToStreamID = getUnsignedInt(data, data.readerIndex() + 4);
+ byte priority = (byte) (data.getByte(data.readerIndex() + 8) >> 6 & 0x03);
+ data.skipBytes(10);
+
+ SpdySynStreamFrame spdySynStreamFrame =
+ new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
+
+ last = (flags & SPDY_FLAG_FIN) != 0;
+ boolean unid = (flags & SPDY_FLAG_UNIDIRECTIONAL) != 0;
+ spdySynStreamFrame.setLast(last);
+ spdySynStreamFrame.setUnidirectional(unid);
+
+ decodeHeaderBlock(spdySynStreamFrame, decompress(data));
+
+ return spdySynStreamFrame;
+
+ case SPDY_SYN_REPLY_FRAME:
+ if (data.readableBytes() < 8) {
+ throw new SpdyProtocolException(
+ "Received invalid SYN_REPLY control frame");
+ }
+ streamID = getUnsignedInt(data, data.readerIndex());
+ data.skipBytes(6);
+
+ SpdySynReplyFrame spdySynReplyFrame =
+ new DefaultSpdySynReplyFrame(streamID);
+
+ last = (flags & SPDY_FLAG_FIN) != 0;
+ spdySynReplyFrame.setLast(last);
+
+ decodeHeaderBlock(spdySynReplyFrame, decompress(data));
+
+ return spdySynReplyFrame;
+
+ case SPDY_RST_STREAM_FRAME:
+ if (flags != 0 || data.readableBytes() != 8) {
+ throw new SpdyProtocolException(
+ "Received invalid RST_STREAM control frame");
+ }
+ streamID = getUnsignedInt(data, data.readerIndex());
+ int statusCode = getSignedInt(data, data.readerIndex() + 4);
+ if (statusCode == 0) {
+ throw new SpdyProtocolException(
+ "Received invalid RST_STREAM status code");
+ }
+
+ return new DefaultSpdyRstStreamFrame(streamID, statusCode);
+
+ case SPDY_SETTINGS_FRAME:
+ if (data.readableBytes() < 4) {
+ throw new SpdyProtocolException(
+ "Received invalid SETTINGS control frame");
+ }
+ // Each ID/Value entry is 8 bytes
+ // The number of entries cannot exceed SPDY_MAX_LENGTH / 8;
+ int numEntries = getUnsignedInt(data, data.readerIndex());
+ if ((numEntries > (SPDY_MAX_LENGTH - 4) / 8) ||
+ (data.readableBytes() != numEntries * 8 + 4)) {
+ throw new SpdyProtocolException(
+ "Received invalid SETTINGS control frame");
+ }
+ data.skipBytes(4);
+
+ SpdySettingsFrame spdySettingsFrame = new DefaultSpdySettingsFrame();
+
+ boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
+ spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
+
+ for (int i = 0; i < numEntries; i ++) {
+ // Chromium Issue 79156
+ // SPDY setting ids are not written in network byte order
+ // Read id assuming the architecture is little endian
+ int ID = (data.readByte() & 0xFF) |
+ (data.readByte() & 0xFF) << 8 |
+ (data.readByte() & 0xFF) << 16;
+ byte ID_flags = data.readByte();
+ int value = getSignedInt(data, data.readerIndex());
+ data.skipBytes(4);
+
+ if (!(spdySettingsFrame.isSet(ID))) {
+ boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0;
+ boolean persisted = (ID_flags & SPDY_SETTINGS_PERSISTED) != 0;
+ spdySettingsFrame.setValue(ID, value, persistVal, persisted);
+ }
+ }
+
+ return spdySettingsFrame;
+
+ case SPDY_NOOP_FRAME:
+ if (data.readableBytes() != 0) {
+ throw new SpdyProtocolException(
+ "Received invalid NOOP control frame");
+ }
+
+ return null;
+
+ case SPDY_PING_FRAME:
+ if (data.readableBytes() != 4) {
+ throw new SpdyProtocolException(
+ "Received invalid PING control frame");
+ }
+ int ID = getSignedInt(data, data.readerIndex());
+
+ return new DefaultSpdyPingFrame(ID);
+
+ case SPDY_GOAWAY_FRAME:
+ if (data.readableBytes() != 4) {
+ throw new SpdyProtocolException(
+ "Received invalid GOAWAY control frame");
+ }
+ int lastGoodStreamID = getUnsignedInt(data, data.readerIndex());
+
+ return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
+
+ case SPDY_HEADERS_FRAME:
+ // Protocol allows length 4 frame when there are no name/value pairs
+ if (data.readableBytes() == 4) {
+ streamID = getUnsignedInt(data, data.readerIndex());
+ return new DefaultSpdyHeadersFrame(streamID);
+ }
+
+ if (data.readableBytes() < 8) {
+ throw new SpdyProtocolException(
+ "Received invalid HEADERS control frame");
+ }
+ streamID = getUnsignedInt(data, data.readerIndex());
+ data.skipBytes(6);
+
+ SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
+
+ decodeHeaderBlock(spdyHeadersFrame, decompress(data));
+
+ return spdyHeadersFrame;
+
+ case SPDY_WINDOW_UPDATE_FRAME:
+ return null;
+
+ default:
+ return null;
+ }
+ }
+
+ private ChannelBuffer decompress(ChannelBuffer compressed) throws Exception {
+ if ((compressed.readableBytes() == 2) &&
+ (compressed.getShort(compressed.readerIndex()) == 0)) {
+ return compressed;
+ }
+ headerBlockDecompressor.offer(compressed);
+ return headerBlockDecompressor.poll();
+ }
+
+ private void decodeHeaderBlock(SpdyHeaderBlock headerFrame, ChannelBuffer headerBlock)
+ throws Exception {
+ if (headerBlock.readableBytes() < 2) {
+ throw new SpdyProtocolException(
+ "Received invalid header block");
+ }
+ int numEntries = getUnsignedShort(headerBlock, headerBlock.readerIndex());
+ headerBlock.skipBytes(2);
+ for (int i = 0; i < numEntries; i ++) {
+ if (headerBlock.readableBytes() < 2) {
+ throw new SpdyProtocolException(
+ "Received invalid header block");
+ }
+ int nameLength = getUnsignedShort(headerBlock, headerBlock.readerIndex());
+ headerBlock.skipBytes(2);
+ if (nameLength == 0) {
+ headerFrame.setInvalid();
+ return;
+ }
+ if (headerBlock.readableBytes() < nameLength) {
+ throw new SpdyProtocolException(
+ "Received invalid header block");
+ }
+ byte[] nameBytes = new byte[nameLength];
+ headerBlock.readBytes(nameBytes);
+ String name = new String(nameBytes, "UTF-8");
+ if (headerFrame.containsHeader(name)) {
+ throw new SpdyProtocolException(
+ "Received duplicate header name: " + name);
+ }
+ if (headerBlock.readableBytes() < 2) {
+ throw new SpdyProtocolException(
+ "Received invalid header block");
+ }
+ int valueLength = getUnsignedShort(headerBlock, headerBlock.readerIndex());
+ headerBlock.skipBytes(2);
+ if (valueLength == 0) {
+ headerFrame.setInvalid();
+ return;
+ }
+ if (headerBlock.readableBytes() < valueLength) {
+ throw new SpdyProtocolException(
+ "Received invalid header block");
+ }
+ byte[] valueBytes = new byte[valueLength];
+ headerBlock.readBytes(valueBytes);
+ int index = 0;
+ int offset = 0;
+ while (index < valueBytes.length) {
+ while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
+ index ++;
+ }
+ if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
+ // Received multiple, in-sequence NULL characters
+ headerFrame.setInvalid();
+ return;
+ }
+ String value = new String(valueBytes, offset, index - offset, "UTF-8");
+ headerFrame.addHeader(name, value);
+ index ++;
+ offset = index;
+ }
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java
new file mode 100644
index 0000000000..19e51fea4c
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.nio.ByteOrder;
+import java.util.Set;
+
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.compression.ZlibEncoder;
+import io.netty.handler.codec.embedder.EncoderEmbedder;
+import io.netty.handler.codec.oneone.OneToOneEncoder;
+
+import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
+
+/**
+ * Encodes a SPDY Data or Control Frame into a {@link ChannelBuffer}.
+ */
+public class SpdyFrameEncoder extends OneToOneEncoder {
+
+ private final EncoderEmbedder headerBlockCompressor =
+ new EncoderEmbedder(new ZlibEncoder(9, SPDY_DICT));
+
+ public SpdyFrameEncoder() {
+ super();
+ }
+
+ @Override
+ protected Object encode(
+ ChannelHandlerContext ctx, Channel channel, Object msg)
+ throws Exception {
+
+ if (msg instanceof SpdyDataFrame) {
+
+ SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
+ ChannelBuffer data = spdyDataFrame.getData();
+ byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
+ if (spdyDataFrame.isCompressed()) {
+ flags |= SPDY_DATA_FLAG_COMPRESS;
+ }
+ ChannelBuffer header = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
+ header.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF);
+ header.writeByte(flags);
+ header.writeMedium(data.readableBytes());
+ return ChannelBuffers.wrappedBuffer(header, data);
+
+ } else if (msg instanceof SpdySynStreamFrame) {
+
+ SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
+ ChannelBuffer data = compressHeaderBlock(
+ encodeHeaderBlock(spdySynStreamFrame));
+ byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
+ if (spdySynStreamFrame.isUnidirectional()) {
+ flags |= SPDY_FLAG_UNIDIRECTIONAL;
+ }
+ int headerBlockLength = data.readableBytes();
+ int length = (headerBlockLength == 0) ? 12 : 10 + headerBlockLength;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_SYN_STREAM_FRAME);
+ frame.writeByte(flags);
+ frame.writeMedium(length);
+ frame.writeInt(spdySynStreamFrame.getStreamID());
+ frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID());
+ frame.writeShort(((short) spdySynStreamFrame.getPriority()) << 14);
+ if (data.readableBytes() == 0) {
+ frame.writeShort(0);
+ }
+ return ChannelBuffers.wrappedBuffer(frame, data);
+
+ } else if (msg instanceof SpdySynReplyFrame) {
+
+ SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
+ ChannelBuffer data = compressHeaderBlock(
+ encodeHeaderBlock(spdySynReplyFrame));
+ byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
+ int headerBlockLength = data.readableBytes();
+ int length = (headerBlockLength == 0) ? 8 : 6 + headerBlockLength;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_SYN_REPLY_FRAME);
+ frame.writeByte(flags);
+ frame.writeMedium(length);
+ frame.writeInt(spdySynReplyFrame.getStreamID());
+ if (data.readableBytes() == 0) {
+ frame.writeInt(0);
+ } else {
+ frame.writeShort(0);
+ }
+ return ChannelBuffers.wrappedBuffer(frame, data);
+
+ } else if (msg instanceof SpdyRstStreamFrame) {
+
+ SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_RST_STREAM_FRAME);
+ frame.writeInt(8);
+ frame.writeInt(spdyRstStreamFrame.getStreamID());
+ frame.writeInt(spdyRstStreamFrame.getStatus().getCode());
+ return frame;
+
+ } else if (msg instanceof SpdySettingsFrame) {
+
+ SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
+ byte flags = spdySettingsFrame.clearPreviouslyPersistedSettings() ?
+ SPDY_SETTINGS_CLEAR : 0;
+ Set IDs = spdySettingsFrame.getIDs();
+ int numEntries = IDs.size();
+ int length = 4 + numEntries * 8;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_SETTINGS_FRAME);
+ frame.writeByte(flags);
+ frame.writeMedium(length);
+ frame.writeInt(numEntries);
+ for (Integer ID: IDs) {
+ int id = ID.intValue();
+ byte ID_flags = (byte) 0;
+ if (spdySettingsFrame.persistValue(id)) {
+ ID_flags |= SPDY_SETTINGS_PERSIST_VALUE;
+ }
+ if (spdySettingsFrame.isPersisted(id)) {
+ ID_flags |= SPDY_SETTINGS_PERSISTED;
+ }
+ // Chromium Issue 79156
+ // SPDY setting ids are not written in network byte order
+ // Write id assuming the architecture is little endian
+ frame.writeByte((id >> 0) & 0xFF);
+ frame.writeByte((id >> 8) & 0xFF);
+ frame.writeByte((id >> 16) & 0xFF);
+ frame.writeByte(ID_flags);
+ frame.writeInt(spdySettingsFrame.getValue(id));
+ }
+ return frame;
+
+ } else if (msg instanceof SpdyNoOpFrame) {
+
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_NOOP_FRAME);
+ frame.writeInt(0);
+ return frame;
+
+ } else if (msg instanceof SpdyPingFrame) {
+
+ SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_PING_FRAME);
+ frame.writeInt(4);
+ frame.writeInt(spdyPingFrame.getID());
+ return frame;
+
+ } else if (msg instanceof SpdyGoAwayFrame) {
+
+ SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_GOAWAY_FRAME);
+ frame.writeInt(4);
+ frame.writeInt(spdyGoAwayFrame.getLastGoodStreamID());
+ return frame;
+
+ } else if (msg instanceof SpdyHeadersFrame) {
+
+ SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
+ ChannelBuffer data = compressHeaderBlock(
+ encodeHeaderBlock(spdyHeadersFrame));
+ int headerBlockLength = data.readableBytes();
+ int length = (headerBlockLength == 0) ? 4 : 6 + headerBlockLength;
+ ChannelBuffer frame = ChannelBuffers.buffer(
+ ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
+ frame.writeShort(SPDY_VERSION | 0x8000);
+ frame.writeShort(SPDY_HEADERS_FRAME);
+ frame.writeInt(length);
+ frame.writeInt(spdyHeadersFrame.getStreamID());
+ if (data.readableBytes() != 0) {
+ frame.writeShort(0);
+ }
+ return ChannelBuffers.wrappedBuffer(frame, data);
+ }
+
+ // Unknown message type
+ return msg;
+ }
+
+ private ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame)
+ throws Exception {
+ Set names = headerFrame.getHeaderNames();
+ int numHeaders = names.size();
+ if (numHeaders == 0) {
+ return ChannelBuffers.EMPTY_BUFFER;
+ }
+ if (numHeaders > SPDY_MAX_NV_LENGTH) {
+ throw new IllegalArgumentException(
+ "header block contains too many headers");
+ }
+ ChannelBuffer headerBlock = ChannelBuffers.dynamicBuffer(
+ ByteOrder.BIG_ENDIAN, 256);
+ headerBlock.writeShort(numHeaders);
+ for (String name: names) {
+ byte[] nameBytes = name.getBytes("UTF-8");
+ headerBlock.writeShort(nameBytes.length);
+ headerBlock.writeBytes(nameBytes);
+ int savedIndex = headerBlock.writerIndex();
+ int valueLength = 0;
+ headerBlock.writeShort(valueLength);
+ for (String value: headerFrame.getHeaders(name)) {
+ byte[] valueBytes = value.getBytes("UTF-8");
+ headerBlock.writeBytes(valueBytes);
+ headerBlock.writeByte(0);
+ valueLength += valueBytes.length + 1;
+ }
+ valueLength --;
+ if (valueLength > SPDY_MAX_NV_LENGTH) {
+ throw new IllegalArgumentException(
+ "header exceeds allowable length: " + name);
+ }
+ headerBlock.setShort(savedIndex, valueLength);
+ headerBlock.writerIndex(headerBlock.writerIndex() - 1);
+ }
+ return headerBlock;
+ }
+
+ private synchronized ChannelBuffer compressHeaderBlock(
+ ChannelBuffer uncompressed) throws Exception {
+ if (uncompressed.readableBytes() == 0) {
+ return ChannelBuffers.EMPTY_BUFFER;
+ }
+ headerBlockCompressor.offer(uncompressed);
+ return headerBlockCompressor.poll();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java
new file mode 100644
index 0000000000..c4c7fb201d
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol GOAWAY Control Frame
+ */
+public interface SpdyGoAwayFrame {
+
+ /**
+ * Returns the Last-good-stream-ID of this frame.
+ */
+ int getLastGoodStreamID();
+
+ /**
+ * Sets the Last-good-stream-ID of this frame. The Last-good-stream-ID
+ * cannot be negative.
+ */
+ void setLastGoodStreamID(int lastGoodStreamID);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlock.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlock.java
new file mode 100644
index 0000000000..a8d0486fed
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlock.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A SPDY Name/Value Header Block which provides common properties for
+ * {@link SpdySynStreamFrame}, {@link SpdySynReplyFrame}, and
+ * {@link SpdyHeadersFrame}.
+ * @see SpdyHeaders
+ */
+public interface SpdyHeaderBlock {
+
+ /**
+ * Returns {@code true} if this header block is invalid.
+ * A RST_STREAM frame with code PROTOCOL_ERROR should be sent.
+ */
+ boolean isInvalid();
+
+ /**
+ * Marks this header block as invalid.
+ */
+ void setInvalid();
+
+ /**
+ * Returns the header value with the specified header name. If there is
+ * more than one header value for the specified header name, the first
+ * value is returned.
+ *
+ * @return the header value or {@code null} if there is no such header
+ */
+ String getHeader(String name);
+
+ /**
+ * Returns the header values with the specified header name.
+ *
+ * @return the {@link List} of header values. An empty list if there is no
+ * such header.
+ */
+ List getHeaders(String name);
+
+ /**
+ * Returns all header names and values that this block contains.
+ *
+ * @return the {@link List} of the header name-value pairs. An empty list
+ * if there is no header in this message.
+ */
+ List> getHeaders();
+
+ /**
+ * Returns {@code true} if and only if there is a header with the specified
+ * header name.
+ */
+ boolean containsHeader(String name);
+
+ /**
+ * Returns the {@link Set} of all header names that this block contains.
+ */
+ Set getHeaderNames();
+
+ /**
+ * Adds a new header with the specified name and value.
+ */
+ void addHeader(String name, Object value);
+
+ /**
+ * Sets a new header with the specified name and value. If there is an
+ * existing header with the same name, the existing header is removed.
+ */
+ void setHeader(String name, Object value);
+
+ /**
+ * Sets a new header with the specified name and values. If there is an
+ * existing header with the same name, the existing header is removed.
+ */
+ void setHeader(String name, Iterable> values);
+
+ /**
+ * Removes the header with the specified name.
+ */
+ void removeHeader(String name);
+
+ /**
+ * Removes all headers from this block.
+ */
+ void clearHeaders();
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java
new file mode 100644
index 0000000000..ec8adea0df
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+
+/**
+ * Provides the constants for the standard SPDY HTTP header names and commonly
+ * used utility methods that access an {@link SpdyHeaderBlock}.
+ * @apiviz.sterotype static
+ */
+public class SpdyHeaders {
+
+ /**
+ * SPDY HTTP header names
+ * @apiviz.sterotype static
+ */
+ public static final class HttpNames {
+ /**
+ * {@code "method"}
+ */
+ public static final String METHOD = "method";
+ /**
+ * {@code "scheme"}
+ */
+ public static final String SCHEME = "scheme";
+ /**
+ * {@code "status"}
+ */
+ public static final String STATUS = "status";
+ /**
+ * {@code "url"}
+ */
+ public static final String URL = "url";
+ /**
+ * {@code "version"}
+ */
+ public static final String VERSION = "version";
+
+ private HttpNames() {
+ super();
+ }
+ }
+
+
+ /**
+ * Returns the header value with the specified header name. If there are
+ * more than one header value for the specified header name, the first
+ * value is returned.
+ *
+ * @return the header value or {@code null} if there is no such header
+ */
+ public static String getHeader(SpdyHeaderBlock block, String name) {
+ return block.getHeader(name);
+ }
+
+ /**
+ * Returns the header value with the specified header name. If there are
+ * more than one header value for the specified header name, the first
+ * value is returned.
+ *
+ * @return the header value or the {@code defaultValue} if there is no such
+ * header
+ */
+ public static String getHeader(SpdyHeaderBlock block, String name, String defaultValue) {
+ String value = block.getHeader(name);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ /**
+ * Sets a new header with the specified name and value. If there is an
+ * existing header with the same name, the existing header is removed.
+ */
+ public static void setHeader(SpdyHeaderBlock block, String name, Object value) {
+ block.setHeader(name, value);
+ }
+
+ /**
+ * Sets a new header with the specified name and values. If there is an
+ * existing header with the same name, the existing header is removed.
+ */
+ public static void setHeader(SpdyHeaderBlock block, String name, Iterable> values) {
+ block.setHeader(name, values);
+ }
+
+ /**
+ * Adds a new header with the specified name and value.
+ */
+ public static void addHeader(SpdyHeaderBlock block, String name, Object value) {
+ block.addHeader(name, value);
+ }
+
+ /**
+ * Removes the {@code "method"} header.
+ */
+ public static void removeMethod(SpdyHeaderBlock block) {
+ block.removeHeader(HttpNames.METHOD);
+ }
+
+ /**
+ * Returns the {@link HttpMethod} represented by the {@code "method"} header.
+ */
+ public static HttpMethod getMethod(SpdyHeaderBlock block) {
+ try {
+ return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the {@code "method"} header.
+ */
+ public static void setMethod(SpdyHeaderBlock block, HttpMethod method) {
+ block.setHeader(HttpNames.METHOD, method.getName());
+ }
+
+ /**
+ * Removes the {@code "scheme"} header.
+ */
+ public static void removeScheme(SpdyHeaderBlock block) {
+ block.removeHeader(HttpNames.SCHEME);
+ }
+
+ /**
+ * Returns the value of the {@code "scheme"} header.
+ */
+ public static String getScheme(SpdyHeaderBlock block) {
+ return block.getHeader(HttpNames.SCHEME);
+ }
+
+ /**
+ * Sets the {@code "scheme"} header.
+ */
+ public static void setScheme(SpdyHeaderBlock block, String value) {
+ block.setHeader(HttpNames.SCHEME, value);
+ }
+
+ /**
+ * Removes the {@code "status"} header.
+ */
+ public static void removeStatus(SpdyHeaderBlock block) {
+ block.removeHeader(HttpNames.STATUS);
+ }
+
+ /**
+ * Returns the {@link HttpResponseStatus} represented by the {@code "status"} header.
+ */
+ public static HttpResponseStatus getStatus(SpdyHeaderBlock block) {
+ try {
+ String status = block.getHeader(HttpNames.STATUS);
+ int space = status.indexOf(' ');
+ if (space == -1) {
+ return HttpResponseStatus.valueOf(Integer.parseInt(status));
+ } else {
+ int code = Integer.parseInt(status.substring(0, space));
+ String reasonPhrase = status.substring(space + 1);
+ HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
+ if (responseStatus.getReasonPhrase().equals(responseStatus)) {
+ return responseStatus;
+ } else {
+ return new HttpResponseStatus(code, reasonPhrase);
+ }
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the {@code "status"} header.
+ */
+ public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) {
+ block.setHeader(HttpNames.STATUS, status.toString());
+ }
+
+ /**
+ * Removes the {@code "url"} header.
+ */
+ public static void removeUrl(SpdyHeaderBlock block) {
+ block.removeHeader(HttpNames.URL);
+ }
+
+ /**
+ * Returns the value of the {@code "url"} header.
+ */
+ public static String getUrl(SpdyHeaderBlock block) {
+ return block.getHeader(HttpNames.URL);
+ }
+
+ /**
+ * Sets the {@code "url"} header.
+ */
+ public static void setUrl(SpdyHeaderBlock block, String value) {
+ block.setHeader(HttpNames.URL, value);
+ }
+
+ /**
+ * Removes the {@code "version"} header.
+ */
+ public static void removeVersion(SpdyHeaderBlock block) {
+ block.removeHeader(HttpNames.VERSION);
+ }
+
+ /**
+ * Returns the {@link HttpVersion} represented by the {@code "version"} header.
+ */
+ public static HttpVersion getVersion(SpdyHeaderBlock block) {
+ try {
+ return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the {@code "version"} header.
+ */
+ public static void setVersion(SpdyHeaderBlock block, HttpVersion version) {
+ block.setHeader(HttpNames.VERSION, version.getText());
+ }
+
+
+ private static final int BUCKET_SIZE = 17;
+
+ private static int hash(String name) {
+ int h = 0;
+ for (int i = name.length() - 1; i >= 0; i --) {
+ char c = name.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 32;
+ }
+ h = 31 * h + c;
+ }
+
+ if (h > 0) {
+ return h;
+ } else if (h == Integer.MIN_VALUE) {
+ return Integer.MAX_VALUE;
+ } else {
+ return -h;
+ }
+ }
+
+ private static boolean eq(String name1, String name2) {
+ int nameLen = name1.length();
+ if (nameLen != name2.length()) {
+ return false;
+ }
+
+ for (int i = nameLen - 1; i >= 0; i --) {
+ char c1 = name1.charAt(i);
+ char c2 = name2.charAt(i);
+ if (c1 != c2) {
+ if (c1 >= 'A' && c1 <= 'Z') {
+ c1 += 32;
+ }
+ if (c2 >= 'A' && c2 <= 'Z') {
+ c2 += 32;
+ }
+ if (c1 != c2) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static int index(int hash) {
+ return hash % BUCKET_SIZE;
+ }
+
+ private final Entry[] entries = new Entry[BUCKET_SIZE];
+ private final Entry head = new Entry(-1, null, null);
+
+ SpdyHeaders() {
+ head.before = head.after = head;
+ }
+
+ void addHeader(final String name, final Object value) {
+ String lowerCaseName = name.toLowerCase();
+ SpdyCodecUtil.validateHeaderName(lowerCaseName);
+ String strVal = toString(value);
+ SpdyCodecUtil.validateHeaderValue(strVal);
+ int h = hash(lowerCaseName);
+ int i = index(h);
+ addHeader0(h, i, lowerCaseName, strVal);
+ }
+
+ private void addHeader0(int h, int i, final String name, final String value) {
+ // Update the hash table.
+ Entry e = entries[i];
+ Entry newEntry;
+ entries[i] = newEntry = new Entry(h, name, value);
+ newEntry.next = e;
+
+ // Update the linked list.
+ newEntry.addBefore(head);
+ }
+
+ void removeHeader(final String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ String lowerCaseName = name.toLowerCase();
+ int h = hash(lowerCaseName);
+ int i = index(h);
+ removeHeader0(h, i, lowerCaseName);
+ }
+
+ private void removeHeader0(int h, int i, String name) {
+ Entry e = entries[i];
+ if (e == null) {
+ return;
+ }
+
+ for (;;) {
+ if (e.hash == h && eq(name, e.key)) {
+ e.remove();
+ Entry next = e.next;
+ if (next != null) {
+ entries[i] = next;
+ e = next;
+ } else {
+ entries[i] = null;
+ return;
+ }
+ } else {
+ break;
+ }
+ }
+
+ for (;;) {
+ Entry next = e.next;
+ if (next == null) {
+ break;
+ }
+ if (next.hash == h && eq(name, next.key)) {
+ e.next = next.next;
+ next.remove();
+ } else {
+ e = next;
+ }
+ }
+ }
+
+ void setHeader(final String name, final Object value) {
+ String lowerCaseName = name.toLowerCase();
+ SpdyCodecUtil.validateHeaderName(lowerCaseName);
+ String strVal = toString(value);
+ SpdyCodecUtil.validateHeaderValue(strVal);
+ int h = hash(lowerCaseName);
+ int i = index(h);
+ removeHeader0(h, i, lowerCaseName);
+ addHeader0(h, i, lowerCaseName, strVal);
+ }
+
+ void setHeader(final String name, final Iterable> values) {
+ if (values == null) {
+ throw new NullPointerException("values");
+ }
+
+ String lowerCaseName = name.toLowerCase();
+ SpdyCodecUtil.validateHeaderName(lowerCaseName);
+
+ int h = hash(lowerCaseName);
+ int i = index(h);
+
+ removeHeader0(h, i, lowerCaseName);
+ for (Object v: values) {
+ if (v == null) {
+ break;
+ }
+ String strVal = toString(v);
+ SpdyCodecUtil.validateHeaderValue(strVal);
+ addHeader0(h, i, lowerCaseName, strVal);
+ }
+ }
+
+ void clearHeaders() {
+ for (int i = 0; i < entries.length; i ++) {
+ entries[i] = null;
+ }
+ head.before = head.after = head;
+ }
+
+ String getHeader(final String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+
+ int h = hash(name);
+ int i = index(h);
+ Entry e = entries[i];
+ while (e != null) {
+ if (e.hash == h && eq(name, e.key)) {
+ return e.value;
+ }
+
+ e = e.next;
+ }
+ return null;
+ }
+
+ List getHeaders(final String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+
+ LinkedList values = new LinkedList();
+
+ int h = hash(name);
+ int i = index(h);
+ Entry e = entries[i];
+ while (e != null) {
+ if (e.hash == h && eq(name, e.key)) {
+ values.addFirst(e.value);
+ }
+ e = e.next;
+ }
+ return values;
+ }
+
+ List> getHeaders() {
+ List> all =
+ new LinkedList>();
+
+ Entry e = head.after;
+ while (e != head) {
+ all.add(e);
+ e = e.after;
+ }
+ return all;
+ }
+
+ boolean containsHeader(String name) {
+ return getHeader(name) != null;
+ }
+
+ Set getHeaderNames() {
+ Set names = new TreeSet();
+
+ Entry e = head.after;
+ while (e != head) {
+ names.add(e.key);
+ e = e.after;
+ }
+ return names;
+ }
+
+ private static String toString(Object value) {
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+
+ private static final class Entry implements Map.Entry {
+ final int hash;
+ final String key;
+ String value;
+ Entry next;
+ Entry before, after;
+
+ Entry(int hash, String key, String value) {
+ this.hash = hash;
+ this.key = key;
+ this.value = value;
+ }
+
+ void remove() {
+ before.after = after;
+ after.before = before;
+ }
+
+ void addBefore(Entry e) {
+ after = e;
+ before = e.before;
+ before.after = this;
+ after.before = this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String setValue(String value) {
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+ SpdyCodecUtil.validateHeaderValue(value);
+ String oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+
+ @Override
+ public String toString() {
+ return key + "=" + value;
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java
new file mode 100644
index 0000000000..ecfbf6017c
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol HEADERS Control Frame
+ */
+public interface SpdyHeadersFrame extends SpdyHeaderBlock {
+
+ /**
+ * Returns the Stream-ID of this frame.
+ */
+ int getStreamID();
+
+ /**
+ * Sets the Stream-ID of this frame. The Stream-ID must be positive.
+ */
+ void setStreamID(int streamID);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java
new file mode 100644
index 0000000000..1652344fd7
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol NOOP Control Frame
+ */
+public interface SpdyNoOpFrame {
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyPingFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyPingFrame.java
new file mode 100644
index 0000000000..48b7bc65ec
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyPingFrame.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol PING Control Frame
+ */
+public interface SpdyPingFrame {
+
+ /**
+ * Returns the ID of this frame.
+ */
+ int getID();
+
+ /**
+ * Sets the ID of this frame.
+ */
+ void setID(int ID);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyProtocolException.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyProtocolException.java
new file mode 100644
index 0000000000..508354fb1d
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyProtocolException.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * An {@link Exception} which is thrown when the received frame cannot
+ * be decoded by the {@link SpdyFrameDecoder}.
+ * @apiviz.exclude
+ */
+public class SpdyProtocolException extends Exception {
+
+ /**
+ * Creates a new instance.
+ */
+ public SpdyProtocolException() {
+ super();
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ public SpdyProtocolException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ public SpdyProtocolException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ public SpdyProtocolException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyRstStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyRstStreamFrame.java
new file mode 100644
index 0000000000..0cfc8b6945
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyRstStreamFrame.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol RST_STREAM Control Frame
+ */
+public interface SpdyRstStreamFrame {
+
+ /**
+ * Returns the Stream-ID of this frame.
+ */
+ int getStreamID();
+
+ /**
+ * Sets the Stream-ID of this frame. The Stream-ID must be positive.
+ */
+ void setStreamID(int streamID);
+
+ /**
+ * Returns the status of this frame.
+ */
+ SpdyStreamStatus getStatus();
+
+ /**
+ * Sets the status of this frame.
+ */
+ void setStatus(SpdyStreamStatus status);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java
new file mode 100644
index 0000000000..7645b65b78
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class SpdySession {
+
+ private final Map activeStreams =
+ new ConcurrentHashMap();
+
+ SpdySession() {
+ }
+
+ public int numActiveStreams() {
+ return activeStreams.size();
+ }
+
+ public boolean noActiveStreams() {
+ return activeStreams.isEmpty();
+ }
+
+ public boolean isActiveStream(int streamID) {
+ return activeStreams.containsKey(new Integer(streamID));
+ }
+
+ public void acceptStream(int streamID, boolean remoteSideClosed, boolean localSideClosed) {
+ if (!remoteSideClosed || !localSideClosed) {
+ activeStreams.put(new Integer(streamID),
+ new StreamState(remoteSideClosed, localSideClosed));
+ }
+ return;
+ }
+
+ public void removeStream(int streamID) {
+ activeStreams.remove(new Integer(streamID));
+ return;
+ }
+
+ public boolean isRemoteSideClosed(int streamID) {
+ StreamState state = activeStreams.get(new Integer(streamID));
+ return (state == null) || state.isRemoteSideClosed();
+ }
+
+ public void closeRemoteSide(int streamID) {
+ Integer StreamID = new Integer(streamID);
+ StreamState state = activeStreams.get(StreamID);
+ if (state != null) {
+ state.closeRemoteSide();
+ if (state.isLocalSideClosed()) {
+ activeStreams.remove(StreamID);
+ }
+ }
+ }
+
+ public boolean isLocalSideClosed(int streamID) {
+ StreamState state = activeStreams.get(new Integer(streamID));
+ return (state == null) || state.isLocalSideClosed();
+ }
+
+ public void closeLocalSide(int streamID) {
+ Integer StreamID = new Integer(streamID);
+ StreamState state = activeStreams.get(StreamID);
+ if (state != null) {
+ state.closeLocalSide();
+ if (state.isRemoteSideClosed()) {
+ activeStreams.remove(StreamID);
+ }
+ }
+ }
+
+ public boolean hasReceivedReply(int streamID) {
+ StreamState state = activeStreams.get(new Integer(streamID));
+ return (state != null) && state.hasReceivedReply();
+ }
+
+ public void receivedReply(int streamID) {
+ StreamState state = activeStreams.get(new Integer(streamID));
+ if (state != null) {
+ state.receivedReply();
+ }
+ }
+
+ private static final class StreamState {
+
+ private boolean remoteSideClosed;
+ private boolean localSideClosed;
+ private boolean receivedReply;
+
+ public StreamState(boolean remoteSideClosed, boolean localSideClosed) {
+ this.remoteSideClosed = remoteSideClosed;
+ this.localSideClosed = localSideClosed;
+ }
+
+ public boolean isRemoteSideClosed() {
+ return remoteSideClosed;
+ }
+
+ public void closeRemoteSide() {
+ remoteSideClosed = true;
+ }
+
+ public boolean isLocalSideClosed() {
+ return localSideClosed;
+ }
+
+ public void closeLocalSide() {
+ localSideClosed = true;
+ }
+
+ public boolean hasReceivedReply() {
+ return receivedReply;
+ }
+
+ public void receivedReply() {
+ receivedReply = true;
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java
new file mode 100644
index 0000000000..70c32c963d
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.net.SocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelDownstreamHandler;
+import io.netty.channel.ChannelEvent;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelStateEvent;
+import io.netty.channel.Channels;
+import io.netty.channel.MessageEvent;
+import io.netty.channel.SimpleChannelUpstreamHandler;
+
+/**
+ * Manages streams within a SPDY session.
+ */
+public class SpdySessionHandler extends SimpleChannelUpstreamHandler
+ implements ChannelDownstreamHandler {
+
+ private static final SpdyProtocolException PROTOCOL_EXCEPTION = new SpdyProtocolException();
+
+ private final SpdySession spdySession = new SpdySession();
+ private volatile int lastGoodStreamID;
+
+ private volatile int remoteConcurrentStreams;
+ private volatile int localConcurrentStreams;
+ private volatile int maxConcurrentStreams;
+
+ private final AtomicInteger pings = new AtomicInteger();
+
+ private volatile boolean sentGoAwayFrame;
+ private volatile boolean receivedGoAwayFrame;
+
+ private volatile ChannelFuture closeSessionFuture;
+
+ private final boolean server;
+
+ /**
+ * Creates a new session handler.
+ *
+ * @param server {@code true} if and only if this session handler should
+ * handle the server endpoint of the connection.
+ * {@code false} if and only if this session handler should
+ * handle the client endpoint of the connection.
+ */
+ public SpdySessionHandler(boolean server) {
+ super();
+ this.server = server;
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+
+ Object msg = e.getMessage();
+ if (msg instanceof SpdyDataFrame) {
+
+ /*
+ * SPDY Data frame processing requirements:
+ *
+ * If an endpoint receives a data frame for a Stream-ID which does not exist,
+ * it must return a RST_STREAM with error code INVALID_STREAM for the Stream-ID.
+ *
+ * If an endpoint which created the stream receives a data frame before receiving
+ * a SYN_REPLY on that stream, it is a protocol error, and the receiver should
+ * close the connection immediately.
+ *
+ * If an endpoint receives multiple data frames for invalid Stream-IDs,
+ * it may terminate the session.
+ *
+ * If an endpoint refuses a stream it must ignore any data frames for that stream.
+ *
+ * If an endpoint receives data on a stream which has already been torn down,
+ * it must ignore the data received after the teardown.
+ */
+
+ SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
+ int streamID = spdyDataFrame.getStreamID();
+
+ // Check if we received a data frame for a Stream-ID which is not open
+ if (spdySession.isRemoteSideClosed(streamID)) {
+ if (!sentGoAwayFrame) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
+ }
+ return;
+ }
+
+ // Check if we received a data frame before receiving a SYN_REPLY
+ if (!isRemoteInitiatedID(streamID) && !spdySession.hasReceivedReply(streamID)) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
+ return;
+ }
+
+ if (spdyDataFrame.isLast()) {
+ // Close remote side of stream
+ halfCloseStream(streamID, true);
+ }
+
+ } else if (msg instanceof SpdySynStreamFrame) {
+
+ /*
+ * SPDY SYN_STREAM frame processing requirements:
+ *
+ * If an endpoint receives a SYN_STREAM with a Stream-ID that is not monotonically
+ * increasing, it must issue a session error with the status PROTOCOL_ERROR.
+ *
+ * If an endpoint receives multiple SYN_STREAM frames with the same active
+ * Stream-ID, it must issue a stream error with the status code PROTOCOL_ERROR.
+ */
+
+ SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
+ int streamID = spdySynStreamFrame.getStreamID();
+
+ // Check if we received a valid SYN_STREAM frame
+ if (spdySynStreamFrame.isInvalid() ||
+ !isRemoteInitiatedID(streamID) ||
+ spdySession.isActiveStream(streamID)) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
+ return;
+ }
+
+ // Stream-IDs must be monotonically increassing
+ if (streamID < lastGoodStreamID) {
+ issueSessionError(ctx, e.getChannel(), e.getRemoteAddress());
+ return;
+ }
+
+ // Try to accept the stream
+ boolean remoteSideClosed = spdySynStreamFrame.isLast();
+ boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
+ if (!acceptStream(streamID, remoteSideClosed, localSideClosed)) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.REFUSED_STREAM);
+ return;
+ }
+
+ } else if (msg instanceof SpdySynReplyFrame) {
+
+ /*
+ * SPDY SYN_REPLY frame processing requirements:
+ *
+ * If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
+ * it must issue a stream error with the status code PROTOCOL_ERROR.
+ */
+
+ SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
+ int streamID = spdySynReplyFrame.getStreamID();
+
+ // Check if we received a valid SYN_REPLY frame
+ if (spdySynReplyFrame.isInvalid() ||
+ isRemoteInitiatedID(streamID) ||
+ spdySession.isRemoteSideClosed(streamID)) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
+ return;
+ }
+
+ // Check if we have received multiple frames for the same Stream-ID
+ if (spdySession.hasReceivedReply(streamID)) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
+ return;
+ }
+
+ spdySession.receivedReply(streamID);
+ if (spdySynReplyFrame.isLast()) {
+ // Close remote side of stream
+ halfCloseStream(streamID, true);
+ }
+
+ } else if (msg instanceof SpdyRstStreamFrame) {
+
+ /*
+ * SPDY RST_STREAM frame processing requirements:
+ *
+ * After receiving a RST_STREAM on a stream, the receiver must not send additional
+ * frames on that stream.
+ */
+
+ SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
+ removeStream(spdyRstStreamFrame.getStreamID());
+
+ } else if (msg instanceof SpdySettingsFrame) {
+
+ /*
+ * Only concerned with MAX_CONCURRENT_STREAMS
+ */
+
+ SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
+ updateConcurrentStreams(spdySettingsFrame, true);
+
+ } else if (msg instanceof SpdyPingFrame) {
+
+ /*
+ * SPDY PING frame processing requirements:
+ *
+ * Receivers of a PING frame should send an identical frame to the sender
+ * as soon as possible.
+ *
+ * Receivers of a PING frame must ignore frames that it did not initiate
+ */
+
+ SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
+
+ if (isRemoteInitiatedID(spdyPingFrame.getID())) {
+ Channels.write(ctx, Channels.future(e.getChannel()), spdyPingFrame, e.getRemoteAddress());
+ return;
+ }
+
+ // Note: only checks that there are outstanding pings since uniqueness is not inforced
+ if (pings.get() == 0) {
+ return;
+ }
+ pings.getAndDecrement();
+
+ } else if (msg instanceof SpdyGoAwayFrame) {
+
+ receivedGoAwayFrame = true;
+
+ } else if (msg instanceof SpdyHeadersFrame) {
+
+ SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
+ int streamID = spdyHeadersFrame.getStreamID();
+
+ // Check if we received a valid HEADERS frame
+ if (spdyHeadersFrame.isInvalid()) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
+ return;
+ }
+
+ if (spdySession.isRemoteSideClosed(streamID)) {
+ issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
+ return;
+ }
+ }
+
+ super.messageReceived(ctx, e);
+ }
+
+ public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
+ throws Exception {
+ if (evt instanceof ChannelStateEvent) {
+ ChannelStateEvent e = (ChannelStateEvent) evt;
+ switch (e.getState()) {
+ case OPEN:
+ case CONNECTED:
+ case BOUND:
+ if (Boolean.FALSE.equals(e.getValue()) || e.getValue() == null) {
+ sendGoAwayFrame(ctx, e);
+ return;
+ }
+ }
+ }
+ if (!(evt instanceof MessageEvent)) {
+ ctx.sendDownstream(evt);
+ return;
+ }
+
+ MessageEvent e = (MessageEvent) evt;
+ Object msg = e.getMessage();
+
+ if (msg instanceof SpdyDataFrame) {
+
+ SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
+ int streamID = spdyDataFrame.getStreamID();
+
+ if (spdySession.isLocalSideClosed(streamID)) {
+ e.getFuture().setFailure(PROTOCOL_EXCEPTION);
+ return;
+ }
+
+ if (spdyDataFrame.isLast()) {
+ halfCloseStream(streamID, false);
+ }
+
+ } else if (msg instanceof SpdySynStreamFrame) {
+
+ SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
+ boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
+ boolean localSideClosed = spdySynStreamFrame.isLast();
+ if (!acceptStream(spdySynStreamFrame.getStreamID(), remoteSideClosed, localSideClosed)) {
+ e.getFuture().setFailure(PROTOCOL_EXCEPTION);
+ return;
+ }
+
+ } else if (msg instanceof SpdySynReplyFrame) {
+
+ SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
+ int streamID = spdySynReplyFrame.getStreamID();
+
+ if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) {
+ e.getFuture().setFailure(PROTOCOL_EXCEPTION);
+ return;
+ }
+
+ if (spdySynReplyFrame.isLast()) {
+ halfCloseStream(streamID, false);
+ }
+
+ } else if (msg instanceof SpdyRstStreamFrame) {
+
+ SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
+ removeStream(spdyRstStreamFrame.getStreamID());
+
+ } else if (msg instanceof SpdySettingsFrame) {
+
+ SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
+ updateConcurrentStreams(spdySettingsFrame, false);
+
+ } else if (msg instanceof SpdyPingFrame) {
+
+ SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
+ if (isRemoteInitiatedID(spdyPingFrame.getID())) {
+ e.getFuture().setFailure(new IllegalArgumentException(
+ "invalid PING ID: " + spdyPingFrame.getID()));
+ return;
+ }
+ pings.getAndIncrement();
+
+ } else if (msg instanceof SpdyGoAwayFrame) {
+
+ // Should send a CLOSE ChannelStateEvent
+ e.getFuture().setFailure(PROTOCOL_EXCEPTION);
+ return;
+
+ } else if (msg instanceof SpdyHeadersFrame) {
+
+ SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
+ int streamID = spdyHeadersFrame.getStreamID();
+
+ if (spdySession.isLocalSideClosed(streamID)) {
+ e.getFuture().setFailure(PROTOCOL_EXCEPTION);
+ return;
+ }
+ }
+
+ ctx.sendDownstream(evt);
+ }
+
+ /*
+ * Error Handling
+ */
+
+ private void issueSessionError(
+ ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
+
+ ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress);
+ future.addListener(ChannelFutureListener.CLOSE);
+ }
+
+ // Send a RST_STREAM frame in response to an incoming MessageEvent
+ // Only called in the upstream direction
+ private void issueStreamError(
+ ChannelHandlerContext ctx, MessageEvent e, int streamID, SpdyStreamStatus status) {
+
+ removeStream(streamID);
+ SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamID, status);
+ Channels.write(ctx, Channels.future(e.getChannel()), spdyRstStreamFrame, e.getRemoteAddress());
+ }
+
+ /*
+ * Helper functions
+ */
+
+ private boolean isServerID(int ID) {
+ return ID % 2 == 0;
+ }
+
+ private boolean isRemoteInitiatedID(int ID) {
+ boolean serverID = isServerID(ID);
+ return (server && !serverID) || (!server && serverID);
+ }
+
+ private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) {
+ int newConcurrentStreams = settings.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
+ if (remote) {
+ remoteConcurrentStreams = newConcurrentStreams;
+ } else {
+ localConcurrentStreams = newConcurrentStreams;
+ }
+ if (localConcurrentStreams == remoteConcurrentStreams) {
+ maxConcurrentStreams = localConcurrentStreams;
+ return;
+ }
+ if (localConcurrentStreams == 0) {
+ maxConcurrentStreams = remoteConcurrentStreams;
+ return;
+ }
+ if (remoteConcurrentStreams == 0) {
+ maxConcurrentStreams = localConcurrentStreams;
+ return;
+ }
+ if (localConcurrentStreams > remoteConcurrentStreams) {
+ maxConcurrentStreams = remoteConcurrentStreams;
+ } else {
+ maxConcurrentStreams = localConcurrentStreams;
+ }
+ }
+
+ // need to synchronize accesses to sentGoAwayFrame and lastGoodStreamID
+ private synchronized boolean acceptStream(
+ int streamID, boolean remoteSideClosed, boolean localSideClosed) {
+ // Cannot initiate any new streams after receiving or sending GOAWAY
+ if (receivedGoAwayFrame || sentGoAwayFrame) {
+ return false;
+ }
+ if ((maxConcurrentStreams != 0) &&
+ (spdySession.numActiveStreams() >= maxConcurrentStreams)) {
+ return false;
+ }
+ spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed);
+ if (isRemoteInitiatedID(streamID)) {
+ lastGoodStreamID = streamID;
+ }
+ return true;
+ }
+
+ private void halfCloseStream(int streamID, boolean remote) {
+ if (remote) {
+ spdySession.closeRemoteSide(streamID);
+ } else {
+ spdySession.closeLocalSide(streamID);
+ }
+ if ((closeSessionFuture != null) && spdySession.noActiveStreams()) {
+ closeSessionFuture.setSuccess();
+ }
+ }
+
+ private void removeStream(int streamID) {
+ spdySession.removeStream(streamID);
+ if ((closeSessionFuture != null) && spdySession.noActiveStreams()) {
+ closeSessionFuture.setSuccess();
+ }
+ }
+
+ private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelStateEvent e) {
+ // Avoid NotYetConnectedException
+ if (!e.getChannel().isConnected()) {
+ ctx.sendDownstream(e);
+ return;
+ }
+
+ ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null);
+ if (spdySession.noActiveStreams()) {
+ future.addListener(new ClosingChannelFutureListener(ctx, e));
+ } else {
+ closeSessionFuture = Channels.future(e.getChannel());
+ closeSessionFuture.addListener(new ClosingChannelFutureListener(ctx, e));
+ }
+ }
+
+ private synchronized ChannelFuture sendGoAwayFrame(
+ ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
+ if (!sentGoAwayFrame) {
+ sentGoAwayFrame = true;
+ ChannelFuture future = Channels.future(channel);
+ Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID));
+ return future;
+ }
+ return Channels.succeededFuture(channel);
+ }
+
+ private static final class ClosingChannelFutureListener implements ChannelFutureListener {
+
+ private final ChannelHandlerContext ctx;
+ private final ChannelStateEvent e;
+
+ ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelStateEvent e) {
+ this.ctx = ctx;
+ this.e = e;
+ }
+
+ public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {
+ if (!(sentGoAwayFuture.getCause() instanceof ClosedChannelException)) {
+ Channels.close(ctx, e.getFuture());
+ } else {
+ e.getFuture().setSuccess();
+ }
+ }
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java
new file mode 100644
index 0000000000..39751cfcb9
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.Set;
+
+/**
+ * A SPDY Protocol SETTINGS Control Frame
+ */
+public interface SpdySettingsFrame {
+
+ int SETTINGS_UPLOAD_BANDWIDTH = 1;
+ int SETTINGS_DOWNLOAD_BANDWIDTH = 2;
+ int SETTINGS_ROUND_TRIP_TIME = 3;
+ int SETTINGS_MAX_CONCURRENT_STREAMS = 4;
+ int SETTINGS_CURRENT_CWND = 5;
+ int SETTINGS_DOWNLOAD_RETRANS_RATE = 6;
+ int SETTINGS_INITIAL_WINDOW_SIZE = 7;
+
+ /**
+ * Returns a {@code Set} of the setting IDs.
+ * The set's iterator will return the IDs in ascending order.
+ */
+ Set getIDs();
+
+ /**
+ * Returns {@code true} if the setting ID has a value.
+ */
+ boolean isSet(int ID);
+
+ /**
+ * Returns the value of the setting ID.
+ * Returns -1 if the setting ID is not set.
+ */
+ int getValue(int ID);
+
+ /**
+ * Sets the value of the setting ID.
+ * The ID must be positive and cannot exceeed 16777215.
+ */
+ void setValue(int ID, int value);
+
+ /**
+ * Sets the value of the setting ID.
+ * Sets if the setting should be persisted (should only be set by the server).
+ * Sets if the setting is persisted (should only be set by the client).
+ * The ID must be positive and cannot exceed 16777215.
+ */
+ void setValue(int ID, int value, boolean persistVal, boolean persisted);
+
+ /**
+ * Removes the value of the setting ID.
+ * Removes all persistance information for the setting.
+ */
+ void removeValue(int ID);
+
+ /**
+ * Returns {@code true} if this setting should be persisted.
+ * Returns {@code false} if this setting should not be persisted
+ * or if the setting ID has no value.
+ */
+ boolean persistValue(int ID);
+
+ /**
+ * Sets if this setting should be persisted.
+ * Has no effect if the setting ID has no value.
+ */
+ void setPersistValue(int ID, boolean persistValue);
+
+ /**
+ * Returns {@code true} if this setting is persisted.
+ * Returns {@code false} if this setting should not be persisted
+ * or if the setting ID has no value.
+ */
+ boolean isPersisted(int ID);
+
+ /**
+ * Sets if this setting is persisted.
+ * Has no effect if the setting ID has no value.
+ */
+ void setPersisted(int ID, boolean persisted);
+
+ /**
+ * Returns {@code true} if previously persisted settings should be cleared.
+ */
+ boolean clearPreviouslyPersistedSettings();
+
+ /**
+ * Sets if previously persisted settings should be cleared.
+ */
+ void setClearPreviouslyPersistedSettings(boolean clear);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java
new file mode 100644
index 0000000000..51272629d8
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * The SPDY stream status code and its description.
+ * @apiviz.exclude
+ */
+public class SpdyStreamStatus implements Comparable {
+
+ /**
+ * 1 Protocol Error
+ */
+ public static final SpdyStreamStatus PROTOCOL_ERROR =
+ new SpdyStreamStatus(1, "PROTOCOL_ERROR");
+
+ /**
+ * 2 Invalid Stream
+ */
+ public static final SpdyStreamStatus INVALID_STREAM =
+ new SpdyStreamStatus(2, "INVALID_STREAM");
+
+ /**
+ * 3 Refused Stream
+ */
+ public static final SpdyStreamStatus REFUSED_STREAM =
+ new SpdyStreamStatus(3, "REFUSED_STREAM");
+
+ /**
+ * 4 Unsupported Version
+ */
+ public static final SpdyStreamStatus UNSUPPORTED_VERSION =
+ new SpdyStreamStatus(4, "UNSUPPORTED_VERSION");
+
+ /**
+ * 5 Cancel
+ */
+ public static final SpdyStreamStatus CANCEL =
+ new SpdyStreamStatus(5, "CANCEL");
+
+ /**
+ * 6 Internal Error
+ */
+ public static final SpdyStreamStatus INTERNAL_ERROR =
+ new SpdyStreamStatus(6, "INTERNAL_ERROR");
+
+ /**
+ * 7 Flow Control Error
+ */
+ public static final SpdyStreamStatus FLOW_CONTROL_ERROR =
+ new SpdyStreamStatus(7, "FLOW_CONTROL_ERROR");
+
+ /**
+ * Returns the {@link SpdyStreamStatus} represented by the specified code.
+ * If the specified code is a defined SPDY status code, a cached instance
+ * will be returned. Otherwise, a new instance will be returned.
+ */
+ public static SpdyStreamStatus valueOf(int code) {
+ if (code == 0) {
+ throw new IllegalArgumentException(
+ "0 is not a valid status code for a RST_STREAM");
+ }
+
+ switch (code) {
+ case 1:
+ return PROTOCOL_ERROR;
+ case 2:
+ return INVALID_STREAM;
+ case 3:
+ return REFUSED_STREAM;
+ case 4:
+ return UNSUPPORTED_VERSION;
+ case 5:
+ return CANCEL;
+ case 6:
+ return INTERNAL_ERROR;
+ case 7:
+ return FLOW_CONTROL_ERROR;
+ }
+
+ return new SpdyStreamStatus(code, "UNKNOWN (" + code + ')');
+ }
+
+ private final int code;
+
+ private final String statusPhrase;
+
+ /**
+ * Creates a new instance with the specified {@code code} and its
+ * {@code statusPhrase}.
+ */
+ public SpdyStreamStatus(int code, String statusPhrase) {
+ if (code == 0) {
+ throw new IllegalArgumentException(
+ "0 is not a valid status code for a RST_STREAM");
+ }
+
+ if (statusPhrase == null) {
+ throw new NullPointerException("statusPhrase");
+ }
+
+ this.code = code;
+ this.statusPhrase = statusPhrase;
+ }
+
+ /**
+ * Returns the code of this status.
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * Returns the status phrase of this status.
+ */
+ public String getStatusPhrase() {
+ return statusPhrase;
+ }
+
+ @Override
+ public int hashCode() {
+ return getCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof SpdyStreamStatus)) {
+ return false;
+ }
+
+ return getCode() == ((SpdyStreamStatus) o).getCode();
+ }
+
+ @Override
+ public String toString() {
+ return getStatusPhrase();
+ }
+
+ public int compareTo(SpdyStreamStatus o) {
+ return getCode() - o.getCode();
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynReplyFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynReplyFrame.java
new file mode 100644
index 0000000000..e24bc844d7
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynReplyFrame.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol SYN_REPLY Control Frame
+ */
+public interface SpdySynReplyFrame extends SpdyHeaderBlock {
+
+ /**
+ * Returns the Stream-ID of this frame.
+ */
+ int getStreamID();
+
+ /**
+ * Sets the Stream-ID of this frame. The Stream-ID must be positive.
+ */
+ void setStreamID(int streamID);
+
+ /**
+ * Returns {@code true} if this frame is the last frame to be transmitted
+ * on the stream.
+ */
+ boolean isLast();
+
+ /**
+ * Sets if this frame is the last frame to be transmitted on the stream.
+ */
+ void setLast(boolean last);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java
new file mode 100644
index 0000000000..b8aa7de181
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+/**
+ * A SPDY Protocol SYN_STREAM Control Frame
+ */
+public interface SpdySynStreamFrame extends SpdyHeaderBlock {
+
+ /**
+ * Returns the Stream-ID of this frame.
+ */
+ int getStreamID();
+
+ /**
+ * Sets the Stream-ID of this frame. The Stream-ID must be positive.
+ */
+ void setStreamID(int streamID);
+
+ /**
+ * Returns the Associated-To-Stream-ID of this frame.
+ */
+ int getAssociatedToStreamID();
+
+ /**
+ * Sets the Associated-To-Stream-ID of this frame.
+ * The Associated-To-Stream-ID cannot be negative.
+ */
+ void setAssociatedToStreamID(int associatedToStreamID);
+
+ /**
+ * Returns the priority of the stream.
+ */
+ byte getPriority();
+
+ /**
+ * Sets the priority of the stream.
+ * The priority must be between 0 and 3 inclusive.
+ */
+ void setPriority(byte priority);
+
+ /**
+ * Returns {@code true} if this frame is the last frame to be transmitted
+ * on the stream.
+ */
+ boolean isLast();
+
+ /**
+ * Sets if this frame is the last frame to be transmitted on the stream.
+ */
+ void setLast(boolean last);
+
+ /**
+ * Returns {@code true} if the stream created with this frame is to be
+ * considered half-closed to the receiver.
+ */
+ boolean isUnidirectional();
+
+ /**
+ * Sets if the stream created with this frame is to be considered
+ * half-closed to the receiver.
+ */
+ void setUnidirectional(boolean unidirectional);
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/package-info.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/package-info.java
new file mode 100644
index 0000000000..3b97176307
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/package-info.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ */
+
+/**
+ * Encoder, decoder, session handler and their related message types for the SPDY protocol.
+ *
+ * @apiviz.exclude ^java\.lang\.
+ * @apiviz.exclude OneToOne(Encoder|Decoder)$
+ * @apiviz.exclude \.SpdyHeaders\.
+ * @apiviz.exclude \.codec\.frame\.
+ * @apiviz.exclude \.(Simple)?Channel[A-Za-z]*Handler$
+ * @apiviz.exclude \.Default
+ * @apiviz.exclude \.SpdyFrameCodec$
+ */
+package io.netty.handler.codec.spdy;
diff --git a/src/test/java/io/netty/handler/codec/http/CookieDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/CookieDecoderTest.java
similarity index 99%
rename from src/test/java/io/netty/handler/codec/http/CookieDecoderTest.java
rename to codec-http/src/test/java/io/netty/handler/codec/http/CookieDecoderTest.java
index b9499381d0..b853328e70 100644
--- a/src/test/java/io/netty/handler/codec/http/CookieDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/CookieDecoderTest.java
@@ -28,8 +28,6 @@ import java.util.Set;
import org.junit.Test;
-/**
- */
public class CookieDecoderTest {
@Test
public void testDecodingSingleCookieV0() {
diff --git a/src/test/java/io/netty/handler/codec/http/CookieEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/CookieEncoderTest.java
similarity index 99%
rename from src/test/java/io/netty/handler/codec/http/CookieEncoderTest.java
rename to codec-http/src/test/java/io/netty/handler/codec/http/CookieEncoderTest.java
index a924ac0d06..b274e44e35 100644
--- a/src/test/java/io/netty/handler/codec/http/CookieEncoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/CookieEncoderTest.java
@@ -24,9 +24,6 @@ import java.util.Date;
import org.junit.Test;
-
-/**
- */
public class CookieEncoderTest {
@Test
public void testEncodingSingleCookieV0() {
diff --git a/src/test/java/io/netty/handler/codec/http/DefaultHttpMessageTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpMessageTest.java
similarity index 99%
rename from src/test/java/io/netty/handler/codec/http/DefaultHttpMessageTest.java
rename to codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpMessageTest.java
index cb3945a1d6..a2d75940cf 100644
--- a/src/test/java/io/netty/handler/codec/http/DefaultHttpMessageTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpMessageTest.java
@@ -18,8 +18,6 @@ package io.netty.handler.codec.http;
import org.junit.Assert;
import org.junit.Test;
-/**
- */
public class DefaultHttpMessageTest {
@Test
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java
new file mode 100644
index 0000000000..0f103abd33
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java
@@ -0,0 +1,62 @@
+/*
+* Copyright 2011 The Netty Project
+*
+* The Netty Project licenses this file to you under the Apache License,
+* version 2.0 (the "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at:
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+*/
+package io.netty.handler.codec.http;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import io.netty.handler.codec.compression.ZlibWrapper;
+
+public class HttpContentCompressorTest {
+ @Test
+ public void testGetTargetContentEncoding() throws Exception {
+ HttpContentCompressor compressor = new HttpContentCompressor();
+
+ String[] tests = {
+ // Accept-Encoding -> Content-Encoding
+ "", null,
+ "*", "gzip",
+ "*;q=0.0", null,
+ "gzip", "gzip",
+ "compress, gzip;q=0.5", "gzip",
+ "gzip; q=0.5, identity", "gzip",
+ "gzip ; q=0.1", "gzip",
+ "gzip; q=0, deflate", "deflate",
+ " defalte ; q=0 , *;q=0.5", "gzip",
+ };
+ for (int i = 0; i < tests.length; i += 2) {
+ String acceptEncoding = tests[i];
+ String contentEncoding = tests[i + 1];
+ ZlibWrapper targetWrapper = compressor.determineWrapper(acceptEncoding);
+ String targetEncoding = null;
+ if (targetWrapper != null) {
+ switch (targetWrapper) {
+ case GZIP:
+ targetEncoding = "gzip";
+ break;
+ case ZLIB:
+ targetEncoding = "deflate";
+ break;
+ default:
+ if (targetWrapper != null) {
+ Assert.fail();
+ }
+ }
+ }
+ Assert.assertEquals(contentEncoding, targetEncoding);
+ }
+ }
+}
diff --git a/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatTest.java
similarity index 100%
rename from src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatTest.java
rename to codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatTest.java
diff --git a/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java
similarity index 86%
rename from src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java
rename to codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java
index 1a3e580ea7..14f93c73c0 100644
--- a/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java
@@ -15,12 +15,13 @@
*/
package io.netty.handler.codec.http;
+import java.util.List;
+import java.util.Map;
+
import io.netty.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Test;
-/**
- */
public class QueryStringDecoderTest {
@Test
@@ -93,6 +94,33 @@ public class QueryStringDecoderTest {
assertQueryString("/foo?a=b&c=d", "/foo?a=b&c=d");
assertQueryString("/foo?a=1&a=&a=", "/foo?a=1&a&a=");
}
+
+ @Test
+ public void testHashDos() throws Exception {
+ StringBuilder buf = new StringBuilder();
+ buf.append('?');
+ for (int i = 0; i < 65536; i ++) {
+ buf.append('k');
+ buf.append(i);
+ buf.append("=v");
+ buf.append(i);
+ buf.append('&');
+ }
+ Assert.assertEquals(1024, new QueryStringDecoder(buf.toString()).getParameters().size());
+ }
+
+ @Test
+ public void testHasPath() throws Exception {
+ QueryStringDecoder decoder = new QueryStringDecoder("1=2", false);
+ Assert.assertEquals("", decoder.getPath());
+ Map> params = decoder.getParameters();
+ Assert.assertEquals(1, params.size());
+ Assert.assertTrue(params.containsKey("1"));
+ List param = params.get("1");
+ Assert.assertNotNull(param);
+ Assert.assertEquals(1, param.size());
+ Assert.assertEquals("2", param.get(0));
+ }
@Test
public void testUrlDecoding() throws Exception {
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java
new file mode 100644
index 0000000000..8e98282542
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.replay;
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.channel.Channel;
+import io.netty.channel.DefaultChannelFuture;
+import io.netty.channel.DefaultChannelPipeline;
+import io.netty.handler.codec.http.DefaultHttpRequest;
+import io.netty.handler.codec.http.HttpChunkAggregator;
+import io.netty.handler.codec.http.HttpHeaders.Names;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+
+import java.nio.charset.Charset;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WebSocketServerHandshaker00Test {
+
+ private DefaultChannelPipeline createPipeline() {
+ DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
+ pipeline.addLast("chunkAggregator", new HttpChunkAggregator(42));
+ pipeline.addLast("wsdecoder", new HttpRequestDecoder());
+ pipeline.addLast("wsencoder", new HttpResponseEncoder());
+ return pipeline;
+ }
+
+ @Test
+ public void testPerformOpeningHandshake() {
+ Channel channelMock = EasyMock.createMock(Channel.class);
+
+ DefaultChannelPipeline pipeline = createPipeline();
+ EasyMock.expect(channelMock.getPipeline()).andReturn(pipeline);
+
+ // capture the http response in order to verify the headers
+ Capture res = new Capture();
+ EasyMock.expect(channelMock.write(capture(res))).andReturn(new DefaultChannelFuture(channelMock, true));
+
+ replay(channelMock);
+
+ HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
+ req.setHeader(Names.HOST, "server.example.com");
+ req.setHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
+ req.setHeader(Names.CONNECTION, "Upgrade");
+ req.setHeader(Names.ORIGIN, "http://example.com");
+ req.setHeader(Names.SEC_WEBSOCKET_KEY1, "4 @1 46546xW%0l 1 5");
+ req.setHeader(Names.SEC_WEBSOCKET_KEY2, "12998 5 Y3 1 .P00");
+ req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
+
+ ChannelBuffer buffer = ChannelBuffers.copiedBuffer("^n:ds[4U", Charset.defaultCharset());
+ req.setContent(buffer);
+
+ WebSocketServerHandshaker00 handsaker = new WebSocketServerHandshaker00("ws://example.com/chat", "chat");
+ handsaker.handshake(channelMock, req);
+
+ Assert.assertEquals("ws://example.com/chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_LOCATION));
+ Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
+ Assert.assertEquals("8jKS'y:G*Co,Wxa-", res.getValue().getContent().toString(Charset.defaultCharset()));
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java
new file mode 100644
index 0000000000..6ca8a495d1
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.replay;
+import io.netty.channel.Channel;
+import io.netty.channel.DefaultChannelFuture;
+import io.netty.channel.DefaultChannelPipeline;
+import io.netty.handler.codec.http.DefaultHttpRequest;
+import io.netty.handler.codec.http.HttpChunkAggregator;
+import io.netty.handler.codec.http.HttpHeaders.Names;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WebSocketServerHandshaker08Test {
+
+ private DefaultChannelPipeline createPipeline() {
+ DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
+ pipeline.addLast("chunkAggregator", new HttpChunkAggregator(42));
+ pipeline.addLast("requestDecoder", new HttpRequestDecoder());
+ pipeline.addLast("responseEncoder", new HttpResponseEncoder());
+ return pipeline;
+ }
+
+ @Test
+ public void testPerformOpeningHandshake() {
+ Channel channelMock = EasyMock.createMock(Channel.class);
+
+ DefaultChannelPipeline pipeline = createPipeline();
+ EasyMock.expect(channelMock.getPipeline()).andReturn(pipeline);
+
+ // capture the http response in order to verify the headers
+ Capture res = new Capture();
+ EasyMock.expect(channelMock.write(capture(res))).andReturn(new DefaultChannelFuture(channelMock, true));
+
+ replay(channelMock);
+
+ HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
+ req.setHeader(Names.HOST, "server.example.com");
+ req.setHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
+ req.setHeader(Names.CONNECTION, "Upgrade");
+ req.setHeader(Names.SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==");
+ req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, "http://example.com");
+ req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
+ req.setHeader(Names.SEC_WEBSOCKET_VERSION, "8");
+
+ WebSocketServerHandshaker08 handsaker = new WebSocketServerHandshaker08("ws://example.com/chat", "chat", false);
+ handsaker.handshake(channelMock, req);
+
+ Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
+ Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java
new file mode 100644
index 0000000000..257e484fa5
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http.websocketx;
+
+import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.replay;
+import io.netty.channel.Channel;
+import io.netty.channel.DefaultChannelFuture;
+import io.netty.channel.DefaultChannelPipeline;
+import io.netty.handler.codec.http.DefaultHttpRequest;
+import io.netty.handler.codec.http.HttpChunkAggregator;
+import io.netty.handler.codec.http.HttpHeaders.Names;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WebSocketServerHandshaker13Test {
+
+ private DefaultChannelPipeline createPipeline() {
+ DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
+ pipeline.addLast("chunkAggregator", new HttpChunkAggregator(42));
+ pipeline.addLast("requestDecoder", new HttpRequestDecoder());
+ pipeline.addLast("responseEncoder", new HttpResponseEncoder());
+ return pipeline;
+ }
+
+ @Test
+ public void testPerformOpeningHandshake() {
+ Channel channelMock = EasyMock.createMock(Channel.class);
+
+ DefaultChannelPipeline pipeline = createPipeline();
+ EasyMock.expect(channelMock.getPipeline()).andReturn(pipeline);
+
+ // capture the http response in order to verify the headers
+ Capture res = new Capture();
+ EasyMock.expect(channelMock.write(capture(res))).andReturn(new DefaultChannelFuture(channelMock, true));
+
+ replay(channelMock);
+
+ HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
+ req.setHeader(Names.HOST, "server.example.com");
+ req.setHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
+ req.setHeader(Names.CONNECTION, "Upgrade");
+ req.setHeader(Names.SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==");
+ req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, "http://example.com");
+ req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
+ req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
+ WebSocketServerHandshaker13 handsaker = new WebSocketServerHandshaker13("ws://example.com/chat", "chat", false);
+ handsaker.handshake(channelMock, req);
+
+ Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
+ Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java
new file mode 100644
index 0000000000..d5b2d42434
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.netty.bootstrap.ClientBootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBuffers;
+import io.netty.channel.Channel;
+import io.netty.channel.Channels;
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelStateEvent;
+import io.netty.channel.ExceptionEvent;
+import io.netty.channel.MessageEvent;
+import io.netty.channel.SimpleChannelUpstreamHandler;
+import io.netty.util.internal.ExecutorUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public abstract class AbstractSocketSpdyEchoTest {
+
+ private static final Random random = new Random();
+ static final ChannelBuffer frames = ChannelBuffers.buffer(1160);
+ static final int ignoredBytes = 20;
+
+ private static ExecutorService executor;
+
+ static {
+ // SPDY UNKNOWN Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(0xFFFF);
+ frames.writeByte(0xFF);
+ frames.writeMedium(4);
+ frames.writeInt(random.nextInt());
+
+ // SPDY NOOP Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(5);
+ frames.writeInt(0);
+
+ // SPDY Data Frame
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+ frames.writeByte(0x01);
+ frames.writeMedium(1024);
+ for (int i = 0; i < 256; i ++) {
+ frames.writeInt(random.nextInt());
+ }
+
+ // SPDY SYN_STREAM Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(1);
+ frames.writeByte(0x03);
+ frames.writeMedium(12);
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+ frames.writeShort(0x8000);
+ frames.writeShort(0);
+
+ // SPDY SYN_REPLY Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(2);
+ frames.writeByte(0x01);
+ frames.writeMedium(8);
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+ frames.writeInt(0);
+
+ // SPDY RST_STREAM Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(3);
+ frames.writeInt(8);
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+ frames.writeInt(random.nextInt() | 0x01);
+
+ // SPDY SETTINGS Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(4);
+ frames.writeByte(0x01);
+ frames.writeMedium(12);
+ frames.writeInt(1);
+ frames.writeMedium(random.nextInt());
+ frames.writeByte(0x03);
+ frames.writeInt(random.nextInt());
+
+ // SPDY PING Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(6);
+ frames.writeInt(4);
+ frames.writeInt(random.nextInt());
+
+ // SPDY GOAWAY Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(7);
+ frames.writeInt(4);
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+
+ // SPDY HEADERS Frame
+ frames.writeByte(0x80);
+ frames.writeByte(2);
+ frames.writeShort(8);
+ frames.writeInt(4);
+ frames.writeInt(random.nextInt() & 0x7FFFFFFF);
+ }
+
+ @BeforeClass
+ public static void init() {
+ executor = Executors.newCachedThreadPool();
+ }
+
+ @AfterClass
+ public static void destroy() {
+ ExecutorUtil.terminate(executor);
+ }
+
+ protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor);
+ protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor);
+
+ @Test
+ public void testSpdyEcho() throws Throwable {
+ ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor));
+ ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor));
+
+ EchoHandler sh = new EchoHandler(true);
+ EchoHandler ch = new EchoHandler(false);
+
+ sb.getPipeline().addLast("decoder", new SpdyFrameDecoder());
+ sb.getPipeline().addLast("encoder", new SpdyFrameEncoder());
+ sb.getPipeline().addLast("handler", sh);
+
+ cb.getPipeline().addLast("handler", ch);
+
+ Channel sc = sb.bind(new InetSocketAddress(0));
+ int port = ((InetSocketAddress) sc.getLocalAddress()).getPort();
+
+ ChannelFuture ccf = cb.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
+ assertTrue(ccf.awaitUninterruptibly().isSuccess());
+
+ Channel cc = ccf.getChannel();
+ cc.write(frames);
+
+ while (ch.counter < frames.writerIndex() - ignoredBytes) {
+ if (sh.exception.get() != null) {
+ break;
+ }
+ if (ch.exception.get() != null) {
+ break;
+ }
+
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+
+ sh.channel.close().awaitUninterruptibly();
+ ch.channel.close().awaitUninterruptibly();
+ sc.close().awaitUninterruptibly();
+
+ if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
+ throw sh.exception.get();
+ }
+ if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
+ throw ch.exception.get();
+ }
+ if (sh.exception.get() != null) {
+ throw sh.exception.get();
+ }
+ if (ch.exception.get() != null) {
+ throw ch.exception.get();
+ }
+ }
+
+ private class EchoHandler extends SimpleChannelUpstreamHandler {
+ volatile Channel channel;
+ final AtomicReference exception = new AtomicReference();
+ volatile int counter;
+ final boolean server;
+
+ EchoHandler(boolean server) {
+ super();
+ this.server = server;
+ }
+
+ @Override
+ public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ channel = e.getChannel();
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+ if (server) {
+ Channels.write(channel, e.getMessage(), e.getRemoteAddress());
+ } else {
+ ChannelBuffer m = (ChannelBuffer) e.getMessage();
+ byte[] actual = new byte[m.readableBytes()];
+ m.getBytes(0, actual);
+
+ int lastIdx = counter;
+ for (int i = 0; i < actual.length; i ++) {
+ assertEquals(frames.getByte(ignoredBytes + i + lastIdx), actual[i]);
+ }
+
+ counter += actual.length;
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+ throws Exception {
+ if (exception.compareAndSet(null, e.getCause())) {
+ e.getChannel().close();
+ }
+ }
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/NioNioSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/NioNioSocketSpdyEchoTest.java
new file mode 100644
index 0000000000..db4b2c194e
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/NioNioSocketSpdyEchoTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.concurrent.Executor;
+
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
+
+public class NioNioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
+
+ @Override
+ protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
+ return new NioClientSocketChannelFactory(executor, executor);
+ }
+
+ @Override
+ protected ChannelFactory newServerSocketChannelFactory(Executor executor) {
+ return new NioServerSocketChannelFactory(executor, executor);
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/NioOioSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/NioOioSocketSpdyEchoTest.java
new file mode 100644
index 0000000000..c9fa404320
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/NioOioSocketSpdyEchoTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.concurrent.Executor;
+
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import io.netty.channel.socket.oio.OioServerSocketChannelFactory;
+
+public class NioOioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
+
+ @Override
+ protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
+ return new NioClientSocketChannelFactory(executor, executor);
+ }
+
+ @Override
+ protected ChannelFactory newServerSocketChannelFactory(Executor executor) {
+ return new OioServerSocketChannelFactory(executor, executor);
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/OioNioSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/OioNioSocketSpdyEchoTest.java
new file mode 100644
index 0000000000..eea3409d42
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/OioNioSocketSpdyEchoTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.concurrent.Executor;
+
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import io.netty.channel.socket.oio.OioClientSocketChannelFactory;
+
+public class OioNioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
+
+ @Override
+ protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
+ return new OioClientSocketChannelFactory(executor);
+ }
+
+ @Override
+ protected ChannelFactory newServerSocketChannelFactory(Executor executor) {
+ return new NioServerSocketChannelFactory(executor, executor);
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/OioOioSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/OioOioSocketSpdyEchoTest.java
new file mode 100644
index 0000000000..423d6f0ffa
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/OioOioSocketSpdyEchoTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.concurrent.Executor;
+
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.socket.oio.OioClientSocketChannelFactory;
+import io.netty.channel.socket.oio.OioServerSocketChannelFactory;
+
+public class OioOioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
+
+ @Override
+ protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
+ return new OioClientSocketChannelFactory(executor);
+ }
+
+ @Override
+ protected ChannelFactory newServerSocketChannelFactory(Executor executor) {
+ return new OioServerSocketChannelFactory(executor, executor);
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java
new file mode 100644
index 0000000000..84875e9fcc
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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 io.netty.handler.codec.spdy;
+
+import java.util.List;
+import java.util.Map;
+
+import io.netty.channel.Channels;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelStateEvent;
+import io.netty.channel.MessageEvent;
+import io.netty.channel.SimpleChannelUpstreamHandler;
+import io.netty.handler.codec.embedder.DecoderEmbedder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SpdySessionHandlerTest {
+
+ private static final int closeSignal = SpdyCodecUtil.SPDY_SETTINGS_MAX_ID;
+ private static final SpdySettingsFrame closeMessage = new DefaultSpdySettingsFrame();
+
+ static {
+ closeMessage.setValue(closeSignal, 0);
+ }
+
+ private void assertHeaderBlock(SpdyHeaderBlock received, SpdyHeaderBlock expected) {
+ for (String name: expected.getHeaderNames()) {
+ List expectedValues = expected.getHeaders(name);
+ List receivedValues = received.getHeaders(name);
+ Assert.assertTrue(receivedValues.containsAll(expectedValues));
+ receivedValues.removeAll(expectedValues);
+ Assert.assertTrue(receivedValues.isEmpty());
+ received.removeHeader(name);
+ }
+ Assert.assertTrue(received.getHeaders().isEmpty());
+ }
+
+ private void assertDataFrame(Object msg, int streamID, boolean last) {
+ Assert.assertNotNull(msg);
+ Assert.assertTrue(msg instanceof SpdyDataFrame);
+ SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
+ Assert.assertTrue(spdyDataFrame.getStreamID() == streamID);
+ Assert.assertTrue(spdyDataFrame.isLast() == last);
+ }
+
+ private void assertSynReply(Object msg, int streamID, boolean last, SpdyHeaderBlock headers) {
+ Assert.assertNotNull(msg);
+ Assert.assertTrue(msg instanceof SpdySynReplyFrame);
+ SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
+ Assert.assertTrue(spdySynReplyFrame.getStreamID() == streamID);
+ Assert.assertTrue(spdySynReplyFrame.isLast() == last);
+ assertHeaderBlock(spdySynReplyFrame, headers);
+ }
+
+ private void assertRstStream(Object msg, int streamID, SpdyStreamStatus status) {
+ Assert.assertNotNull(msg);
+ Assert.assertTrue(msg instanceof SpdyRstStreamFrame);
+ SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
+ Assert.assertTrue(spdyRstStreamFrame.getStreamID() == streamID);
+ Assert.assertTrue(spdyRstStreamFrame.getStatus().equals(status));
+ }
+
+ private void assertPing(Object msg, int ID) {
+ Assert.assertNotNull(msg);
+ Assert.assertTrue(msg instanceof SpdyPingFrame);
+ SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
+ Assert.assertTrue(spdyPingFrame.getID() == ID);
+ }
+
+ private void assertGoAway(Object msg, int lastGoodStreamID) {
+ Assert.assertNotNull(msg);
+ Assert.assertTrue(msg instanceof SpdyGoAwayFrame);
+ SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
+ Assert.assertTrue(spdyGoAwayFrame.getLastGoodStreamID() == lastGoodStreamID);
+ }
+
+ private void assertHeaders(Object msg, int streamID, SpdyHeaderBlock headers) {
+ Assert.assertNotNull(msg);
+ Assert.assertTrue(msg instanceof SpdyHeadersFrame);
+ SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
+ Assert.assertTrue(spdyHeadersFrame.getStreamID() == streamID);
+ assertHeaderBlock(spdyHeadersFrame, headers);
+ }
+
+ private void testSpdySessionHandler(boolean server) {
+ DecoderEmbedder