SPDY: add SPDY/3 support
This commit is contained in:
parent
f60997686d
commit
1a28d6119f
@ -23,6 +23,7 @@ import io.netty.util.internal.StringUtil;
|
||||
public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
|
||||
|
||||
private int lastGoodStreamID;
|
||||
private SpdySessionStatus status;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -30,7 +31,28 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID) {
|
||||
this(lastGoodStreamID, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
* @param statusCode the Status code of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID, int statusCode) {
|
||||
this(lastGoodStreamID, SpdySessionStatus.valueOf(statusCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
* @param status the status of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID, SpdySessionStatus status) {
|
||||
setLastGoodStreamID(lastGoodStreamID);
|
||||
setStatus(status);
|
||||
}
|
||||
|
||||
public int getLastGoodStreamID() {
|
||||
@ -45,6 +67,14 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
|
||||
this.lastGoodStreamID = lastGoodStreamID;
|
||||
}
|
||||
|
||||
public SpdySessionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(SpdySessionStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
@ -52,6 +82,9 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Last-good-stream-ID = ");
|
||||
buf.append(lastGoodStreamID);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Status: ");
|
||||
buf.append(status.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
|
||||
implements SpdyHeadersFrame {
|
||||
|
||||
private int streamID;
|
||||
private boolean last;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -47,10 +48,21 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
|
||||
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);
|
||||
|
@ -74,9 +74,9 @@ public class DefaultSpdySynStreamFrame extends DefaultSpdyHeaderBlock
|
||||
}
|
||||
|
||||
public void setPriority(byte priority) {
|
||||
if (priority < 0 || priority > 3) {
|
||||
if (priority < 0 || priority > 7) {
|
||||
throw new IllegalArgumentException(
|
||||
"Priortiy must be between 0 and 3 inclusive: " + priority);
|
||||
"Priority must be between 0 and 7 inclusive: " + priority);
|
||||
}
|
||||
this.priority = priority;
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
final class SpdyCodecUtil {
|
||||
|
||||
static final int SPDY_VERSION = 2;
|
||||
static final int SPDY_MIN_VERSION = 2;
|
||||
static final int SPDY_MAX_VERSION = 3;
|
||||
|
||||
static final int SPDY_HEADER_TYPE_OFFSET = 2;
|
||||
static final int SPDY_HEADER_FLAGS_OFFSET = 4;
|
||||
@ -39,6 +40,7 @@ final class SpdyCodecUtil {
|
||||
static final int SPDY_GOAWAY_FRAME = 7;
|
||||
static final int SPDY_HEADERS_FRAME = 8;
|
||||
static final int SPDY_WINDOW_UPDATE_FRAME = 9;
|
||||
static final int SPDY_CREDENTIAL_FRAME = 10;
|
||||
|
||||
static final byte SPDY_FLAG_FIN = 0x01;
|
||||
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;
|
||||
@ -52,7 +54,188 @@ final class SpdyCodecUtil {
|
||||
static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field
|
||||
|
||||
// Zlib Dictionary
|
||||
private static final String SPDY_DICT_S =
|
||||
static final byte[] SPDY_DICT = {
|
||||
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i
|
||||
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h
|
||||
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p
|
||||
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e
|
||||
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - -
|
||||
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - -
|
||||
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t -
|
||||
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
|
||||
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e
|
||||
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c
|
||||
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - -
|
||||
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l
|
||||
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e -
|
||||
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
|
||||
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s
|
||||
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e -
|
||||
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w
|
||||
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o
|
||||
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c
|
||||
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r
|
||||
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n
|
||||
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e
|
||||
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - -
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t -
|
||||
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e
|
||||
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g
|
||||
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o
|
||||
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - -
|
||||
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
|
||||
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - -
|
||||
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t
|
||||
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - -
|
||||
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
|
||||
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - -
|
||||
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - -
|
||||
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - -
|
||||
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t
|
||||
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i
|
||||
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f
|
||||
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i
|
||||
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h -
|
||||
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o
|
||||
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s
|
||||
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - -
|
||||
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e -
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - -
|
||||
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g
|
||||
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f -
|
||||
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i
|
||||
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e
|
||||
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t
|
||||
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e
|
||||
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - -
|
||||
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r
|
||||
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - -
|
||||
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a -
|
||||
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - -
|
||||
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - -
|
||||
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - -
|
||||
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r
|
||||
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r
|
||||
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r -
|
||||
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e
|
||||
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e -
|
||||
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l
|
||||
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r
|
||||
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e
|
||||
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g -
|
||||
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a
|
||||
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s
|
||||
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t
|
||||
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y
|
||||
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a -
|
||||
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i
|
||||
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w
|
||||
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - -
|
||||
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d
|
||||
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t -
|
||||
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u
|
||||
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0
|
||||
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - -
|
||||
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1
|
||||
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r
|
||||
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b
|
||||
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s
|
||||
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i
|
||||
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e
|
||||
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e -
|
||||
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i
|
||||
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2
|
||||
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5
|
||||
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0
|
||||
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3
|
||||
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7
|
||||
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0
|
||||
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4
|
||||
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1
|
||||
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1
|
||||
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4
|
||||
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4
|
||||
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N
|
||||
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o
|
||||
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e
|
||||
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 -
|
||||
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e
|
||||
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o
|
||||
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m
|
||||
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4
|
||||
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0
|
||||
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0
|
||||
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d
|
||||
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N
|
||||
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d
|
||||
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r
|
||||
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o
|
||||
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t
|
||||
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e
|
||||
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 -
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e -
|
||||
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a
|
||||
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F
|
||||
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A
|
||||
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J
|
||||
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A
|
||||
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t -
|
||||
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v -
|
||||
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0
|
||||
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n
|
||||
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W
|
||||
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u -
|
||||
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a
|
||||
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - -
|
||||
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k
|
||||
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t -
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a
|
||||
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i
|
||||
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g
|
||||
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g
|
||||
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
|
||||
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l
|
||||
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l
|
||||
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t
|
||||
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r
|
||||
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l
|
||||
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t
|
||||
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e
|
||||
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e
|
||||
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d
|
||||
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e
|
||||
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c
|
||||
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i
|
||||
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 -
|
||||
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - -
|
||||
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 -
|
||||
};
|
||||
|
||||
private static final String SPDY2_DICT_S =
|
||||
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
|
||||
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
|
||||
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
|
||||
@ -66,19 +249,19 @@ final class SpdyCodecUtil {
|
||||
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
|
||||
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
|
||||
".1statusversionurl ";
|
||||
static final byte[] SPDY_DICT;
|
||||
static final byte[] SPDY2_DICT;
|
||||
static {
|
||||
byte[] SPDY_DICT_ = null;
|
||||
byte[] SPDY2_DICT_ = null;
|
||||
|
||||
try {
|
||||
SPDY_DICT_ = SPDY_DICT_S.getBytes("US-ASCII");
|
||||
SPDY2_DICT_ = SPDY2_DICT_S.getBytes("US-ASCII");
|
||||
// dictionary is null terminated
|
||||
SPDY_DICT_[SPDY_DICT_.length - 1] = (byte) 0;
|
||||
SPDY2_DICT_[SPDY2_DICT_.length - 1] = (byte) 0;
|
||||
} catch (Exception e) {
|
||||
SPDY_DICT_ = new byte[1];
|
||||
SPDY2_DICT_ = new byte[1];
|
||||
}
|
||||
|
||||
SPDY_DICT = SPDY_DICT_;
|
||||
SPDY2_DICT = SPDY2_DICT_;
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,23 +32,24 @@ public class SpdyFrameCodec implements ChannelUpstreamHandler,
|
||||
private final SpdyFrameEncoder encoder;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default decoder and encoder options
|
||||
* Creates a new instance with the specified {@code version} and
|
||||
* the default decoder and encoder options
|
||||
* ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
|
||||
* {@code compressionLevel (6)}, {@code windowBits (15)}, and
|
||||
* {@code memLevel (8)}).
|
||||
* {@code compressionLevel (6)}, {@code windowBits (15)},
|
||||
* and {@code memLevel (8)}).
|
||||
*/
|
||||
public SpdyFrameCodec() {
|
||||
this(8192, 16384, 6, 15, 8);
|
||||
public SpdyFrameCodec(int version) {
|
||||
this(version, 8192, 16384, 6, 15, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
int maxChunkSize, int maxHeaderSize,
|
||||
int version, int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel) {
|
||||
decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize);
|
||||
encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel);
|
||||
decoder = new SpdyFrameDecoder(version, maxChunkSize, maxHeaderSize);
|
||||
encoder = new SpdyFrameEncoder(version, compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
|
||||
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
|
||||
|
@ -30,11 +30,11 @@ import io.netty.handler.codec.frame.TooLongFrameException;
|
||||
*/
|
||||
public class SpdyFrameDecoder extends FrameDecoder {
|
||||
|
||||
private final int spdyVersion;
|
||||
private final int maxChunkSize;
|
||||
private final int maxHeaderSize;
|
||||
|
||||
private final SpdyHeaderBlockDecompressor headerBlockDecompressor =
|
||||
SpdyHeaderBlockDecompressor.newInstance();
|
||||
private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
|
||||
|
||||
private State state;
|
||||
private SpdySettingsFrame spdySettingsFrame;
|
||||
@ -64,18 +64,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default {@code maxChunkSize (8192)}
|
||||
* and {@code maxHeaderSize (16384)}.
|
||||
* Creates a new instance with the specified {@code version} and the default
|
||||
* {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}.
|
||||
*/
|
||||
public SpdyFrameDecoder() {
|
||||
this(8192, 16384);
|
||||
public SpdyFrameDecoder(int version) {
|
||||
this(version, 8192, 16384);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) {
|
||||
public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
|
||||
super(false);
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (maxChunkSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxChunkSize must be a positive integer: " + maxChunkSize);
|
||||
@ -84,8 +88,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
throw new IllegalArgumentException(
|
||||
"maxHeaderSize must be a positive integer: " + maxHeaderSize);
|
||||
}
|
||||
spdyVersion = version;
|
||||
this.maxChunkSize = maxChunkSize;
|
||||
this.maxHeaderSize = maxHeaderSize;
|
||||
headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
@ -109,7 +115,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
case READ_COMMON_HEADER:
|
||||
state = readCommonHeader(buffer);
|
||||
if (state == State.FRAME_ERROR) {
|
||||
if (version != SPDY_VERSION) {
|
||||
if (version != spdyVersion) {
|
||||
fireProtocolException(ctx, "Unsupported version: " + version);
|
||||
} else {
|
||||
fireInvalidControlFrameException(ctx);
|
||||
@ -175,13 +181,21 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
|
||||
int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
|
||||
for (int i = 0; i < readableEntries; i ++) {
|
||||
int ID;
|
||||
byte ID_flags;
|
||||
if (version < 3) {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Read id assuming the architecture is little endian
|
||||
int ID = buffer.readByte() & 0xFF |
|
||||
ID = buffer.readByte() & 0xFF |
|
||||
(buffer.readByte() & 0xFF) << 8 |
|
||||
(buffer.readByte() & 0xFF) << 16;
|
||||
byte ID_flags = buffer.readByte();
|
||||
ID_flags = buffer.readByte();
|
||||
} else {
|
||||
ID_flags = buffer.readByte();
|
||||
ID = getUnsignedMedium(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(3);
|
||||
}
|
||||
int value = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
@ -327,7 +341,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
type = getUnsignedShort(buffer, typeOffset);
|
||||
|
||||
// Check version first then validity
|
||||
if (version != SPDY_VERSION || !isValidControlFrameHeader()) {
|
||||
if (version != spdyVersion || !isValidControlFrameHeader()) {
|
||||
return State.FRAME_ERROR;
|
||||
}
|
||||
|
||||
@ -364,6 +378,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
|
||||
private Object readControlFrame(ChannelBuffer buffer) {
|
||||
int streamID;
|
||||
int statusCode;
|
||||
switch (type) {
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
@ -371,7 +386,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
|
||||
@ -387,14 +402,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return new DefaultSpdyPingFrame(ID);
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
int minLength = version < 3 ? 4 : 8;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
if (version < 3) {
|
||||
return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
|
||||
}
|
||||
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
return new DefaultSpdyGoAwayFrame(lastGoodStreamID, statusCode);
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
@ -413,22 +436,27 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
}
|
||||
|
||||
private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) {
|
||||
int minLength;
|
||||
int streamID;
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 12) {
|
||||
minLength = version < 3 ? 12 : 10;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int offset = buffer.readerIndex();
|
||||
streamID = getUnsignedInt(buffer, offset);
|
||||
int associatedToStreamID = getUnsignedInt(buffer, offset + 4);
|
||||
byte priority = (byte) (buffer.getByte(offset + 8) >> 6 & 0x03);
|
||||
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
|
||||
if (version < 3) {
|
||||
priority >>= 1;
|
||||
}
|
||||
buffer.skipBytes(10);
|
||||
length -= 10;
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
@ -441,16 +469,23 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return spdySynStreamFrame;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
minLength = version < 3 ? 8 : 4;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(6);
|
||||
length -= 6;
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// SPDY/2 has 16-bits of unused space
|
||||
if (version < 3) {
|
||||
buffer.skipBytes(2);
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
@ -461,9 +496,12 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return spdySynReplyFrame;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
// Protocol allows length 4 frame when there are no name/value pairs
|
||||
int minLength = length == 4 ? 4 : 8;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// SPDY/2 allows length 4 frame when there are no name/value pairs
|
||||
if (version < 3 && length > 4 && buffer.readableBytes() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -471,16 +509,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (length == 4 && buffer.getShort(buffer.readerIndex() + 2) == 0) {
|
||||
buffer.skipBytes(4);
|
||||
length = 0;
|
||||
} else if (length != 0) {
|
||||
// SPDY/2 has 16-bits of unused space
|
||||
if (version < 3 && length != 0) {
|
||||
buffer.skipBytes(2);
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
return new DefaultSpdyHeadersFrame(streamID);
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
|
||||
spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
|
||||
return spdyHeadersFrame;
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
@ -497,7 +541,11 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
}
|
||||
|
||||
private int readLengthField() {
|
||||
if (version < 3) {
|
||||
return decompressed.readUnsignedShort();
|
||||
} else {
|
||||
return decompressed.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
|
||||
@ -519,7 +567,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return;
|
||||
}
|
||||
|
||||
int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields
|
||||
int lengthFieldSize = version < 3 ? 2 : 4;
|
||||
|
||||
if (numHeaders == -1) {
|
||||
// Read number of Name/Value pairs
|
||||
@ -527,6 +575,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return;
|
||||
}
|
||||
numHeaders = readLengthField();
|
||||
if (numHeaders < 0) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (numHeaders > 0) {
|
||||
@ -542,7 +594,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
int nameLength = readLengthField();
|
||||
|
||||
// Recipients of a zero-length name must issue a stream error
|
||||
if (nameLength == 0) {
|
||||
if (nameLength <= 0) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
@ -577,7 +629,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
int valueLength = readLengthField();
|
||||
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
if (valueLength == 0) {
|
||||
if (valueLength <= 0) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
@ -630,10 +682,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
private boolean isValidControlFrameHeader() {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
return length >= 12;
|
||||
return version < 3 ? length >= 12 : length >= 10;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
return length >= 8;
|
||||
return version < 3 ? length >= 8 : length >= 4;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
return flags == 0 && length == 8;
|
||||
@ -648,14 +700,19 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return length == 4;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
return length == 4;
|
||||
return version < 3 ? length == 4 : length == 8;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
if (version < 3) {
|
||||
return length == 4 || length >= 8;
|
||||
} else {
|
||||
return length >= 4;
|
||||
}
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return length == 8;
|
||||
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@ -674,6 +731,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
return true;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -717,6 +775,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
message = "Received invalid WINDOW_UPDATE control frame";
|
||||
break;
|
||||
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
message = "Received invalid CREDENTIAL control frame";
|
||||
break;
|
||||
}
|
||||
fireProtocolException(ctx, message);
|
||||
}
|
||||
|
@ -33,23 +33,31 @@ import io.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
*/
|
||||
public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
|
||||
private final int version;
|
||||
private volatile boolean finished;
|
||||
private final SpdyHeaderBlockCompressor headerBlockCompressor;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default {@code compressionLevel (6)},
|
||||
* {@code windowBits (15)}, and {@code memLevel (8)}.
|
||||
* Creates a new instance with the specified {@code version} and the
|
||||
* default {@code compressionLevel (6)}, {@code windowBits (15)},
|
||||
* and {@code memLevel (8)}.
|
||||
*/
|
||||
public SpdyFrameEncoder() {
|
||||
this(6, 15, 8);
|
||||
public SpdyFrameEncoder(int version) {
|
||||
this(version, 6, 15, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameEncoder(int compressionLevel, int windowBits, int memLevel) {
|
||||
public SpdyFrameEncoder(int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
super();
|
||||
headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(compressionLevel, windowBits, memLevel);
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unknown version: " + version);
|
||||
}
|
||||
this.version = version;
|
||||
headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(
|
||||
version, compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,23 +101,37 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdySynStreamFrame));
|
||||
encodeHeaderBlock(version, 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;
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength;
|
||||
} else {
|
||||
length = 10 + headerBlockLength;
|
||||
}
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_SYN_STREAM_FRAME);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(spdySynStreamFrame.getStreamID());
|
||||
frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID());
|
||||
frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 14);
|
||||
if (data.readableBytes() == 0) {
|
||||
if (version < 3) {
|
||||
// Restrict priorities for SPDY/2 to between 0 and 3
|
||||
byte priority = spdySynStreamFrame.getPriority();
|
||||
if (priority > 3) {
|
||||
priority = 3;
|
||||
}
|
||||
frame.writeShort((priority & 0xFF) << 14);
|
||||
} else {
|
||||
frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 13);
|
||||
}
|
||||
if (version < 3 && data.readableBytes() == 0) {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
return ChannelBuffers.wrappedBuffer(frame, data);
|
||||
@ -118,22 +140,29 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdySynReplyFrame));
|
||||
encodeHeaderBlock(version, spdySynReplyFrame));
|
||||
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength;
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength;
|
||||
} else {
|
||||
length = 4 + headerBlockLength;
|
||||
}
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_SYN_REPLY_FRAME);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(spdySynReplyFrame.getStreamID());
|
||||
if (version < 3) {
|
||||
if (data.readableBytes() == 0) {
|
||||
frame.writeInt(0);
|
||||
} else {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
}
|
||||
return ChannelBuffers.wrappedBuffer(frame, data);
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
@ -141,7 +170,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_RST_STREAM_FRAME);
|
||||
frame.writeInt(8);
|
||||
frame.writeInt(spdyRstStreamFrame.getStreamID());
|
||||
@ -158,7 +187,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
int length = 4 + numEntries * 8;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_SETTINGS_FRAME);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
@ -172,6 +201,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
if (spdySettingsFrame.isPersisted(id)) {
|
||||
ID_flags |= SPDY_SETTINGS_PERSISTED;
|
||||
}
|
||||
if (version < 3) {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Write id assuming the architecture is little endian
|
||||
@ -179,6 +209,10 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
frame.writeByte(id >> 8 & 0xFF);
|
||||
frame.writeByte(id >> 16 & 0xFF);
|
||||
frame.writeByte(ID_flags);
|
||||
} else {
|
||||
frame.writeByte(ID_flags);
|
||||
frame.writeMedium(id);
|
||||
}
|
||||
frame.writeInt(spdySettingsFrame.getValue(id));
|
||||
}
|
||||
return frame;
|
||||
@ -187,7 +221,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_NOOP_FRAME);
|
||||
frame.writeInt(0);
|
||||
return frame;
|
||||
@ -197,7 +231,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_PING_FRAME);
|
||||
frame.writeInt(4);
|
||||
frame.writeInt(spdyPingFrame.getID());
|
||||
@ -206,28 +240,39 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
|
||||
int length = version < 3 ? 4 : 8;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_GOAWAY_FRAME);
|
||||
frame.writeInt(4);
|
||||
frame.writeInt(length);
|
||||
frame.writeInt(spdyGoAwayFrame.getLastGoodStreamID());
|
||||
if (version >= 3) {
|
||||
frame.writeInt(spdyGoAwayFrame.getStatus().getCode());
|
||||
}
|
||||
return frame;
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdyHeadersFrame));
|
||||
encodeHeaderBlock(version, spdyHeadersFrame));
|
||||
byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength;
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength;
|
||||
} else {
|
||||
length = 4 + headerBlockLength;
|
||||
}
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_HEADERS_FRAME);
|
||||
frame.writeInt(length);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(spdyHeadersFrame.getStreamID());
|
||||
if (data.readableBytes() != 0) {
|
||||
if (version < 3 && data.readableBytes() != 0) {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
return ChannelBuffers.wrappedBuffer(frame, data);
|
||||
@ -237,7 +282,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(version | 0x8000);
|
||||
frame.writeShort(SPDY_WINDOW_UPDATE_FRAME);
|
||||
frame.writeInt(8);
|
||||
frame.writeInt(spdyWindowUpdateFrame.getStreamID());
|
||||
@ -249,7 +294,23 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
return msg;
|
||||
}
|
||||
|
||||
private static ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame)
|
||||
private static void writeLengthField(int version, ChannelBuffer buffer, int length) {
|
||||
if (version < 3) {
|
||||
buffer.writeShort(length);
|
||||
} else {
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLengthField(int version, ChannelBuffer buffer, int writerIndex, int length) {
|
||||
if (version < 3) {
|
||||
buffer.setShort(writerIndex, length);
|
||||
} else {
|
||||
buffer.setInt(writerIndex, length);
|
||||
}
|
||||
}
|
||||
|
||||
private static ChannelBuffer encodeHeaderBlock(int version, SpdyHeaderBlock headerFrame)
|
||||
throws Exception {
|
||||
Set<String> names = headerFrame.getHeaderNames();
|
||||
int numHeaders = names.size();
|
||||
@ -262,14 +323,14 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
}
|
||||
ChannelBuffer headerBlock = ChannelBuffers.dynamicBuffer(
|
||||
ByteOrder.BIG_ENDIAN, 256);
|
||||
headerBlock.writeShort(numHeaders);
|
||||
writeLengthField(version, headerBlock, numHeaders);
|
||||
for (String name: names) {
|
||||
byte[] nameBytes = name.getBytes("UTF-8");
|
||||
headerBlock.writeShort(nameBytes.length);
|
||||
writeLengthField(version, headerBlock, nameBytes.length);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
int savedIndex = headerBlock.writerIndex();
|
||||
int valueLength = 0;
|
||||
headerBlock.writeShort(valueLength);
|
||||
writeLengthField(version, headerBlock, valueLength);
|
||||
for (String value: headerFrame.getHeaders(name)) {
|
||||
byte[] valueBytes = value.getBytes("UTF-8");
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
@ -281,7 +342,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
throw new IllegalArgumentException(
|
||||
"header exceeds allowable length: " + name);
|
||||
}
|
||||
headerBlock.setShort(savedIndex, valueLength);
|
||||
setLengthField(version, headerBlock, savedIndex, valueLength);
|
||||
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
|
||||
}
|
||||
return headerBlock;
|
||||
|
@ -30,4 +30,14 @@ public interface SpdyGoAwayFrame {
|
||||
* cannot be negative.
|
||||
*/
|
||||
void setLastGoodStreamID(int lastGoodStreamID);
|
||||
|
||||
/**
|
||||
* Returns the status of this frame.
|
||||
*/
|
||||
SpdySessionStatus getStatus();
|
||||
|
||||
/**
|
||||
* Sets the status of this frame.
|
||||
*/
|
||||
void setStatus(SpdySessionStatus status);
|
||||
}
|
||||
|
@ -21,13 +21,14 @@ import io.netty.util.internal.DetectionUtil;
|
||||
abstract class SpdyHeaderBlockCompressor {
|
||||
|
||||
static SpdyHeaderBlockCompressor newInstance(
|
||||
int compressionLevel, int windowBits, int memLevel) {
|
||||
int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
|
||||
if (DetectionUtil.javaVersion() >= 7) {
|
||||
return new SpdyHeaderBlockZlibCompressor(compressionLevel);
|
||||
return new SpdyHeaderBlockZlibCompressor(
|
||||
version, compressionLevel);
|
||||
} else {
|
||||
return new SpdyHeaderBlockJZlibCompressor(
|
||||
compressionLevel, windowBits, memLevel);
|
||||
version, compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,8 @@ import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
abstract class SpdyHeaderBlockDecompressor {
|
||||
|
||||
static SpdyHeaderBlockDecompressor newInstance() {
|
||||
return new SpdyHeaderBlockZlibDecompressor();
|
||||
static SpdyHeaderBlockDecompressor newInstance(int version) {
|
||||
return new SpdyHeaderBlockZlibDecompressor(version);
|
||||
}
|
||||
|
||||
abstract void setInput(ChannelBuffer compressed);
|
||||
|
@ -26,7 +26,12 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
|
||||
private final ZStream z = new ZStream();
|
||||
|
||||
public SpdyHeaderBlockJZlibCompressor(int compressionLevel, int windowBits, int memLevel) {
|
||||
public SpdyHeaderBlockJZlibCompressor(
|
||||
int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
@ -45,8 +50,12 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException(
|
||||
"failed to initialize an SPDY header block deflater: " + resultCode);
|
||||
} else {
|
||||
if (version < 3) {
|
||||
resultCode = z.deflateSetDictionary(SPDY2_DICT, SPDY2_DICT.length);
|
||||
} else {
|
||||
resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length);
|
||||
}
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException(
|
||||
"failed to set the SPDY dictionary: " + resultCode);
|
||||
|
@ -26,14 +26,22 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
private final byte[] out = new byte[8192];
|
||||
private final Deflater compressor;
|
||||
|
||||
public SpdyHeaderBlockZlibCompressor(int compressionLevel) {
|
||||
public SpdyHeaderBlockZlibCompressor(int version, int compressionLevel) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
}
|
||||
compressor = new Deflater(compressionLevel);
|
||||
if (version < 3) {
|
||||
compressor.setDictionary(SPDY2_DICT);
|
||||
} else {
|
||||
compressor.setDictionary(SPDY_DICT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ChannelBuffer decompressed) {
|
||||
|
@ -24,9 +24,18 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
|
||||
|
||||
private final int version;
|
||||
private final byte[] out = new byte[8192];
|
||||
private final Inflater decompressor = new Inflater();
|
||||
|
||||
public SpdyHeaderBlockZlibDecompressor(int version) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ChannelBuffer compressed) {
|
||||
byte[] in = new byte[compressed.readableBytes()];
|
||||
@ -39,7 +48,11 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
|
||||
try {
|
||||
int numBytes = decompressor.inflate(out);
|
||||
if (numBytes == 0 && decompressor.needsDictionary()) {
|
||||
if (version < 3) {
|
||||
decompressor.setDictionary(SPDY2_DICT);
|
||||
} else {
|
||||
decompressor.setDictionary(SPDY_DICT);
|
||||
}
|
||||
numBytes = decompressor.inflate(out);
|
||||
}
|
||||
decompressed.writeBytes(out, 0, numBytes);
|
||||
|
@ -27,7 +27,7 @@ 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}.
|
||||
* used utility methods that access a {@link SpdyHeaderBlock}.
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public class SpdyHeaders {
|
||||
@ -37,6 +37,41 @@ public class SpdyHeaders {
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public static final class HttpNames {
|
||||
/**
|
||||
* {@code ":host"}
|
||||
*/
|
||||
public static final String HOST = ":host";
|
||||
/**
|
||||
* {@code ":method"}
|
||||
*/
|
||||
public static final String METHOD = ":method";
|
||||
/**
|
||||
* {@code ":path"}
|
||||
*/
|
||||
public static final String PATH = ":path";
|
||||
/**
|
||||
* {@code ":scheme"}
|
||||
*/
|
||||
public static final String SCHEME = ":scheme";
|
||||
/**
|
||||
* {@code ":status"}
|
||||
*/
|
||||
public static final String STATUS = ":status";
|
||||
/**
|
||||
* {@code ":version"}
|
||||
*/
|
||||
public static final String VERSION = ":version";
|
||||
|
||||
private HttpNames() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SPDY/2 HTTP header names
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public static final class Spdy2HttpNames {
|
||||
/**
|
||||
* {@code "method"}
|
||||
*/
|
||||
@ -58,7 +93,7 @@ public class SpdyHeaders {
|
||||
*/
|
||||
public static final String VERSION = "version";
|
||||
|
||||
private HttpNames() {
|
||||
private Spdy2HttpNames() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@ -115,64 +150,118 @@ public class SpdyHeaders {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "method"} header.
|
||||
* Removes the SPDY host header.
|
||||
*/
|
||||
public static void removeMethod(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.METHOD);
|
||||
public static void removeHost(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpMethod} represented by the {@code "method"} header.
|
||||
* Returns the SPDY host header.
|
||||
*/
|
||||
public static HttpMethod getMethod(SpdyHeaderBlock block) {
|
||||
public static String getHost(SpdyHeaderBlock block) {
|
||||
return block.getHeader(HttpNames.HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SPDY host header.
|
||||
*/
|
||||
public static void setHost(SpdyHeaderBlock block, String host) {
|
||||
block.setHeader(HttpNames.HOST, host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP method header.
|
||||
*/
|
||||
public static void removeMethod(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.METHOD);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpMethod} represented by the HTTP method header.
|
||||
*/
|
||||
public static HttpMethod getMethod(int spdyVersion, SpdyHeaderBlock block) {
|
||||
try {
|
||||
if (spdyVersion < 3) {
|
||||
return HttpMethod.valueOf(block.getHeader(Spdy2HttpNames.METHOD));
|
||||
} else {
|
||||
return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "method"} header.
|
||||
* Sets the HTTP method header.
|
||||
*/
|
||||
public static void setMethod(SpdyHeaderBlock block, HttpMethod method) {
|
||||
public static void setMethod(int spdyVersion, SpdyHeaderBlock block, HttpMethod method) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.METHOD, method.getName());
|
||||
} else {
|
||||
block.setHeader(HttpNames.METHOD, method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "scheme"} header.
|
||||
* Removes the URL scheme header.
|
||||
*/
|
||||
public static void removeScheme(SpdyHeaderBlock block) {
|
||||
public static void removeScheme(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 2) {
|
||||
block.removeHeader(Spdy2HttpNames.SCHEME);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.SCHEME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "scheme"} header.
|
||||
* Returns the value of the URL scheme header.
|
||||
*/
|
||||
public static String getScheme(SpdyHeaderBlock block) {
|
||||
public static String getScheme(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
return block.getHeader(Spdy2HttpNames.SCHEME);
|
||||
} else {
|
||||
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.
|
||||
* Sets the URL scheme header.
|
||||
*/
|
||||
public static void removeStatus(SpdyHeaderBlock block) {
|
||||
public static void setScheme(int spdyVersion, SpdyHeaderBlock block, String scheme) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.SCHEME, scheme);
|
||||
} else {
|
||||
block.setHeader(HttpNames.SCHEME, scheme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP response status header.
|
||||
*/
|
||||
public static void removeStatus(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.STATUS);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpResponseStatus} represented by the {@code "status"} header.
|
||||
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
|
||||
*/
|
||||
public static HttpResponseStatus getStatus(SpdyHeaderBlock block) {
|
||||
public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeaderBlock block) {
|
||||
try {
|
||||
String status = block.getHeader(HttpNames.STATUS);
|
||||
String status;
|
||||
if (spdyVersion < 3) {
|
||||
status = block.getHeader(Spdy2HttpNames.STATUS);
|
||||
} else {
|
||||
status = block.getHeader(HttpNames.STATUS);
|
||||
}
|
||||
int space = status.indexOf(' ');
|
||||
if (space == -1) {
|
||||
return HttpResponseStatus.valueOf(Integer.parseInt(status));
|
||||
@ -180,7 +269,7 @@ public class SpdyHeaders {
|
||||
int code = Integer.parseInt(status.substring(0, space));
|
||||
String reasonPhrase = status.substring(space + 1);
|
||||
HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
|
||||
if (responseStatus.getReasonPhrase().equals(responseStatus)) {
|
||||
if (responseStatus.getReasonPhrase().equals(reasonPhrase)) {
|
||||
return responseStatus;
|
||||
} else {
|
||||
return new HttpResponseStatus(code, reasonPhrase);
|
||||
@ -192,56 +281,84 @@ public class SpdyHeaders {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "status"} header.
|
||||
* Sets the HTTP response status header.
|
||||
*/
|
||||
public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) {
|
||||
public static void setStatus(int spdyVersion, SpdyHeaderBlock block, HttpResponseStatus status) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.STATUS, status.toString());
|
||||
} else {
|
||||
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.
|
||||
* Removes the URL path header.
|
||||
*/
|
||||
public static String getUrl(SpdyHeaderBlock block) {
|
||||
return block.getHeader(HttpNames.URL);
|
||||
public static void removeUrl(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.URL);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.PATH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "url"} header.
|
||||
* Returns the value of the URL path header.
|
||||
*/
|
||||
public static void setUrl(SpdyHeaderBlock block, String value) {
|
||||
block.setHeader(HttpNames.URL, value);
|
||||
public static String getUrl(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
return block.getHeader(Spdy2HttpNames.URL);
|
||||
} else {
|
||||
return block.getHeader(HttpNames.PATH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "version"} header.
|
||||
* Sets the URL path header.
|
||||
*/
|
||||
public static void removeVersion(SpdyHeaderBlock block) {
|
||||
public static void setUrl(int spdyVersion, SpdyHeaderBlock block, String path) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.URL, path);
|
||||
} else {
|
||||
block.setHeader(HttpNames.PATH, path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP version header.
|
||||
*/
|
||||
public static void removeVersion(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.VERSION);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpVersion} represented by the {@code "version"} header.
|
||||
* Returns the {@link HttpVersion} represented by the HTTP version header.
|
||||
*/
|
||||
public static HttpVersion getVersion(SpdyHeaderBlock block) {
|
||||
public static HttpVersion getVersion(int spdyVersion, SpdyHeaderBlock block) {
|
||||
try {
|
||||
if (spdyVersion < 3) {
|
||||
return HttpVersion.valueOf(block.getHeader(Spdy2HttpNames.VERSION));
|
||||
} else {
|
||||
return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "version"} header.
|
||||
* Sets the HTTP version header.
|
||||
*/
|
||||
public static void setVersion(SpdyHeaderBlock block, HttpVersion version) {
|
||||
block.setHeader(HttpNames.VERSION, version.getText());
|
||||
public static void setVersion(int spdyVersion, SpdyHeaderBlock block, HttpVersion httpVersion) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.VERSION, httpVersion.getText());
|
||||
} else {
|
||||
block.setHeader(HttpNames.VERSION, httpVersion.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -29,4 +29,15 @@ public interface SpdyHeadersFrame extends SpdyHeaderBlock {
|
||||
* 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);
|
||||
}
|
||||
|
@ -28,13 +28,14 @@ import io.netty.channel.ChannelUpstreamHandler;
|
||||
public class SpdyHttpCodec implements ChannelUpstreamHandler, ChannelDownstreamHandler {
|
||||
|
||||
private final SpdyHttpDecoder decoder;
|
||||
private final SpdyHttpEncoder encoder = new SpdyHttpEncoder();
|
||||
private final SpdyHttpEncoder encoder;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder options.
|
||||
*/
|
||||
public SpdyHttpCodec(int maxContentLength) {
|
||||
decoder = new SpdyHttpDecoder(maxContentLength);
|
||||
public SpdyHttpCodec(int version, int maxContentLength) {
|
||||
decoder = new SpdyHttpDecoder(version, maxContentLength);
|
||||
encoder = new SpdyHttpEncoder(version);
|
||||
}
|
||||
|
||||
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -42,22 +44,29 @@ import io.netty.handler.codec.oneone.OneToOneDecoder;
|
||||
*/
|
||||
public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
|
||||
private final int spdyVersion;
|
||||
private final int maxContentLength;
|
||||
private final Map<Integer, HttpMessage> messageMap = new HashMap<Integer, HttpMessage>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
* @param maxContentLength the maximum length of the message content.
|
||||
* If the length of the message content exceeds this value,
|
||||
* a {@link TooLongFrameException} will be raised.
|
||||
*/
|
||||
public SpdyHttpDecoder(int maxContentLength) {
|
||||
public SpdyHttpDecoder(int version, int maxContentLength) {
|
||||
super();
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (maxContentLength <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxContentLength must be a positive integer: " + maxContentLength);
|
||||
}
|
||||
spdyVersion = version;
|
||||
this.maxContentLength = maxContentLength;
|
||||
}
|
||||
|
||||
@ -72,7 +81,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
int streamID = spdySynStreamFrame.getStreamID();
|
||||
|
||||
if (SpdyCodecUtil.isServerID(streamID)) {
|
||||
// SYN_STREAM frames inititated by the server are pushed resources
|
||||
// SYN_STREAM frames initiated by the server are pushed resources
|
||||
int associatedToStreamID = spdySynStreamFrame.getAssociatedToStreamID();
|
||||
|
||||
// If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0
|
||||
@ -83,7 +92,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
String URL = SpdyHeaders.getUrl(spdySynStreamFrame);
|
||||
String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// If a client receives a SYN_STREAM without a 'url' header
|
||||
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
|
||||
@ -94,7 +103,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResponse httpResponse = createHttpResponse(spdySynStreamFrame);
|
||||
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
|
||||
SpdyHttpHeaders.setStreamID(httpResponse, streamID);
|
||||
@ -118,7 +127,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
} else {
|
||||
// SYN_STREAM frames initiated by the client are HTTP requests
|
||||
try {
|
||||
HttpRequest httpRequest = createHttpRequest(spdySynStreamFrame);
|
||||
HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
SpdyHttpHeaders.setStreamID(httpRequest, streamID);
|
||||
@ -130,13 +139,13 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
messageMap.put(new Integer(streamID), httpRequest);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// If a client sends a SYN_STREAM without method, url, and version headers
|
||||
// the server must reply with a HTTP 400 BAD REQUEST reply
|
||||
// If a client sends a SYN_STREAM without all of the method, url (host and path),
|
||||
// scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply.
|
||||
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
spdySynReplyFrame.setLast(true);
|
||||
SpdyHeaders.setStatus(spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
|
||||
SpdyHeaders.setVersion(spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
|
||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
||||
Channels.write(ctx, Channels.future(channel), spdySynReplyFrame);
|
||||
}
|
||||
}
|
||||
@ -147,7 +156,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
int streamID = spdySynReplyFrame.getStreamID();
|
||||
|
||||
try {
|
||||
HttpResponse httpResponse = createHttpResponse(spdySynReplyFrame);
|
||||
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
SpdyHttpHeaders.setStreamID(httpResponse, streamID);
|
||||
@ -215,67 +224,72 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
messageMap.remove(streamID);
|
||||
return httpMessage;
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
Integer streamID = new Integer(spdyRstStreamFrame.getStreamID());
|
||||
messageMap.remove(streamID);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame)
|
||||
private static HttpRequest createHttpRequest(int spdyVersion, SpdyHeaderBlock requestFrame)
|
||||
throws Exception {
|
||||
// Create the first line of the request from the name/value pairs
|
||||
HttpMethod method = SpdyHeaders.getMethod(requestFrame);
|
||||
String url = SpdyHeaders.getUrl(requestFrame);
|
||||
HttpVersion version = SpdyHeaders.getVersion(requestFrame);
|
||||
SpdyHeaders.removeMethod(requestFrame);
|
||||
SpdyHeaders.removeUrl(requestFrame);
|
||||
SpdyHeaders.removeVersion(requestFrame);
|
||||
HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame);
|
||||
String url = SpdyHeaders.getUrl(spdyVersion, requestFrame);
|
||||
HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
|
||||
SpdyHeaders.removeMethod(spdyVersion, requestFrame);
|
||||
SpdyHeaders.removeUrl(spdyVersion, requestFrame);
|
||||
SpdyHeaders.removeVersion(spdyVersion, requestFrame);
|
||||
|
||||
HttpRequest httpRequest = new DefaultHttpRequest(version, method, url);
|
||||
for (Map.Entry<String, String> e: requestFrame.getHeaders()) {
|
||||
httpRequest.addHeader(e.getKey(), e.getValue());
|
||||
HttpRequest httpRequest = new DefaultHttpRequest(httpVersion, method, url);
|
||||
|
||||
// Remove the scheme header
|
||||
SpdyHeaders.removeScheme(spdyVersion, requestFrame);
|
||||
|
||||
if (spdyVersion >= 3) {
|
||||
// Replace the SPDY host header with the HTTP host header
|
||||
String host = SpdyHeaders.getHost(requestFrame);
|
||||
SpdyHeaders.removeHost(requestFrame);
|
||||
HttpHeaders.setHost(httpRequest, host);
|
||||
}
|
||||
|
||||
// Chunked encoding is no longer valid
|
||||
List<String> encodings = httpRequest.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
encodings.remove(HttpHeaders.Values.CHUNKED);
|
||||
if (encodings.isEmpty()) {
|
||||
httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
} else {
|
||||
httpRequest.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings);
|
||||
for (Map.Entry<String, String> e: requestFrame.getHeaders()) {
|
||||
httpRequest.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpHeaders.setKeepAlive(httpRequest, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
private static HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame)
|
||||
private static HttpResponse createHttpResponse(int spdyVersion, SpdyHeaderBlock responseFrame)
|
||||
throws Exception {
|
||||
// Create the first line of the response from the name/value pairs
|
||||
HttpResponseStatus status = SpdyHeaders.getStatus(responseFrame);
|
||||
HttpVersion version = SpdyHeaders.getVersion(responseFrame);
|
||||
SpdyHeaders.removeStatus(responseFrame);
|
||||
SpdyHeaders.removeVersion(responseFrame);
|
||||
HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
|
||||
HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
|
||||
SpdyHeaders.removeStatus(spdyVersion, responseFrame);
|
||||
SpdyHeaders.removeVersion(spdyVersion, responseFrame);
|
||||
|
||||
HttpResponse httpResponse = new DefaultHttpResponse(version, status);
|
||||
for (Map.Entry<String, String> e: responseFrame.getHeaders()) {
|
||||
httpResponse.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// Chunked encoding is no longer valid
|
||||
List<String> encodings = httpResponse.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
encodings.remove(HttpHeaders.Values.CHUNKED);
|
||||
if (encodings.isEmpty()) {
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
} else {
|
||||
httpResponse.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings);
|
||||
}
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRAILER);
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpHeaders.setKeepAlive(httpResponse, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRAILER);
|
||||
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -51,8 +53,8 @@ import io.netty.handler.codec.http.HttpResponse;
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Priority"}</td>
|
||||
* <td>The priority value for this request.
|
||||
* The priority should be between 0 and 3 inclusive.
|
||||
* 0 represents the highest priority and 3 represents the lowest.
|
||||
* The priority should be between 0 and 7 inclusive.
|
||||
* 0 represents the highest priority and 7 represents the lowest.
|
||||
* This header is optional and defaults to 0.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
@ -84,21 +86,32 @@ import io.netty.handler.codec.http.HttpResponse;
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Associated-To-Stream-ID"}</td>
|
||||
* <td>The Stream-ID of the request that inititated this pushed resource.</td>
|
||||
* <td>The Stream-ID of the request that initiated this pushed resource.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Priority"}</td>
|
||||
* <td>The priority value for this resource.
|
||||
* The priority should be between 0 and 3 inclusive.
|
||||
* 0 represents the highest priority and 3 represents the lowest.
|
||||
* The priority should be between 0 and 7 inclusive.
|
||||
* 0 represents the highest priority and 7 represents the lowest.
|
||||
* This header is optional and defaults to 0.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-URL"}</td>
|
||||
* <td>The full URL for the resource being pushed.</td>
|
||||
* <td>The absolute path for the resource being pushed.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h3>Required Annotations</h3>
|
||||
*
|
||||
* SPDY requires that all Requests and Pushed Resources contain
|
||||
* an HTTP "Host" header.
|
||||
*
|
||||
* <h3>Optional Annotations</h3>
|
||||
*
|
||||
* Requests and Pushed Resources must contain a SPDY scheme header.
|
||||
* This can be set via the {@code "X-SPDY-Scheme"} header but otherwise
|
||||
* defaults to "https" as that is the most common SPDY deployment.
|
||||
*
|
||||
* <h3>Chunked Content</h3>
|
||||
*
|
||||
* This encoder associates all {@link HttpChunk}s that it receives
|
||||
@ -112,9 +125,20 @@ import io.netty.handler.codec.http.HttpResponse;
|
||||
*/
|
||||
public class SpdyHttpEncoder implements ChannelDownstreamHandler {
|
||||
|
||||
private final int spdyVersion;
|
||||
private volatile int currentStreamID;
|
||||
|
||||
public SpdyHttpEncoder() {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
*/
|
||||
public SpdyHttpEncoder(int version) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
spdyVersion = version;
|
||||
}
|
||||
|
||||
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
|
||||
@ -228,15 +252,17 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
|
||||
throws Exception {
|
||||
boolean chunked = httpMessage.isChunked();
|
||||
|
||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, and URL from the headers
|
||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamID(httpMessage);
|
||||
int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage);
|
||||
byte priority = SpdyHttpHeaders.getPriority(httpMessage);
|
||||
String URL = SpdyHttpHeaders.getUrl(httpMessage);
|
||||
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
|
||||
SpdyHttpHeaders.removeStreamID(httpMessage);
|
||||
SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage);
|
||||
SpdyHttpHeaders.removePriority(httpMessage);
|
||||
SpdyHttpHeaders.removeUrl(httpMessage);
|
||||
SpdyHttpHeaders.removeScheme(httpMessage);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
@ -246,24 +272,39 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
|
||||
httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
|
||||
for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
|
||||
spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Unfold the first line of the message into name/value pairs
|
||||
SpdyHeaders.setVersion(spdySynStreamFrame, httpMessage.getProtocolVersion());
|
||||
if (httpMessage instanceof HttpRequest) {
|
||||
HttpRequest httpRequest = (HttpRequest) httpMessage;
|
||||
SpdyHeaders.setMethod(spdySynStreamFrame, httpRequest.getMethod());
|
||||
SpdyHeaders.setUrl(spdySynStreamFrame, httpRequest.getUri());
|
||||
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
|
||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
|
||||
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
|
||||
}
|
||||
if (httpMessage instanceof HttpResponse) {
|
||||
HttpResponse httpResponse = (HttpResponse) httpMessage;
|
||||
SpdyHeaders.setStatus(spdySynStreamFrame, httpResponse.getStatus());
|
||||
SpdyHeaders.setUrl(spdySynStreamFrame, URL);
|
||||
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
|
||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
|
||||
spdySynStreamFrame.setUnidirectional(true);
|
||||
}
|
||||
|
||||
// Replace the HTTP host header with the SPDY host header
|
||||
if (spdyVersion >= 3) {
|
||||
String host = HttpHeaders.getHost(httpMessage);
|
||||
httpMessage.removeHeader(HttpHeaders.Names.HOST);
|
||||
SpdyHeaders.setHost(spdySynStreamFrame, host);
|
||||
}
|
||||
|
||||
// Set the SPDY scheme header
|
||||
if (scheme == null) {
|
||||
scheme = "https";
|
||||
}
|
||||
SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
|
||||
|
||||
// Transfer the remaining HTTP headers
|
||||
for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
|
||||
spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (chunked) {
|
||||
currentStreamID = streamID;
|
||||
spdySynStreamFrame.setLast(false);
|
||||
@ -290,14 +331,16 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
|
||||
// Unfold the first line of the response into name/value pairs
|
||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
|
||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
|
||||
|
||||
// Transfer the remaining HTTP headers
|
||||
for (Map.Entry<String, String> entry: httpResponse.getHeaders()) {
|
||||
spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Unfold the first line of the repsonse into name/value pairs
|
||||
SpdyHeaders.setStatus(spdySynReplyFrame, httpResponse.getStatus());
|
||||
SpdyHeaders.setVersion(spdySynReplyFrame, httpResponse.getProtocolVersion());
|
||||
|
||||
if (chunked) {
|
||||
currentStreamID = streamID;
|
||||
spdySynReplyFrame.setLast(false);
|
||||
|
@ -46,6 +46,10 @@ public final class SpdyHttpHeaders {
|
||||
* {@code "X-SPDY-URL"}
|
||||
*/
|
||||
public static final String URL = "X-SPDY-URL";
|
||||
/**
|
||||
* {@code "X-SPDY-Scheme"}
|
||||
*/
|
||||
public static final String SCHEME = "X-SPDY-Scheme";
|
||||
|
||||
private Names() {
|
||||
super();
|
||||
@ -144,4 +148,25 @@ public final class SpdyHttpHeaders {
|
||||
public static void setUrl(HttpMessage message, String url) {
|
||||
message.setHeader(Names.URL, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static void removeScheme(HttpMessage message) {
|
||||
message.removeHeader(Names.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static String getScheme(HttpMessage message) {
|
||||
return message.getHeader(Names.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static void setScheme(HttpMessage message, String scheme) {
|
||||
message.setHeader(Names.URL, scheme);
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,20 @@
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.netty.channel.MessageEvent;
|
||||
|
||||
final class SpdySession {
|
||||
|
||||
private static final SpdyProtocolException STREAM_CLOSED = new SpdyProtocolException("Stream closed");
|
||||
|
||||
private final Map<Integer, StreamState> activeStreams =
|
||||
new ConcurrentHashMap<Integer, StreamState>();
|
||||
|
||||
@ -38,17 +47,35 @@ final class SpdySession {
|
||||
return activeStreams.containsKey(new Integer(streamID));
|
||||
}
|
||||
|
||||
public void acceptStream(int streamID, boolean remoteSideClosed, boolean localSideClosed) {
|
||||
// Stream-IDs should be iterated in priority order
|
||||
public Set<Integer> getActiveStreams() {
|
||||
TreeSet<Integer> StreamIDs = new TreeSet<Integer>(new PriorityComparator());
|
||||
StreamIDs.addAll(activeStreams.keySet());
|
||||
return StreamIDs;
|
||||
}
|
||||
|
||||
public void acceptStream(
|
||||
int streamID, byte priority, boolean remoteSideClosed, boolean localSideClosed,
|
||||
int sendWindowSize, int receiveWindowSize) {
|
||||
if (!remoteSideClosed || !localSideClosed) {
|
||||
activeStreams.put(new Integer(streamID),
|
||||
new StreamState(remoteSideClosed, localSideClosed));
|
||||
activeStreams.put(
|
||||
new Integer(streamID),
|
||||
new StreamState(priority, remoteSideClosed, localSideClosed, sendWindowSize, receiveWindowSize));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public void removeStream(int streamID) {
|
||||
activeStreams.remove(new Integer(streamID));
|
||||
return;
|
||||
Integer StreamID = new Integer(streamID);
|
||||
StreamState state = activeStreams.get(StreamID);
|
||||
activeStreams.remove(StreamID);
|
||||
if (state != null) {
|
||||
MessageEvent e = state.removePendingWrite();
|
||||
while (e != null) {
|
||||
e.getFuture().setFailure(STREAM_CLOSED);
|
||||
e = state.removePendingWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRemoteSideClosed(int streamID) {
|
||||
@ -83,6 +110,11 @@ final class SpdySession {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* hasReceivedReply and receivedReply are only called from messageReceived
|
||||
* no need to synchronize access to the StreamState
|
||||
*/
|
||||
|
||||
public boolean hasReceivedReply(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return (state != null) && state.hasReceivedReply();
|
||||
@ -95,15 +127,77 @@ final class SpdySession {
|
||||
}
|
||||
}
|
||||
|
||||
public int getSendWindowSize(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return state != null ? state.getSendWindowSize() : -1;
|
||||
}
|
||||
|
||||
public int updateSendWindowSize(int streamID, int deltaWindowSize) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return state != null ? state.updateSendWindowSize(deltaWindowSize) : -1;
|
||||
}
|
||||
|
||||
public int updateReceiveWindowSize(int streamID, int deltaWindowSize) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
if (deltaWindowSize > 0) {
|
||||
state.setReceiveWindowSizeLowerBound(0);
|
||||
}
|
||||
return state != null ? state.updateReceiveWindowSize(deltaWindowSize) : -1;
|
||||
}
|
||||
|
||||
public int getReceiveWindowSizeLowerBound(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return state != null ? state.getReceiveWindowSizeLowerBound() : 0;
|
||||
}
|
||||
|
||||
public void updateAllReceiveWindowSizes(int deltaWindowSize) {
|
||||
for (StreamState state: activeStreams.values()) {
|
||||
state.updateReceiveWindowSize(deltaWindowSize);
|
||||
if (deltaWindowSize < 0) {
|
||||
state.setReceiveWindowSizeLowerBound(deltaWindowSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putPendingWrite(int streamID, MessageEvent evt) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return state != null && state.putPendingWrite(evt);
|
||||
}
|
||||
|
||||
public MessageEvent getPendingWrite(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return state != null ? state.getPendingWrite() : null;
|
||||
}
|
||||
|
||||
public MessageEvent removePendingWrite(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return state != null ? state.removePendingWrite() : null;
|
||||
}
|
||||
|
||||
private static final class StreamState {
|
||||
|
||||
private boolean remoteSideClosed;
|
||||
private boolean localSideClosed;
|
||||
private final byte priority;
|
||||
private volatile boolean remoteSideClosed;
|
||||
private volatile boolean localSideClosed;
|
||||
private boolean receivedReply;
|
||||
private AtomicInteger sendWindowSize;
|
||||
private AtomicInteger receiveWindowSize;
|
||||
private volatile int receiveWindowSizeLowerBound;
|
||||
private ConcurrentLinkedQueue<MessageEvent> pendingWriteQueue =
|
||||
new ConcurrentLinkedQueue<MessageEvent>();
|
||||
|
||||
public StreamState(boolean remoteSideClosed, boolean localSideClosed) {
|
||||
public StreamState(
|
||||
byte priority, boolean remoteSideClosed, boolean localSideClosed,
|
||||
int sendWindowSize, int receiveWindowSize) {
|
||||
this.priority = priority;
|
||||
this.remoteSideClosed = remoteSideClosed;
|
||||
this.localSideClosed = localSideClosed;
|
||||
this.sendWindowSize = new AtomicInteger(sendWindowSize);
|
||||
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
|
||||
}
|
||||
|
||||
public byte getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public boolean isRemoteSideClosed() {
|
||||
@ -129,5 +223,50 @@ final class SpdySession {
|
||||
public void receivedReply() {
|
||||
receivedReply = true;
|
||||
}
|
||||
|
||||
public int getSendWindowSize() {
|
||||
return sendWindowSize.get();
|
||||
}
|
||||
|
||||
public int updateSendWindowSize(int deltaWindowSize) {
|
||||
return sendWindowSize.addAndGet(deltaWindowSize);
|
||||
}
|
||||
|
||||
public int updateReceiveWindowSize(int deltaWindowSize) {
|
||||
return receiveWindowSize.addAndGet(deltaWindowSize);
|
||||
}
|
||||
|
||||
public int getReceiveWindowSizeLowerBound() {
|
||||
return receiveWindowSizeLowerBound;
|
||||
}
|
||||
|
||||
public void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {
|
||||
this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound;
|
||||
}
|
||||
|
||||
public boolean putPendingWrite(MessageEvent evt) {
|
||||
return pendingWriteQueue.offer(evt);
|
||||
}
|
||||
|
||||
public MessageEvent getPendingWrite() {
|
||||
return pendingWriteQueue.peek();
|
||||
}
|
||||
|
||||
public MessageEvent removePendingWrite() {
|
||||
return pendingWriteQueue.poll();
|
||||
}
|
||||
}
|
||||
|
||||
private final class PriorityComparator implements Comparator<Integer> {
|
||||
|
||||
public PriorityComparator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public int compare(Integer id1, Integer id2) {
|
||||
StreamState state1 = activeStreams.get(id1);
|
||||
StreamState state2 = activeStreams.get(id2);
|
||||
return state1.getPriority() - state2.getPriority();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -46,6 +48,12 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
private volatile int localConcurrentStreams;
|
||||
private volatile int maxConcurrentStreams;
|
||||
|
||||
private static final int DEFAULT_WINDOW_SIZE = 64 * 1024; // 64 KB default initial window size
|
||||
private volatile int initialSendWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
private volatile int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
|
||||
private final Object flowControlLock = new Object();
|
||||
|
||||
private final AtomicInteger pings = new AtomicInteger();
|
||||
|
||||
private volatile boolean sentGoAwayFrame;
|
||||
@ -54,18 +62,25 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
private volatile ChannelFuture closeSessionFuture;
|
||||
|
||||
private final boolean server;
|
||||
private final boolean flowControl;
|
||||
|
||||
/**
|
||||
* Creates a new session handler.
|
||||
*
|
||||
* @param version the protocol version
|
||||
* @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) {
|
||||
public SpdySessionHandler(int version, boolean server) {
|
||||
super();
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
this.server = server;
|
||||
this.flowControl = version >= 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -78,41 +93,95 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
/*
|
||||
* 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 receives a data frame for a Stream-ID which is not open
|
||||
* and the endpoint has not sent a GOAWAY frame, it must issue a stream error
|
||||
* with the 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.
|
||||
* a SYN_REPLY on that stream, it is a protocol error, and the recipient must
|
||||
* issue a stream error with the status code PROTOCOL_ERROR for the Stream-ID.
|
||||
*
|
||||
* If an endpoint receives multiple data frames for invalid Stream-IDs,
|
||||
* it may terminate the session.
|
||||
* it may close 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.
|
||||
* If an endpoint receives a data frame after the stream is half-closed from the
|
||||
* sender, it must send a RST_STREAM frame with the status STREAM_ALREADY_CLOSED.
|
||||
*
|
||||
* If an endpoint receives a data frame after the stream is closed, it must send
|
||||
* a RST_STREAM frame with the status PROTOCOL_ERROR.
|
||||
*/
|
||||
|
||||
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);
|
||||
if (!spdySession.isActiveStream(streamID)) {
|
||||
if (streamID <= lastGoodStreamID) {
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
} else if (!sentGoAwayFrame) {
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we received a data frame for a stream which is half-closed
|
||||
if (spdySession.isRemoteSideClosed(streamID)) {
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_ALREADY_CLOSED);
|
||||
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);
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SPDY Data frame flow control processing requirements:
|
||||
*
|
||||
* Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
|
||||
*/
|
||||
|
||||
if (flowControl) {
|
||||
// Update receive window size
|
||||
int deltaWindowSize = -1 * spdyDataFrame.getData().readableBytes();
|
||||
int newWindowSize = spdySession.updateReceiveWindowSize(streamID, deltaWindowSize);
|
||||
|
||||
// Window size can become negative if we sent a SETTINGS frame that reduces the
|
||||
// size of the transfer window after the peer has written data frames.
|
||||
// The value is bounded by the length that SETTINGS frame decrease the window.
|
||||
// This difference is stored for the session when writing the SETTINGS frame
|
||||
// and is cleared once we send a WINDOW_UPDATE frame.
|
||||
if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamID)) {
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.FLOW_CONTROL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Window size became negative due to sender writing frame before receiving SETTINGS
|
||||
// Send data frames upstream in initialReceiveWindowSize chunks
|
||||
if (newWindowSize < 0) {
|
||||
while (spdyDataFrame.getData().readableBytes() > initialReceiveWindowSize) {
|
||||
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
partialDataFrame.setData(spdyDataFrame.getData().readSlice(initialReceiveWindowSize));
|
||||
Channels.fireMessageReceived(ctx, partialDataFrame, e.getRemoteAddress());
|
||||
}
|
||||
}
|
||||
|
||||
// Send a WINDOW_UPDATE frame if less than half the window size remains
|
||||
if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) {
|
||||
deltaWindowSize = initialReceiveWindowSize - newWindowSize;
|
||||
spdySession.updateReceiveWindowSize(streamID, deltaWindowSize);
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame =
|
||||
new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize);
|
||||
Channels.write(
|
||||
ctx, Channels.future(e.getChannel()), spdyWindowUpdateFrame, e.getRemoteAddress());
|
||||
}
|
||||
}
|
||||
|
||||
// Close the remote side of the stream if this is the last frame
|
||||
if (spdyDataFrame.isLast()) {
|
||||
// Close remote side of stream
|
||||
halfCloseStream(streamID, true);
|
||||
}
|
||||
|
||||
@ -121,11 +190,15 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
/*
|
||||
* 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 a SYN_STREAM with a Stream-ID that is less than
|
||||
* any previously received SYN_STREAM, 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.
|
||||
*
|
||||
* The recipient can reject a stream by sending a stream error with the
|
||||
* status code REFUSED_STREAM.
|
||||
*/
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
@ -135,21 +208,22 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
if (spdySynStreamFrame.isInvalid() ||
|
||||
!isRemoteInitiatedID(streamID) ||
|
||||
spdySession.isActiveStream(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stream-IDs must be monotonically increassing
|
||||
if (streamID < lastGoodStreamID) {
|
||||
issueSessionError(ctx, e.getChannel(), e.getRemoteAddress());
|
||||
// Stream-IDs must be monotonically increasing
|
||||
if (streamID <= lastGoodStreamID) {
|
||||
issueSessionError(ctx, e.getChannel(), e.getRemoteAddress(), SpdySessionStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to accept the stream
|
||||
byte priority = spdySynStreamFrame.getPriority();
|
||||
boolean remoteSideClosed = spdySynStreamFrame.isLast();
|
||||
boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
|
||||
if (!acceptStream(streamID, remoteSideClosed, localSideClosed)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.REFUSED_STREAM);
|
||||
if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) {
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.REFUSED_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -159,7 +233,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
* 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.
|
||||
* it must issue a stream error with the status code STREAM_IN_USE.
|
||||
*/
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
@ -169,19 +243,20 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
if (spdySynReplyFrame.isInvalid() ||
|
||||
isRemoteInitiatedID(streamID) ||
|
||||
spdySession.isRemoteSideClosed(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
issueStreamError(ctx, e.getRemoteAddress(), 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);
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_IN_USE);
|
||||
return;
|
||||
}
|
||||
|
||||
spdySession.receivedReply(streamID);
|
||||
|
||||
// Close the remote side of the stream if this is the last frame
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
// Close remote side of stream
|
||||
halfCloseStream(streamID, true);
|
||||
}
|
||||
|
||||
@ -190,8 +265,10 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
/*
|
||||
* SPDY RST_STREAM frame processing requirements:
|
||||
*
|
||||
* After receiving a RST_STREAM on a stream, the receiver must not send additional
|
||||
* frames on that stream.
|
||||
* After receiving a RST_STREAM on a stream, the receiver must not send
|
||||
* additional frames on that stream.
|
||||
*
|
||||
* An endpoint must not send a RST_STREAM in response to a RST_STREAM.
|
||||
*/
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
@ -199,12 +276,29 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
/*
|
||||
* Only concerned with MAX_CONCURRENT_STREAMS
|
||||
*/
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
updateConcurrentStreams(spdySettingsFrame, true);
|
||||
|
||||
int newConcurrentStreams =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
if (newConcurrentStreams >= 0) {
|
||||
updateConcurrentStreams(newConcurrentStreams, true);
|
||||
}
|
||||
|
||||
// Persistence flag are inconsistent with the use of SETTINGS to communicate
|
||||
// the initial window size. Remove flags from the sender requesting that the
|
||||
// value be persisted. Remove values that the sender indicates are persisted.
|
||||
if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
|
||||
spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
}
|
||||
spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
|
||||
|
||||
if (flowControl) {
|
||||
int newInitialWindowSize =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
if (newInitialWindowSize >= 0) {
|
||||
updateInitialSendWindowSize(newInitialWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
@ -224,7 +318,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: only checks that there are outstanding pings since uniqueness is not inforced
|
||||
// Note: only checks that there are outstanding pings since uniqueness is not enforced
|
||||
if (pings.get() == 0) {
|
||||
return;
|
||||
}
|
||||
@ -241,14 +335,51 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
|
||||
// Check if we received a valid HEADERS frame
|
||||
if (spdyHeadersFrame.isInvalid()) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spdySession.isRemoteSideClosed(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the remote side of the stream if this is the last frame
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
halfCloseStream(streamID, true);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
/*
|
||||
* SPDY WINDOW_UPDATE frame processing requirements:
|
||||
*
|
||||
* Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
|
||||
* must send a RST_STREAM with the status code FLOW_CONTROL_ERROR.
|
||||
*
|
||||
* Sender should ignore all WINDOW_UPDATE frames associated with a stream
|
||||
* after sending the last frame for the stream.
|
||||
*/
|
||||
|
||||
if (flowControl) {
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
|
||||
int streamID = spdyWindowUpdateFrame.getStreamID();
|
||||
int deltaWindowSize = spdyWindowUpdateFrame.getDeltaWindowSize();
|
||||
|
||||
// Ignore frames for half-closed streams
|
||||
if (spdySession.isLocalSideClosed(streamID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for numerical overflow
|
||||
if (spdySession.getSendWindowSize(streamID) > Integer.MAX_VALUE - deltaWindowSize) {
|
||||
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.FLOW_CONTROL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
updateSendWindowSize(ctx, streamID, deltaWindowSize);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
super.messageReceived(ctx, e);
|
||||
@ -260,7 +391,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof SpdyProtocolException) {
|
||||
issueSessionError(ctx, e.getChannel(), null);
|
||||
issueSessionError(ctx, e.getChannel(), null, SpdySessionStatus.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
super.exceptionCaught(ctx, e);
|
||||
@ -274,6 +405,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
case OPEN:
|
||||
case CONNECTED:
|
||||
case BOUND:
|
||||
|
||||
/*
|
||||
* SPDY connection requirements:
|
||||
*
|
||||
* When either endpoint closes the transport-level connection,
|
||||
* it must first send a GOAWAY frame.
|
||||
*/
|
||||
if (Boolean.FALSE.equals(e.getValue()) || e.getValue() == null) {
|
||||
sendGoAwayFrame(ctx, e);
|
||||
return;
|
||||
@ -291,13 +429,85 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
int streamID = spdyDataFrame.getStreamID();
|
||||
final int streamID = spdyDataFrame.getStreamID();
|
||||
|
||||
// Frames must not be sent on half-closed streams
|
||||
if (spdySession.isLocalSideClosed(streamID)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SPDY Data frame flow control processing requirements:
|
||||
*
|
||||
* Sender must not send a data frame with data length greater
|
||||
* than the transfer window size.
|
||||
*
|
||||
* After sending each data frame, the sender decrements its
|
||||
* transfer window size by the amount of data transmitted.
|
||||
*
|
||||
* When the window size becomes less than or equal to 0, the
|
||||
* sender must pause transmitting data frames.
|
||||
*/
|
||||
|
||||
if (flowControl) {
|
||||
synchronized (flowControlLock) {
|
||||
int dataLength = spdyDataFrame.getData().readableBytes();
|
||||
int sendWindowSize = spdySession.getSendWindowSize(streamID);
|
||||
|
||||
if (sendWindowSize >= dataLength) {
|
||||
// Window size is large enough to send entire data frame
|
||||
spdySession.updateSendWindowSize(streamID, -1 * dataLength);
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the stream on write failures that leaves the transfer window in a corrupt state.
|
||||
final SocketAddress remoteAddress = e.getRemoteAddress();
|
||||
final ChannelHandlerContext context = ctx;
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else if (sendWindowSize > 0) {
|
||||
// Stream is not stalled but we cannot send the entire frame
|
||||
spdySession.updateSendWindowSize(streamID, -1 * sendWindowSize);
|
||||
|
||||
// Create a partial data frame whose length is the current window size
|
||||
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
partialDataFrame.setData(spdyDataFrame.getData().readSlice(sendWindowSize));
|
||||
|
||||
// Enqueue the remaining data (will be the first frame queued)
|
||||
spdySession.putPendingWrite(streamID, e);
|
||||
|
||||
ChannelFuture writeFuture = Channels.future(e.getChannel());
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the stream on write failures that leaves the transfer window in a corrupt state.
|
||||
final SocketAddress remoteAddress = e.getRemoteAddress();
|
||||
final ChannelHandlerContext context = ctx;
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Channels.write(ctx, writeFuture, partialDataFrame, remoteAddress);
|
||||
return;
|
||||
|
||||
} else {
|
||||
// Stream is stalled -- enqueue Data frame and return
|
||||
spdySession.putPendingWrite(streamID, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdyDataFrame.isLast()) {
|
||||
halfCloseStream(streamID, false);
|
||||
}
|
||||
@ -305,9 +515,17 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
int streamID = spdySynStreamFrame.getStreamID();
|
||||
|
||||
if (isRemoteInitiatedID(streamID)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
byte priority = spdySynStreamFrame.getPriority();
|
||||
boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
|
||||
boolean localSideClosed = spdySynStreamFrame.isLast();
|
||||
if (!acceptStream(spdySynStreamFrame.getStreamID(), remoteSideClosed, localSideClosed)) {
|
||||
if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
@ -317,11 +535,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamID = spdySynReplyFrame.getStreamID();
|
||||
|
||||
// Frames must not be sent on half-closed streams
|
||||
if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
halfCloseStream(streamID, false);
|
||||
}
|
||||
@ -334,7 +554,28 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
updateConcurrentStreams(spdySettingsFrame, false);
|
||||
|
||||
int newConcurrentStreams =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
if (newConcurrentStreams >= 0) {
|
||||
updateConcurrentStreams(newConcurrentStreams, false);
|
||||
}
|
||||
|
||||
// Persistence flag are inconsistent with the use of SETTINGS to communicate
|
||||
// the initial window size. Remove flags from the sender requesting that the
|
||||
// value be persisted. Remove values that the sender indicates are persisted.
|
||||
if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
|
||||
spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
}
|
||||
spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
|
||||
|
||||
if (flowControl) {
|
||||
int newInitialWindowSize =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
if (newInitialWindowSize >= 0) {
|
||||
updateInitialReceiveWindowSize(newInitialWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
@ -348,7 +589,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
// Should send a CLOSE ChannelStateEvent
|
||||
// Why is this being sent? Intercept it and fail the write.
|
||||
// Should have sent a CLOSE ChannelStateEvent
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
|
||||
@ -357,34 +599,65 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
int streamID = spdyHeadersFrame.getStreamID();
|
||||
|
||||
// Frames must not be sent on half-closed streams
|
||||
if (spdySession.isLocalSideClosed(streamID)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
halfCloseStream(streamID, false);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
// Why is this being sent? Intercept it and fail the write.
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.sendDownstream(evt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Error Handling
|
||||
* SPDY Session Error Handling:
|
||||
*
|
||||
* When a session error occurs, the endpoint encountering the error must first
|
||||
* send a GOAWAY frame with the Stream-ID of the most recently received stream
|
||||
* from the remote endpoint, and the error code for why the session is terminating.
|
||||
*
|
||||
* After sending the GOAWAY frame, the endpoint must close the TCP connection.
|
||||
*/
|
||||
|
||||
private void issueSessionError(
|
||||
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
|
||||
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) {
|
||||
|
||||
ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress);
|
||||
ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress, status);
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
// Send a RST_STREAM frame in response to an incoming MessageEvent
|
||||
// Only called in the upstream direction
|
||||
/*
|
||||
* SPDY Stream Error Handling:
|
||||
*
|
||||
* Upon a stream error, the endpoint must send a RST_STREAM frame which contains
|
||||
* the Stream-ID for the stream where the error occurred and the error status which
|
||||
* caused the error.
|
||||
*
|
||||
* After sending the RST_STREAM, the stream is closed to the sending endpoint.
|
||||
*
|
||||
* Note: this is only called by the worker thread
|
||||
*/
|
||||
private void issueStreamError(
|
||||
ChannelHandlerContext ctx, MessageEvent e, int streamID, SpdyStreamStatus status) {
|
||||
ChannelHandlerContext ctx, SocketAddress remoteAddress, int streamID, SpdyStreamStatus status) {
|
||||
|
||||
boolean fireMessageReceived = !spdySession.isRemoteSideClosed(streamID);
|
||||
removeStream(streamID);
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamID, status);
|
||||
Channels.write(ctx, Channels.future(e.getChannel()), spdyRstStreamFrame, e.getRemoteAddress());
|
||||
Channels.write(ctx, Channels.future(ctx.getChannel()), spdyRstStreamFrame, remoteAddress);
|
||||
if (fireMessageReceived) {
|
||||
Channels.fireMessageReceived(ctx, spdyRstStreamFrame, remoteAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -396,8 +669,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
return server && !serverID || !server && serverID;
|
||||
}
|
||||
|
||||
private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) {
|
||||
int newConcurrentStreams = settings.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
private void updateConcurrentStreams(int newConcurrentStreams, boolean remote) {
|
||||
if (remote) {
|
||||
remoteConcurrentStreams = newConcurrentStreams;
|
||||
} else {
|
||||
@ -422,18 +694,37 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
}
|
||||
}
|
||||
|
||||
// need to synchronize accesses to sentGoAwayFrame and lastGoodStreamID
|
||||
// need to synchronize to prevent new streams from being created while updating active streams
|
||||
private synchronized void updateInitialSendWindowSize(int newInitialWindowSize) {
|
||||
int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;
|
||||
initialSendWindowSize = newInitialWindowSize;
|
||||
for (Integer StreamID: spdySession.getActiveStreams()) {
|
||||
spdySession.updateSendWindowSize(StreamID.intValue(), deltaWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
// need to synchronize to prevent new streams from being created while updating active streams
|
||||
private synchronized void updateInitialReceiveWindowSize(int newInitialWindowSize) {
|
||||
int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;
|
||||
initialReceiveWindowSize = newInitialWindowSize;
|
||||
spdySession.updateAllReceiveWindowSizes(deltaWindowSize);
|
||||
}
|
||||
|
||||
// need to synchronize accesses to sentGoAwayFrame, lastGoodStreamID, and initial window sizes
|
||||
private synchronized boolean acceptStream(
|
||||
int streamID, boolean remoteSideClosed, boolean localSideClosed) {
|
||||
int streamID, byte priority, boolean remoteSideClosed, boolean localSideClosed) {
|
||||
// Cannot initiate any new streams after receiving or sending GOAWAY
|
||||
if (receivedGoAwayFrame || sentGoAwayFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxConcurrentStreams = this.maxConcurrentStreams; // read volatile once
|
||||
if (maxConcurrentStreams != 0 &&
|
||||
spdySession.numActiveStreams() >= maxConcurrentStreams) {
|
||||
return false;
|
||||
}
|
||||
spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed);
|
||||
spdySession.acceptStream(
|
||||
streamID, priority, remoteSideClosed, localSideClosed, initialSendWindowSize, initialReceiveWindowSize);
|
||||
if (isRemoteInitiatedID(streamID)) {
|
||||
lastGoodStreamID = streamID;
|
||||
}
|
||||
@ -458,6 +749,74 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSendWindowSize(ChannelHandlerContext ctx, final int streamID, int deltaWindowSize) {
|
||||
synchronized (flowControlLock) {
|
||||
int newWindowSize = spdySession.updateSendWindowSize(streamID, deltaWindowSize);
|
||||
|
||||
while (newWindowSize > 0) {
|
||||
// Check if we have unblocked a stalled stream
|
||||
MessageEvent e = spdySession.getPendingWrite(streamID);
|
||||
if (e == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) e.getMessage();
|
||||
int dataFrameSize = spdyDataFrame.getData().readableBytes();
|
||||
|
||||
if (newWindowSize >= dataFrameSize) {
|
||||
// Window size is large enough to send entire data frame
|
||||
spdySession.removePendingWrite(streamID);
|
||||
newWindowSize = spdySession.updateSendWindowSize(streamID, -1 * dataFrameSize);
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the stream on write failures that leaves the transfer window in a corrupt state.
|
||||
final SocketAddress remoteAddress = e.getRemoteAddress();
|
||||
final ChannelHandlerContext context = ctx;
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdyDataFrame.isLast()) {
|
||||
halfCloseStream(streamID, false);
|
||||
}
|
||||
|
||||
Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress());
|
||||
|
||||
} else {
|
||||
// We can send a partial frame
|
||||
spdySession.updateSendWindowSize(streamID, -1 * newWindowSize);
|
||||
|
||||
// Create a partial data frame whose length is the current window size
|
||||
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
partialDataFrame.setData(spdyDataFrame.getData().readSlice(newWindowSize));
|
||||
|
||||
ChannelFuture writeFuture = Channels.future(e.getChannel());
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the stream on write failures that leaves the transfer window in a corrupt state.
|
||||
final SocketAddress remoteAddress = e.getRemoteAddress();
|
||||
final ChannelHandlerContext context = ctx;
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Channels.write(ctx, writeFuture, partialDataFrame, remoteAddress);
|
||||
|
||||
newWindowSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelStateEvent e) {
|
||||
// Avoid NotYetConnectedException
|
||||
if (!e.getChannel().isConnected()) {
|
||||
@ -465,7 +824,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null);
|
||||
ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null, SpdySessionStatus.OK);
|
||||
if (spdySession.noActiveStreams()) {
|
||||
future.addListener(new ClosingChannelFutureListener(ctx, e));
|
||||
} else {
|
||||
@ -475,18 +834,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
}
|
||||
|
||||
private synchronized ChannelFuture sendGoAwayFrame(
|
||||
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
|
||||
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) {
|
||||
if (!sentGoAwayFrame) {
|
||||
sentGoAwayFrame = true;
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamID, status);
|
||||
ChannelFuture future = Channels.future(channel);
|
||||
Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID), remoteAddress);
|
||||
Channels.write(ctx, future, spdyGoAwayFrame, remoteAddress);
|
||||
return future;
|
||||
}
|
||||
return Channels.succeededFuture(channel);
|
||||
}
|
||||
|
||||
private static final class ClosingChannelFutureListener implements ChannelFutureListener {
|
||||
|
||||
private final ChannelHandlerContext ctx;
|
||||
private final ChannelStateEvent e;
|
||||
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* The SPDY session status code and its description.
|
||||
* @apiviz.exclude
|
||||
*/
|
||||
public class SpdySessionStatus implements Comparable<SpdySessionStatus> {
|
||||
|
||||
/**
|
||||
* 0 OK
|
||||
*/
|
||||
public static final SpdySessionStatus OK =
|
||||
new SpdySessionStatus(0, "OK");
|
||||
|
||||
/**
|
||||
* 1 Protocol Error
|
||||
*/
|
||||
public static final SpdySessionStatus PROTOCOL_ERROR =
|
||||
new SpdySessionStatus(1, "PROTOCOL_ERROR");
|
||||
|
||||
/**
|
||||
* 11 Internal Error
|
||||
*/
|
||||
public static final SpdySessionStatus INTERNAL_ERROR =
|
||||
new SpdySessionStatus(11, "INTERNAL_ERROR");
|
||||
|
||||
/**
|
||||
* Returns the {@link SpdySessionStatus} 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 SpdySessionStatus valueOf(int code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return OK;
|
||||
case 1:
|
||||
return PROTOCOL_ERROR;
|
||||
case 11:
|
||||
return INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
return new SpdySessionStatus(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 SpdySessionStatus(int code, String statusPhrase) {
|
||||
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 SpdySessionStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getCode() == ((SpdySessionStatus) o).getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getStatusPhrase();
|
||||
}
|
||||
|
||||
public int compareTo(SpdySessionStatus o) {
|
||||
return getCode() - o.getCode();
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ public interface SpdySettingsFrame {
|
||||
int SETTINGS_CURRENT_CWND = 5;
|
||||
int SETTINGS_DOWNLOAD_RETRANS_RATE = 6;
|
||||
int SETTINGS_INITIAL_WINDOW_SIZE = 7;
|
||||
int SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
|
||||
|
||||
/**
|
||||
* Returns a {@code Set} of the setting IDs.
|
||||
@ -49,7 +50,7 @@ public interface SpdySettingsFrame {
|
||||
|
||||
/**
|
||||
* Sets the value of the setting ID.
|
||||
* The ID must be positive and cannot exceeed 16777215.
|
||||
* The ID must be positive and cannot exceed 16777215.
|
||||
*/
|
||||
void setValue(int ID, int value);
|
||||
|
||||
|
@ -63,6 +63,30 @@ public class SpdyStreamStatus implements Comparable<SpdyStreamStatus> {
|
||||
public static final SpdyStreamStatus FLOW_CONTROL_ERROR =
|
||||
new SpdyStreamStatus(7, "FLOW_CONTROL_ERROR");
|
||||
|
||||
/**
|
||||
* 8 Stream In Use
|
||||
*/
|
||||
public static final SpdyStreamStatus STREAM_IN_USE =
|
||||
new SpdyStreamStatus(8, "STREAM_IN_USE");
|
||||
|
||||
/**
|
||||
* 9 Stream Already Closed
|
||||
*/
|
||||
public static final SpdyStreamStatus STREAM_ALREADY_CLOSED =
|
||||
new SpdyStreamStatus(9, "STREAM_ALREADY_CLOSED");
|
||||
|
||||
/**
|
||||
* 10 Invalid Credentials
|
||||
*/
|
||||
public static final SpdyStreamStatus INVALID_CREDENTIALS =
|
||||
new SpdyStreamStatus(10, "INVALID_CREDENTIALS");
|
||||
|
||||
/**
|
||||
* 11 Frame Too Large
|
||||
*/
|
||||
public static final SpdyStreamStatus FRAME_TOO_LARGE =
|
||||
new SpdyStreamStatus(11, "FRAME_TOO_LARGE");
|
||||
|
||||
/**
|
||||
* Returns the {@link SpdyStreamStatus} represented by the specified code.
|
||||
* If the specified code is a defined SPDY status code, a cached instance
|
||||
@ -89,6 +113,14 @@ public class SpdyStreamStatus implements Comparable<SpdyStreamStatus> {
|
||||
return INTERNAL_ERROR;
|
||||
case 7:
|
||||
return FLOW_CONTROL_ERROR;
|
||||
case 8:
|
||||
return STREAM_IN_USE;
|
||||
case 9:
|
||||
return STREAM_ALREADY_CLOSED;
|
||||
case 10:
|
||||
return INVALID_CREDENTIALS;
|
||||
case 11:
|
||||
return FRAME_TOO_LARGE;
|
||||
}
|
||||
|
||||
return new SpdyStreamStatus(code, "UNKNOWN (" + code + ')');
|
||||
|
@ -48,7 +48,7 @@ public interface SpdySynStreamFrame extends SpdyHeaderBlock {
|
||||
|
||||
/**
|
||||
* Sets the priority of the stream.
|
||||
* The priority must be between 0 and 3 inclusive.
|
||||
* The priority must be between 0 and 7 inclusive.
|
||||
*/
|
||||
void setPriority(byte priority);
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.handler.codec.spdy;
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol WINDOW_UPDATE Control Frame
|
||||
|
@ -17,6 +17,8 @@ package io.netty.handler.codec.spdy;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@ -47,15 +49,17 @@ import org.junit.Test;
|
||||
public abstract class AbstractSocketSpdyEchoTest {
|
||||
|
||||
private static final Random random = new Random();
|
||||
static final ChannelBuffer frames = ChannelBuffers.buffer(1176);
|
||||
static final int ignoredBytes = 20;
|
||||
|
||||
private static ExecutorService executor;
|
||||
|
||||
static {
|
||||
private static ChannelBuffer createFrames(int version) {
|
||||
int length = version < 3 ? 1176 : 1174;
|
||||
ChannelBuffer frames = ChannelBuffers.buffer(length);
|
||||
|
||||
// SPDY UNKNOWN Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(0xFFFF);
|
||||
frames.writeByte(0xFF);
|
||||
frames.writeMedium(4);
|
||||
@ -63,7 +67,7 @@ public abstract class AbstractSocketSpdyEchoTest {
|
||||
|
||||
// SPDY NOOP Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(5);
|
||||
frames.writeInt(0);
|
||||
|
||||
@ -77,27 +81,39 @@ public abstract class AbstractSocketSpdyEchoTest {
|
||||
|
||||
// SPDY SYN_STREAM Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(1);
|
||||
frames.writeByte(0x03);
|
||||
if (version < 3) {
|
||||
frames.writeMedium(12);
|
||||
} else {
|
||||
frames.writeMedium(10);
|
||||
}
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF);
|
||||
frames.writeShort(0x8000);
|
||||
if (version < 3) {
|
||||
frames.writeShort(0);
|
||||
}
|
||||
|
||||
// SPDY SYN_REPLY Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(2);
|
||||
frames.writeByte(0x01);
|
||||
if (version < 3) {
|
||||
frames.writeMedium(8);
|
||||
} else {
|
||||
frames.writeMedium(4);
|
||||
}
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
if (version < 3) {
|
||||
frames.writeInt(0);
|
||||
}
|
||||
|
||||
// SPDY RST_STREAM Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(3);
|
||||
frames.writeInt(8);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
@ -105,43 +121,58 @@ public abstract class AbstractSocketSpdyEchoTest {
|
||||
|
||||
// SPDY SETTINGS Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(4);
|
||||
frames.writeByte(0x01);
|
||||
frames.writeMedium(12);
|
||||
frames.writeInt(1);
|
||||
if (version < 3) {
|
||||
frames.writeMedium(random.nextInt());
|
||||
frames.writeByte(0x03);
|
||||
} else {
|
||||
frames.writeByte(0x03);
|
||||
frames.writeMedium(random.nextInt());
|
||||
}
|
||||
frames.writeInt(random.nextInt());
|
||||
|
||||
// SPDY PING Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(6);
|
||||
frames.writeInt(4);
|
||||
frames.writeInt(random.nextInt());
|
||||
|
||||
// SPDY GOAWAY Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(7);
|
||||
if (version < 3) {
|
||||
frames.writeInt(4);
|
||||
} else {
|
||||
frames.writeInt(8);
|
||||
}
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF);
|
||||
if (version >= 3) {
|
||||
frames.writeInt(random.nextInt() | 0x01);
|
||||
}
|
||||
|
||||
// SPDY HEADERS Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(8);
|
||||
frames.writeInt(4);
|
||||
frames.writeByte(0x01);
|
||||
frames.writeMedium(4);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
|
||||
// SPDY WINDOW_UPDATE Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(2);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(9);
|
||||
frames.writeInt(8);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@ -159,14 +190,22 @@ public abstract class AbstractSocketSpdyEchoTest {
|
||||
|
||||
@Test(timeout = 10000)
|
||||
public void testSpdyEcho() throws Throwable {
|
||||
for (int version = SPDY_MIN_VERSION; version <= SPDY_MAX_VERSION; version ++) {
|
||||
testSpdyEcho(version);
|
||||
}
|
||||
}
|
||||
|
||||
private void testSpdyEcho(int version) throws Throwable {
|
||||
ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor));
|
||||
ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor));
|
||||
|
||||
EchoHandler sh = new EchoHandler(true);
|
||||
EchoHandler ch = new EchoHandler(false);
|
||||
ChannelBuffer frames = createFrames(version);
|
||||
|
||||
sb.getPipeline().addLast("decoder", new SpdyFrameDecoder());
|
||||
sb.getPipeline().addLast("encoder", new SpdyFrameEncoder());
|
||||
EchoHandler sh = new EchoHandler(frames, true);
|
||||
EchoHandler ch = new EchoHandler(frames, false);
|
||||
|
||||
sb.getPipeline().addLast("decoder", new SpdyFrameDecoder(version));
|
||||
sb.getPipeline().addLast("encoder", new SpdyFrameEncoder(version));
|
||||
sb.getPipeline().addLast("handler", sh);
|
||||
|
||||
cb.getPipeline().addLast("handler", ch);
|
||||
@ -216,11 +255,13 @@ public abstract class AbstractSocketSpdyEchoTest {
|
||||
private class EchoHandler extends SimpleChannelUpstreamHandler {
|
||||
volatile Channel channel;
|
||||
final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
|
||||
final ChannelBuffer frames;
|
||||
volatile int counter;
|
||||
final boolean server;
|
||||
|
||||
EchoHandler(boolean server) {
|
||||
EchoHandler(ChannelBuffer frames, boolean server) {
|
||||
super();
|
||||
this.frames = frames;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
|
@ -95,10 +95,10 @@ public class SpdySessionHandlerTest {
|
||||
assertHeaderBlock(spdyHeadersFrame, headers);
|
||||
}
|
||||
|
||||
private void testSpdySessionHandler(boolean server) {
|
||||
private void testSpdySessionHandler(int version, boolean server) {
|
||||
DecoderEmbedder<Object> sessionHandler =
|
||||
new DecoderEmbedder<Object>(
|
||||
new SpdySessionHandler(server), new EchoHandler(closeSignal, server));
|
||||
new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server));
|
||||
sessionHandler.pollAll();
|
||||
|
||||
int localStreamID = server ? 1 : 2;
|
||||
@ -132,7 +132,7 @@ public class SpdySessionHandlerTest {
|
||||
sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID));
|
||||
Assert.assertNull(sessionHandler.peek());
|
||||
sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID));
|
||||
assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.STREAM_IN_USE);
|
||||
Assert.assertNull(sessionHandler.peek());
|
||||
remoteStreamID += 2;
|
||||
|
||||
@ -252,12 +252,12 @@ public class SpdySessionHandlerTest {
|
||||
|
||||
@Test
|
||||
public void testSpdyClientSessionHandler() {
|
||||
testSpdySessionHandler(false);
|
||||
testSpdySessionHandler(2, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyServerSessionHandler() {
|
||||
testSpdySessionHandler(true);
|
||||
testSpdySessionHandler(2, true);
|
||||
}
|
||||
|
||||
// Echo Handler opens 4 half-closed streams on session connection
|
||||
|
Loading…
x
Reference in New Issue
Block a user