SPDY: add SPDY/3 support

This commit is contained in:
Jeff Pinner 2012-05-23 08:54:11 -07:00
parent f60997686d
commit 1a28d6119f
28 changed files with 1623 additions and 334 deletions

View File

@ -23,6 +23,7 @@ import io.netty.util.internal.StringUtil;
public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame { public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
private int lastGoodStreamID; private int lastGoodStreamID;
private SpdySessionStatus status;
/** /**
* Creates a new instance. * Creates a new instance.
@ -30,7 +31,28 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
* @param lastGoodStreamID the Last-good-stream-ID of this frame * @param lastGoodStreamID the Last-good-stream-ID of this frame
*/ */
public DefaultSpdyGoAwayFrame(int lastGoodStreamID) { 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); setLastGoodStreamID(lastGoodStreamID);
setStatus(status);
} }
public int getLastGoodStreamID() { public int getLastGoodStreamID() {
@ -45,6 +67,14 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
this.lastGoodStreamID = lastGoodStreamID; this.lastGoodStreamID = lastGoodStreamID;
} }
public SpdySessionStatus getStatus() {
return status;
}
public void setStatus(SpdySessionStatus status) {
this.status = status;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
@ -52,6 +82,9 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
buf.append(StringUtil.NEWLINE); buf.append(StringUtil.NEWLINE);
buf.append("--> Last-good-stream-ID = "); buf.append("--> Last-good-stream-ID = ");
buf.append(lastGoodStreamID); buf.append(lastGoodStreamID);
buf.append(StringUtil.NEWLINE);
buf.append("--> Status: ");
buf.append(status.toString());
return buf.toString(); return buf.toString();
} }
} }

View File

@ -24,6 +24,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
implements SpdyHeadersFrame { implements SpdyHeadersFrame {
private int streamID; private int streamID;
private boolean last;
/** /**
* Creates a new instance. * Creates a new instance.
@ -47,10 +48,21 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
this.streamID = streamID; this.streamID = streamID;
} }
public boolean isLast() {
return last;
}
public void setLast(boolean last) {
this.last = last;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName()); buf.append(getClass().getSimpleName());
buf.append("(last: ");
buf.append(isLast());
buf.append(')');
buf.append(StringUtil.NEWLINE); buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = "); buf.append("--> Stream-ID = ");
buf.append(streamID); buf.append(streamID);

View File

@ -74,9 +74,9 @@ public class DefaultSpdySynStreamFrame extends DefaultSpdyHeaderBlock
} }
public void setPriority(byte priority) { public void setPriority(byte priority) {
if (priority < 0 || priority > 3) { if (priority < 0 || priority > 7) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Priortiy must be between 0 and 3 inclusive: " + priority); "Priority must be between 0 and 7 inclusive: " + priority);
} }
this.priority = priority; this.priority = priority;
} }

View File

@ -19,7 +19,8 @@ import io.netty.buffer.ChannelBuffer;
final class SpdyCodecUtil { 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_TYPE_OFFSET = 2;
static final int SPDY_HEADER_FLAGS_OFFSET = 4; 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_GOAWAY_FRAME = 7;
static final int SPDY_HEADERS_FRAME = 8; static final int SPDY_HEADERS_FRAME = 8;
static final int SPDY_WINDOW_UPDATE_FRAME = 9; 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_FIN = 0x01;
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02; 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 static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field
// Zlib Dictionary // 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-" + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
@ -66,19 +249,19 @@ final class SpdyCodecUtil {
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
".1statusversionurl "; ".1statusversionurl ";
static final byte[] SPDY_DICT; static final byte[] SPDY2_DICT;
static { static {
byte[] SPDY_DICT_ = null; byte[] SPDY2_DICT_ = null;
try { try {
SPDY_DICT_ = SPDY_DICT_S.getBytes("US-ASCII"); SPDY2_DICT_ = SPDY2_DICT_S.getBytes("US-ASCII");
// dictionary is null terminated // dictionary is null terminated
SPDY_DICT_[SPDY_DICT_.length - 1] = (byte) 0; SPDY2_DICT_[SPDY2_DICT_.length - 1] = (byte) 0;
} catch (Exception e) { } catch (Exception e) {
SPDY_DICT_ = new byte[1]; SPDY2_DICT_ = new byte[1];
} }
SPDY_DICT = SPDY_DICT_; SPDY2_DICT = SPDY2_DICT_;
} }

View File

@ -32,23 +32,24 @@ public class SpdyFrameCodec implements ChannelUpstreamHandler,
private final SpdyFrameEncoder encoder; 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 maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
* {@code compressionLevel (6)}, {@code windowBits (15)}, and * {@code compressionLevel (6)}, {@code windowBits (15)},
* {@code memLevel (8)}). * and {@code memLevel (8)}).
*/ */
public SpdyFrameCodec() { public SpdyFrameCodec(int version) {
this(8192, 16384, 6, 15, 8); this(version, 8192, 16384, 6, 15, 8);
} }
/** /**
* Creates a new instance with the specified decoder and encoder options. * Creates a new instance with the specified decoder and encoder options.
*/ */
public SpdyFrameCodec( public SpdyFrameCodec(
int maxChunkSize, int maxHeaderSize, int version, int maxChunkSize, int maxHeaderSize,
int compressionLevel, int windowBits, int memLevel) { int compressionLevel, int windowBits, int memLevel) {
decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize); decoder = new SpdyFrameDecoder(version, maxChunkSize, maxHeaderSize);
encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel); encoder = new SpdyFrameEncoder(version, compressionLevel, windowBits, memLevel);
} }
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)

View File

@ -30,11 +30,11 @@ import io.netty.handler.codec.frame.TooLongFrameException;
*/ */
public class SpdyFrameDecoder extends FrameDecoder { public class SpdyFrameDecoder extends FrameDecoder {
private final int spdyVersion;
private final int maxChunkSize; private final int maxChunkSize;
private final int maxHeaderSize; private final int maxHeaderSize;
private final SpdyHeaderBlockDecompressor headerBlockDecompressor = private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
SpdyHeaderBlockDecompressor.newInstance();
private State state; private State state;
private SpdySettingsFrame spdySettingsFrame; private SpdySettingsFrame spdySettingsFrame;
@ -64,18 +64,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
/** /**
* Creates a new instance with the default {@code maxChunkSize (8192)} * Creates a new instance with the specified {@code version} and the default
* and {@code maxHeaderSize (16384)}. * {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}.
*/ */
public SpdyFrameDecoder() { public SpdyFrameDecoder(int version) {
this(8192, 16384); this(version, 8192, 16384);
} }
/** /**
* Creates a new instance with the specified parameters. * Creates a new instance with the specified parameters.
*/ */
public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) { public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
super(false); super(false);
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
if (maxChunkSize <= 0) { if (maxChunkSize <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " + maxChunkSize); "maxChunkSize must be a positive integer: " + maxChunkSize);
@ -84,8 +88,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"maxHeaderSize must be a positive integer: " + maxHeaderSize); "maxHeaderSize must be a positive integer: " + maxHeaderSize);
} }
spdyVersion = version;
this.maxChunkSize = maxChunkSize; this.maxChunkSize = maxChunkSize;
this.maxHeaderSize = maxHeaderSize; this.maxHeaderSize = maxHeaderSize;
headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
state = State.READ_COMMON_HEADER; state = State.READ_COMMON_HEADER;
} }
@ -109,7 +115,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
case READ_COMMON_HEADER: case READ_COMMON_HEADER:
state = readCommonHeader(buffer); state = readCommonHeader(buffer);
if (state == State.FRAME_ERROR) { if (state == State.FRAME_ERROR) {
if (version != SPDY_VERSION) { if (version != spdyVersion) {
fireProtocolException(ctx, "Unsupported version: " + version); fireProtocolException(ctx, "Unsupported version: " + version);
} else { } else {
fireInvalidControlFrameException(ctx); fireInvalidControlFrameException(ctx);
@ -175,13 +181,21 @@ public class SpdyFrameDecoder extends FrameDecoder {
int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3); int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
for (int i = 0; i < readableEntries; i ++) { for (int i = 0; i < readableEntries; i ++) {
// Chromium Issue 79156 int ID;
// SPDY setting ids are not written in network byte order byte ID_flags;
// Read id assuming the architecture is little endian if (version < 3) {
int ID = buffer.readByte() & 0xFF | // Chromium Issue 79156
// SPDY setting ids are not written in network byte order
// Read id assuming the architecture is little endian
ID = buffer.readByte() & 0xFF |
(buffer.readByte() & 0xFF) << 8 | (buffer.readByte() & 0xFF) << 8 |
(buffer.readByte() & 0xFF) << 16; (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()); int value = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4); buffer.skipBytes(4);
@ -327,7 +341,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
type = getUnsignedShort(buffer, typeOffset); type = getUnsignedShort(buffer, typeOffset);
// Check version first then validity // Check version first then validity
if (version != SPDY_VERSION || !isValidControlFrameHeader()) { if (version != spdyVersion || !isValidControlFrameHeader()) {
return State.FRAME_ERROR; return State.FRAME_ERROR;
} }
@ -364,6 +378,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
private Object readControlFrame(ChannelBuffer buffer) { private Object readControlFrame(ChannelBuffer buffer) {
int streamID; int streamID;
int statusCode;
switch (type) { switch (type) {
case SPDY_RST_STREAM_FRAME: case SPDY_RST_STREAM_FRAME:
if (buffer.readableBytes() < 8) { if (buffer.readableBytes() < 8) {
@ -371,7 +386,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
streamID = getUnsignedInt(buffer, buffer.readerIndex()); streamID = getUnsignedInt(buffer, buffer.readerIndex());
int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4); statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8); buffer.skipBytes(8);
return new DefaultSpdyRstStreamFrame(streamID, statusCode); return new DefaultSpdyRstStreamFrame(streamID, statusCode);
@ -387,14 +402,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
return new DefaultSpdyPingFrame(ID); return new DefaultSpdyPingFrame(ID);
case SPDY_GOAWAY_FRAME: case SPDY_GOAWAY_FRAME:
if (buffer.readableBytes() < 4) { int minLength = version < 3 ? 4 : 8;
if (buffer.readableBytes() < minLength) {
return null; return null;
} }
int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex()); int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4); buffer.skipBytes(4);
return new DefaultSpdyGoAwayFrame(lastGoodStreamID); 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: case SPDY_WINDOW_UPDATE_FRAME:
if (buffer.readableBytes() < 8) { if (buffer.readableBytes() < 8) {
@ -413,22 +436,27 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) { private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) {
int minLength;
int streamID; int streamID;
switch (type) { switch (type) {
case SPDY_SYN_STREAM_FRAME: case SPDY_SYN_STREAM_FRAME:
if (buffer.readableBytes() < 12) { minLength = version < 3 ? 12 : 10;
if (buffer.readableBytes() < minLength) {
return null; return null;
} }
int offset = buffer.readerIndex(); int offset = buffer.readerIndex();
streamID = getUnsignedInt(buffer, offset); streamID = getUnsignedInt(buffer, offset);
int associatedToStreamID = getUnsignedInt(buffer, offset + 4); 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); buffer.skipBytes(10);
length -= 10; length -= 10;
// SPDY/2 requires 16-bits of padding for empty header blocks // 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); buffer.skipBytes(2);
length = 0; length = 0;
} }
@ -441,16 +469,23 @@ public class SpdyFrameDecoder extends FrameDecoder {
return spdySynStreamFrame; return spdySynStreamFrame;
case SPDY_SYN_REPLY_FRAME: case SPDY_SYN_REPLY_FRAME:
if (buffer.readableBytes() < 8) { minLength = version < 3 ? 8 : 4;
if (buffer.readableBytes() < minLength) {
return null; return null;
} }
streamID = getUnsignedInt(buffer, buffer.readerIndex()); streamID = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(6); buffer.skipBytes(4);
length -= 6; 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 // 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); buffer.skipBytes(2);
length = 0; length = 0;
} }
@ -461,9 +496,12 @@ public class SpdyFrameDecoder extends FrameDecoder {
return spdySynReplyFrame; return spdySynReplyFrame;
case SPDY_HEADERS_FRAME: case SPDY_HEADERS_FRAME:
// Protocol allows length 4 frame when there are no name/value pairs if (buffer.readableBytes() < 4) {
int minLength = length == 4 ? 4 : 8; return null;
if (buffer.readableBytes() < minLength) { }
// SPDY/2 allows length 4 frame when there are no name/value pairs
if (version < 3 && length > 4 && buffer.readableBytes() < 8) {
return null; return null;
} }
@ -471,16 +509,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
buffer.skipBytes(4); buffer.skipBytes(4);
length -= 4; length -= 4;
// SPDY/2 requires 16-bits of padding for empty header blocks // SPDY/2 has 16-bits of unused space
if (length == 4 && buffer.getShort(buffer.readerIndex() + 2) == 0) { if (version < 3 && length != 0) {
buffer.skipBytes(4);
length = 0;
} else if (length != 0) {
buffer.skipBytes(2); buffer.skipBytes(2);
length -= 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: default:
throw new Error("Shouldn't reach here."); throw new Error("Shouldn't reach here.");
@ -497,7 +541,11 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
private int readLengthField() { private int readLengthField() {
return decompressed.readUnsignedShort(); if (version < 3) {
return decompressed.readUnsignedShort();
} else {
return decompressed.readInt();
}
} }
private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception { private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
@ -519,7 +567,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
return; return;
} }
int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields int lengthFieldSize = version < 3 ? 2 : 4;
if (numHeaders == -1) { if (numHeaders == -1) {
// Read number of Name/Value pairs // Read number of Name/Value pairs
@ -527,6 +575,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
return; return;
} }
numHeaders = readLengthField(); numHeaders = readLengthField();
if (numHeaders < 0) {
spdyHeaderBlock.setInvalid();
return;
}
} }
while (numHeaders > 0) { while (numHeaders > 0) {
@ -542,7 +594,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
int nameLength = readLengthField(); int nameLength = readLengthField();
// Recipients of a zero-length name must issue a stream error // Recipients of a zero-length name must issue a stream error
if (nameLength == 0) { if (nameLength <= 0) {
spdyHeaderBlock.setInvalid(); spdyHeaderBlock.setInvalid();
return; return;
} }
@ -577,7 +629,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
int valueLength = readLengthField(); int valueLength = readLengthField();
// Recipients of illegal value fields must issue a stream error // Recipients of illegal value fields must issue a stream error
if (valueLength == 0) { if (valueLength <= 0) {
spdyHeaderBlock.setInvalid(); spdyHeaderBlock.setInvalid();
return; return;
} }
@ -630,10 +682,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
private boolean isValidControlFrameHeader() { private boolean isValidControlFrameHeader() {
switch (type) { switch (type) {
case SPDY_SYN_STREAM_FRAME: case SPDY_SYN_STREAM_FRAME:
return length >= 12; return version < 3 ? length >= 12 : length >= 10;
case SPDY_SYN_REPLY_FRAME: case SPDY_SYN_REPLY_FRAME:
return length >= 8; return version < 3 ? length >= 8 : length >= 4;
case SPDY_RST_STREAM_FRAME: case SPDY_RST_STREAM_FRAME:
return flags == 0 && length == 8; return flags == 0 && length == 8;
@ -648,14 +700,19 @@ public class SpdyFrameDecoder extends FrameDecoder {
return length == 4; return length == 4;
case SPDY_GOAWAY_FRAME: case SPDY_GOAWAY_FRAME:
return length == 4; return version < 3 ? length == 4 : length == 8;
case SPDY_HEADERS_FRAME: case SPDY_HEADERS_FRAME:
return length == 4 || length >= 8; if (version < 3) {
return length == 4 || length >= 8;
} else {
return length >= 4;
}
case SPDY_WINDOW_UPDATE_FRAME: case SPDY_WINDOW_UPDATE_FRAME:
return length == 8; return length == 8;
case SPDY_CREDENTIAL_FRAME:
default: default:
return true; return true;
} }
@ -674,6 +731,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
return true; return true;
case SPDY_NOOP_FRAME: case SPDY_NOOP_FRAME:
case SPDY_CREDENTIAL_FRAME:
default: default:
return false; return false;
} }
@ -717,6 +775,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
case SPDY_WINDOW_UPDATE_FRAME: case SPDY_WINDOW_UPDATE_FRAME:
message = "Received invalid WINDOW_UPDATE control frame"; message = "Received invalid WINDOW_UPDATE control frame";
break; break;
case SPDY_CREDENTIAL_FRAME:
message = "Received invalid CREDENTIAL control frame";
break;
} }
fireProtocolException(ctx, message); fireProtocolException(ctx, message);
} }

View File

@ -33,23 +33,31 @@ import io.netty.handler.codec.oneone.OneToOneEncoder;
*/ */
public class SpdyFrameEncoder extends OneToOneEncoder { public class SpdyFrameEncoder extends OneToOneEncoder {
private final int version;
private volatile boolean finished; private volatile boolean finished;
private final SpdyHeaderBlockCompressor headerBlockCompressor; private final SpdyHeaderBlockCompressor headerBlockCompressor;
/** /**
* Creates a new instance with the default {@code compressionLevel (6)}, * Creates a new instance with the specified {@code version} and the
* {@code windowBits (15)}, and {@code memLevel (8)}. * default {@code compressionLevel (6)}, {@code windowBits (15)},
* and {@code memLevel (8)}.
*/ */
public SpdyFrameEncoder() { public SpdyFrameEncoder(int version) {
this(6, 15, 8); this(version, 6, 15, 8);
} }
/** /**
* Creates a new instance with the specified parameters. * 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(); 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 @Override
@ -93,23 +101,37 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
ChannelBuffer data = compressHeaderBlock( ChannelBuffer data = compressHeaderBlock(
encodeHeaderBlock(spdySynStreamFrame)); encodeHeaderBlock(version, spdySynStreamFrame));
byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0; byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
if (spdySynStreamFrame.isUnidirectional()) { if (spdySynStreamFrame.isUnidirectional()) {
flags |= SPDY_FLAG_UNIDIRECTIONAL; flags |= SPDY_FLAG_UNIDIRECTIONAL;
} }
int headerBlockLength = data.readableBytes(); 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( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_SYN_STREAM_FRAME); frame.writeShort(SPDY_SYN_STREAM_FRAME);
frame.writeByte(flags); frame.writeByte(flags);
frame.writeMedium(length); frame.writeMedium(length);
frame.writeInt(spdySynStreamFrame.getStreamID()); frame.writeInt(spdySynStreamFrame.getStreamID());
frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID()); frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID());
frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 14); if (version < 3) {
if (data.readableBytes() == 0) { // 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); frame.writeShort(0);
} }
return ChannelBuffers.wrappedBuffer(frame, data); return ChannelBuffers.wrappedBuffer(frame, data);
@ -118,21 +140,28 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
ChannelBuffer data = compressHeaderBlock( ChannelBuffer data = compressHeaderBlock(
encodeHeaderBlock(spdySynReplyFrame)); encodeHeaderBlock(version, spdySynReplyFrame));
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0; byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
int headerBlockLength = data.readableBytes(); 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( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_SYN_REPLY_FRAME); frame.writeShort(SPDY_SYN_REPLY_FRAME);
frame.writeByte(flags); frame.writeByte(flags);
frame.writeMedium(length); frame.writeMedium(length);
frame.writeInt(spdySynReplyFrame.getStreamID()); frame.writeInt(spdySynReplyFrame.getStreamID());
if (data.readableBytes() == 0) { if (version < 3) {
frame.writeInt(0); if (data.readableBytes() == 0) {
} else { frame.writeInt(0);
frame.writeShort(0); } else {
frame.writeShort(0);
}
} }
return ChannelBuffers.wrappedBuffer(frame, data); return ChannelBuffers.wrappedBuffer(frame, data);
@ -141,7 +170,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
ChannelBuffer frame = ChannelBuffers.buffer( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_RST_STREAM_FRAME); frame.writeShort(SPDY_RST_STREAM_FRAME);
frame.writeInt(8); frame.writeInt(8);
frame.writeInt(spdyRstStreamFrame.getStreamID()); frame.writeInt(spdyRstStreamFrame.getStreamID());
@ -158,7 +187,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
int length = 4 + numEntries * 8; int length = 4 + numEntries * 8;
ChannelBuffer frame = ChannelBuffers.buffer( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_SETTINGS_FRAME); frame.writeShort(SPDY_SETTINGS_FRAME);
frame.writeByte(flags); frame.writeByte(flags);
frame.writeMedium(length); frame.writeMedium(length);
@ -172,13 +201,18 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
if (spdySettingsFrame.isPersisted(id)) { if (spdySettingsFrame.isPersisted(id)) {
ID_flags |= SPDY_SETTINGS_PERSISTED; ID_flags |= SPDY_SETTINGS_PERSISTED;
} }
// Chromium Issue 79156 if (version < 3) {
// SPDY setting ids are not written in network byte order // Chromium Issue 79156
// Write id assuming the architecture is little endian // SPDY setting ids are not written in network byte order
frame.writeByte(id >> 0 & 0xFF); // Write id assuming the architecture is little endian
frame.writeByte(id >> 8 & 0xFF); frame.writeByte(id >> 0 & 0xFF);
frame.writeByte(id >> 16 & 0xFF); frame.writeByte(id >> 8 & 0xFF);
frame.writeByte(ID_flags); frame.writeByte(id >> 16 & 0xFF);
frame.writeByte(ID_flags);
} else {
frame.writeByte(ID_flags);
frame.writeMedium(id);
}
frame.writeInt(spdySettingsFrame.getValue(id)); frame.writeInt(spdySettingsFrame.getValue(id));
} }
return frame; return frame;
@ -187,7 +221,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
ChannelBuffer frame = ChannelBuffers.buffer( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_NOOP_FRAME); frame.writeShort(SPDY_NOOP_FRAME);
frame.writeInt(0); frame.writeInt(0);
return frame; return frame;
@ -197,7 +231,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg; SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
ChannelBuffer frame = ChannelBuffers.buffer( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_PING_FRAME); frame.writeShort(SPDY_PING_FRAME);
frame.writeInt(4); frame.writeInt(4);
frame.writeInt(spdyPingFrame.getID()); frame.writeInt(spdyPingFrame.getID());
@ -206,28 +240,39 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
} else if (msg instanceof SpdyGoAwayFrame) { } else if (msg instanceof SpdyGoAwayFrame) {
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg; SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
int length = version < 3 ? 4 : 8;
ChannelBuffer frame = ChannelBuffers.buffer( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_GOAWAY_FRAME); frame.writeShort(SPDY_GOAWAY_FRAME);
frame.writeInt(4); frame.writeInt(length);
frame.writeInt(spdyGoAwayFrame.getLastGoodStreamID()); frame.writeInt(spdyGoAwayFrame.getLastGoodStreamID());
if (version >= 3) {
frame.writeInt(spdyGoAwayFrame.getStatus().getCode());
}
return frame; return frame;
} else if (msg instanceof SpdyHeadersFrame) { } else if (msg instanceof SpdyHeadersFrame) {
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
ChannelBuffer data = compressHeaderBlock( ChannelBuffer data = compressHeaderBlock(
encodeHeaderBlock(spdyHeadersFrame)); encodeHeaderBlock(version, spdyHeadersFrame));
byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
int headerBlockLength = data.readableBytes(); 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( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_HEADERS_FRAME); frame.writeShort(SPDY_HEADERS_FRAME);
frame.writeInt(length); frame.writeByte(flags);
frame.writeMedium(length);
frame.writeInt(spdyHeadersFrame.getStreamID()); frame.writeInt(spdyHeadersFrame.getStreamID());
if (data.readableBytes() != 0) { if (version < 3 && data.readableBytes() != 0) {
frame.writeShort(0); frame.writeShort(0);
} }
return ChannelBuffers.wrappedBuffer(frame, data); return ChannelBuffers.wrappedBuffer(frame, data);
@ -237,7 +282,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg; SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
ChannelBuffer frame = ChannelBuffers.buffer( ChannelBuffer frame = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8);
frame.writeShort(SPDY_VERSION | 0x8000); frame.writeShort(version | 0x8000);
frame.writeShort(SPDY_WINDOW_UPDATE_FRAME); frame.writeShort(SPDY_WINDOW_UPDATE_FRAME);
frame.writeInt(8); frame.writeInt(8);
frame.writeInt(spdyWindowUpdateFrame.getStreamID()); frame.writeInt(spdyWindowUpdateFrame.getStreamID());
@ -249,7 +294,23 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
return msg; 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 { throws Exception {
Set<String> names = headerFrame.getHeaderNames(); Set<String> names = headerFrame.getHeaderNames();
int numHeaders = names.size(); int numHeaders = names.size();
@ -262,14 +323,14 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
} }
ChannelBuffer headerBlock = ChannelBuffers.dynamicBuffer( ChannelBuffer headerBlock = ChannelBuffers.dynamicBuffer(
ByteOrder.BIG_ENDIAN, 256); ByteOrder.BIG_ENDIAN, 256);
headerBlock.writeShort(numHeaders); writeLengthField(version, headerBlock, numHeaders);
for (String name: names) { for (String name: names) {
byte[] nameBytes = name.getBytes("UTF-8"); byte[] nameBytes = name.getBytes("UTF-8");
headerBlock.writeShort(nameBytes.length); writeLengthField(version, headerBlock, nameBytes.length);
headerBlock.writeBytes(nameBytes); headerBlock.writeBytes(nameBytes);
int savedIndex = headerBlock.writerIndex(); int savedIndex = headerBlock.writerIndex();
int valueLength = 0; int valueLength = 0;
headerBlock.writeShort(valueLength); writeLengthField(version, headerBlock, valueLength);
for (String value: headerFrame.getHeaders(name)) { for (String value: headerFrame.getHeaders(name)) {
byte[] valueBytes = value.getBytes("UTF-8"); byte[] valueBytes = value.getBytes("UTF-8");
headerBlock.writeBytes(valueBytes); headerBlock.writeBytes(valueBytes);
@ -281,7 +342,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"header exceeds allowable length: " + name); "header exceeds allowable length: " + name);
} }
headerBlock.setShort(savedIndex, valueLength); setLengthField(version, headerBlock, savedIndex, valueLength);
headerBlock.writerIndex(headerBlock.writerIndex() - 1); headerBlock.writerIndex(headerBlock.writerIndex() - 1);
} }
return headerBlock; return headerBlock;

View File

@ -30,4 +30,14 @@ public interface SpdyGoAwayFrame {
* cannot be negative. * cannot be negative.
*/ */
void setLastGoodStreamID(int lastGoodStreamID); void setLastGoodStreamID(int lastGoodStreamID);
/**
* Returns the status of this frame.
*/
SpdySessionStatus getStatus();
/**
* Sets the status of this frame.
*/
void setStatus(SpdySessionStatus status);
} }

View File

@ -21,13 +21,14 @@ import io.netty.util.internal.DetectionUtil;
abstract class SpdyHeaderBlockCompressor { abstract class SpdyHeaderBlockCompressor {
static SpdyHeaderBlockCompressor newInstance( static SpdyHeaderBlockCompressor newInstance(
int compressionLevel, int windowBits, int memLevel) { int version, int compressionLevel, int windowBits, int memLevel) {
if (DetectionUtil.javaVersion() >= 7) { if (DetectionUtil.javaVersion() >= 7) {
return new SpdyHeaderBlockZlibCompressor(compressionLevel); return new SpdyHeaderBlockZlibCompressor(
version, compressionLevel);
} else { } else {
return new SpdyHeaderBlockJZlibCompressor( return new SpdyHeaderBlockJZlibCompressor(
compressionLevel, windowBits, memLevel); version, compressionLevel, windowBits, memLevel);
} }
} }

View File

@ -19,8 +19,8 @@ import io.netty.buffer.ChannelBuffer;
abstract class SpdyHeaderBlockDecompressor { abstract class SpdyHeaderBlockDecompressor {
static SpdyHeaderBlockDecompressor newInstance() { static SpdyHeaderBlockDecompressor newInstance(int version) {
return new SpdyHeaderBlockZlibDecompressor(); return new SpdyHeaderBlockZlibDecompressor(version);
} }
abstract void setInput(ChannelBuffer compressed); abstract void setInput(ChannelBuffer compressed);

View File

@ -26,7 +26,12 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
private final ZStream z = new ZStream(); 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) { if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)"); "compressionLevel: " + compressionLevel + " (expected: 0-9)");
@ -46,7 +51,11 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
throw new CompressionException( throw new CompressionException(
"failed to initialize an SPDY header block deflater: " + resultCode); "failed to initialize an SPDY header block deflater: " + resultCode);
} else { } else {
resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length); 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) { if (resultCode != JZlib.Z_OK) {
throw new CompressionException( throw new CompressionException(
"failed to set the SPDY dictionary: " + resultCode); "failed to set the SPDY dictionary: " + resultCode);

View File

@ -26,13 +26,21 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
private final byte[] out = new byte[8192]; private final byte[] out = new byte[8192];
private final Deflater compressor; 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) { if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)"); "compressionLevel: " + compressionLevel + " (expected: 0-9)");
} }
compressor = new Deflater(compressionLevel); compressor = new Deflater(compressionLevel);
compressor.setDictionary(SPDY_DICT); if (version < 3) {
compressor.setDictionary(SPDY2_DICT);
} else {
compressor.setDictionary(SPDY_DICT);
}
} }
@Override @Override

View File

@ -24,9 +24,18 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor { class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
private final int version;
private final byte[] out = new byte[8192]; private final byte[] out = new byte[8192];
private final Inflater decompressor = new Inflater(); 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 @Override
public void setInput(ChannelBuffer compressed) { public void setInput(ChannelBuffer compressed) {
byte[] in = new byte[compressed.readableBytes()]; byte[] in = new byte[compressed.readableBytes()];
@ -39,7 +48,11 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
try { try {
int numBytes = decompressor.inflate(out); int numBytes = decompressor.inflate(out);
if (numBytes == 0 && decompressor.needsDictionary()) { if (numBytes == 0 && decompressor.needsDictionary()) {
decompressor.setDictionary(SPDY_DICT); if (version < 3) {
decompressor.setDictionary(SPDY2_DICT);
} else {
decompressor.setDictionary(SPDY_DICT);
}
numBytes = decompressor.inflate(out); numBytes = decompressor.inflate(out);
} }
decompressed.writeBytes(out, 0, numBytes); decompressed.writeBytes(out, 0, numBytes);

View File

@ -27,7 +27,7 @@ import io.netty.handler.codec.http.HttpVersion;
/** /**
* Provides the constants for the standard SPDY HTTP header names and commonly * 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 * @apiviz.stereotype static
*/ */
public class SpdyHeaders { public class SpdyHeaders {
@ -37,6 +37,41 @@ public class SpdyHeaders {
* @apiviz.stereotype static * @apiviz.stereotype static
*/ */
public static final class HttpNames { 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"} * {@code "method"}
*/ */
@ -58,7 +93,7 @@ public class SpdyHeaders {
*/ */
public static final String VERSION = "version"; public static final String VERSION = "version";
private HttpNames() { private Spdy2HttpNames() {
super(); super();
} }
} }
@ -115,64 +150,118 @@ public class SpdyHeaders {
} }
/** /**
* Removes the {@code "method"} header. * Removes the SPDY host header.
*/ */
public static void removeMethod(SpdyHeaderBlock block) { public static void removeHost(SpdyHeaderBlock block) {
block.removeHeader(HttpNames.METHOD); 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 { try {
return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD)); if (spdyVersion < 3) {
return HttpMethod.valueOf(block.getHeader(Spdy2HttpNames.METHOD));
} else {
return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
}
} catch (Exception e) { } catch (Exception e) {
return null; 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) {
block.setHeader(HttpNames.METHOD, method.getName()); 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) {
block.removeHeader(HttpNames.SCHEME); 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) {
return block.getHeader(HttpNames.SCHEME); if (spdyVersion < 3) {
return block.getHeader(Spdy2HttpNames.SCHEME);
} else {
return block.getHeader(HttpNames.SCHEME);
}
} }
/** /**
* Sets the {@code "scheme"} header. * Sets the URL scheme header.
*/ */
public static void setScheme(SpdyHeaderBlock block, String value) { public static void setScheme(int spdyVersion, SpdyHeaderBlock block, String scheme) {
block.setHeader(HttpNames.SCHEME, value); if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.SCHEME, scheme);
} else {
block.setHeader(HttpNames.SCHEME, scheme);
}
} }
/** /**
* Removes the {@code "status"} header. * Removes the HTTP response status header.
*/ */
public static void removeStatus(SpdyHeaderBlock block) { public static void removeStatus(int spdyVersion, SpdyHeaderBlock block) {
block.removeHeader(HttpNames.STATUS); 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 { 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(' '); int space = status.indexOf(' ');
if (space == -1) { if (space == -1) {
return HttpResponseStatus.valueOf(Integer.parseInt(status)); return HttpResponseStatus.valueOf(Integer.parseInt(status));
@ -180,7 +269,7 @@ public class SpdyHeaders {
int code = Integer.parseInt(status.substring(0, space)); int code = Integer.parseInt(status.substring(0, space));
String reasonPhrase = status.substring(space + 1); String reasonPhrase = status.substring(space + 1);
HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code); HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
if (responseStatus.getReasonPhrase().equals(responseStatus)) { if (responseStatus.getReasonPhrase().equals(reasonPhrase)) {
return responseStatus; return responseStatus;
} else { } else {
return new HttpResponseStatus(code, reasonPhrase); 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) {
block.setHeader(HttpNames.STATUS, status.toString()); if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.STATUS, status.toString());
} else {
block.setHeader(HttpNames.STATUS, status.toString());
}
} }
/** /**
* Removes the {@code "url"} header. * Removes the URL path header.
*/ */
public static void removeUrl(SpdyHeaderBlock block) { public static void removeUrl(int spdyVersion, SpdyHeaderBlock block) {
block.removeHeader(HttpNames.URL); if (spdyVersion < 3) {
block.removeHeader(Spdy2HttpNames.URL);
} else {
block.removeHeader(HttpNames.PATH);
}
} }
/** /**
* Returns the value of the {@code "url"} header. * Returns the value of the URL path header.
*/ */
public static String getUrl(SpdyHeaderBlock block) { public static String getUrl(int spdyVersion, SpdyHeaderBlock block) {
return block.getHeader(HttpNames.URL); if (spdyVersion < 3) {
return block.getHeader(Spdy2HttpNames.URL);
} else {
return block.getHeader(HttpNames.PATH);
}
} }
/** /**
* Sets the {@code "url"} header. * Sets the URL path header.
*/ */
public static void setUrl(SpdyHeaderBlock block, String value) { public static void setUrl(int spdyVersion, SpdyHeaderBlock block, String path) {
block.setHeader(HttpNames.URL, value); if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.URL, path);
} else {
block.setHeader(HttpNames.PATH, path);
}
} }
/** /**
* Removes the {@code "version"} header. * Removes the HTTP version header.
*/ */
public static void removeVersion(SpdyHeaderBlock block) { public static void removeVersion(int spdyVersion, SpdyHeaderBlock block) {
block.removeHeader(HttpNames.VERSION); 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 { try {
return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION)); if (spdyVersion < 3) {
return HttpVersion.valueOf(block.getHeader(Spdy2HttpNames.VERSION));
} else {
return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
}
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
/** /**
* Sets the {@code "version"} header. * Sets the HTTP version header.
*/ */
public static void setVersion(SpdyHeaderBlock block, HttpVersion version) { public static void setVersion(int spdyVersion, SpdyHeaderBlock block, HttpVersion httpVersion) {
block.setHeader(HttpNames.VERSION, version.getText()); if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.VERSION, httpVersion.getText());
} else {
block.setHeader(HttpNames.VERSION, httpVersion.getText());
}
} }

View File

@ -29,4 +29,15 @@ public interface SpdyHeadersFrame extends SpdyHeaderBlock {
* Sets the Stream-ID of this frame. The Stream-ID must be positive. * Sets the Stream-ID of this frame. The Stream-ID must be positive.
*/ */
void setStreamID(int streamID); 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);
} }

View File

@ -28,13 +28,14 @@ import io.netty.channel.ChannelUpstreamHandler;
public class SpdyHttpCodec implements ChannelUpstreamHandler, ChannelDownstreamHandler { public class SpdyHttpCodec implements ChannelUpstreamHandler, ChannelDownstreamHandler {
private final SpdyHttpDecoder decoder; private final SpdyHttpDecoder decoder;
private final SpdyHttpEncoder encoder = new SpdyHttpEncoder(); private final SpdyHttpEncoder encoder;
/** /**
* Creates a new instance with the specified decoder options. * Creates a new instance with the specified decoder options.
*/ */
public SpdyHttpCodec(int maxContentLength) { public SpdyHttpCodec(int version, int maxContentLength) {
decoder = new SpdyHttpDecoder(maxContentLength); decoder = new SpdyHttpDecoder(version, maxContentLength);
encoder = new SpdyHttpEncoder(version);
} }
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)

View File

@ -15,6 +15,8 @@
*/ */
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -42,22 +44,29 @@ import io.netty.handler.codec.oneone.OneToOneDecoder;
*/ */
public class SpdyHttpDecoder extends OneToOneDecoder { public class SpdyHttpDecoder extends OneToOneDecoder {
private final int spdyVersion;
private final int maxContentLength; private final int maxContentLength;
private final Map<Integer, HttpMessage> messageMap = new HashMap<Integer, HttpMessage>(); private final Map<Integer, HttpMessage> messageMap = new HashMap<Integer, HttpMessage>();
/** /**
* Creates a new instance. * Creates a new instance.
* *
* @param version the protocol version
* @param maxContentLength the maximum length of the message content. * @param maxContentLength the maximum length of the message content.
* If the length of the message content exceeds this value, * If the length of the message content exceeds this value,
* a {@link TooLongFrameException} will be raised. * a {@link TooLongFrameException} will be raised.
*/ */
public SpdyHttpDecoder(int maxContentLength) { public SpdyHttpDecoder(int version, int maxContentLength) {
super(); super();
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
if (maxContentLength <= 0) { if (maxContentLength <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"maxContentLength must be a positive integer: " + maxContentLength); "maxContentLength must be a positive integer: " + maxContentLength);
} }
spdyVersion = version;
this.maxContentLength = maxContentLength; this.maxContentLength = maxContentLength;
} }
@ -72,7 +81,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
int streamID = spdySynStreamFrame.getStreamID(); int streamID = spdySynStreamFrame.getStreamID();
if (SpdyCodecUtil.isServerID(streamID)) { 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(); int associatedToStreamID = spdySynStreamFrame.getAssociatedToStreamID();
// If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0 // 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); 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 // If a client receives a SYN_STREAM without a 'url' header
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR // it must reply with a RST_STREAM with error code PROTOCOL_ERROR
@ -94,7 +103,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
} }
try { try {
HttpResponse httpResponse = createHttpResponse(spdySynStreamFrame); HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
SpdyHttpHeaders.setStreamID(httpResponse, streamID); SpdyHttpHeaders.setStreamID(httpResponse, streamID);
@ -118,7 +127,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
} else { } else {
// SYN_STREAM frames initiated by the client are HTTP requests // SYN_STREAM frames initiated by the client are HTTP requests
try { try {
HttpRequest httpRequest = createHttpRequest(spdySynStreamFrame); HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
// Set the Stream-ID as a header // Set the Stream-ID as a header
SpdyHttpHeaders.setStreamID(httpRequest, streamID); SpdyHttpHeaders.setStreamID(httpRequest, streamID);
@ -130,13 +139,13 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
messageMap.put(new Integer(streamID), httpRequest); messageMap.put(new Integer(streamID), httpRequest);
} }
} catch (Exception e) { } catch (Exception e) {
// If a client sends a SYN_STREAM without method, url, and version headers // If a client sends a SYN_STREAM without all of the method, url (host and path),
// the server must reply with a HTTP 400 BAD REQUEST reply // 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 // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
spdySynReplyFrame.setLast(true); spdySynReplyFrame.setLast(true);
SpdyHeaders.setStatus(spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST); SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
SpdyHeaders.setVersion(spdySynReplyFrame, HttpVersion.HTTP_1_0); SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
Channels.write(ctx, Channels.future(channel), spdySynReplyFrame); Channels.write(ctx, Channels.future(channel), spdySynReplyFrame);
} }
} }
@ -147,7 +156,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
int streamID = spdySynReplyFrame.getStreamID(); int streamID = spdySynReplyFrame.getStreamID();
try { try {
HttpResponse httpResponse = createHttpResponse(spdySynReplyFrame); HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
// Set the Stream-ID as a header // Set the Stream-ID as a header
SpdyHttpHeaders.setStreamID(httpResponse, streamID); SpdyHttpHeaders.setStreamID(httpResponse, streamID);
@ -215,67 +224,72 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
messageMap.remove(streamID); messageMap.remove(streamID);
return httpMessage; return httpMessage;
} }
} else if (msg instanceof SpdyRstStreamFrame) {
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
Integer streamID = new Integer(spdyRstStreamFrame.getStreamID());
messageMap.remove(streamID);
} }
return null; return null;
} }
private static HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame) private static HttpRequest createHttpRequest(int spdyVersion, SpdyHeaderBlock requestFrame)
throws Exception { throws Exception {
// Create the first line of the request from the name/value pairs // Create the first line of the request from the name/value pairs
HttpMethod method = SpdyHeaders.getMethod(requestFrame); HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame);
String url = SpdyHeaders.getUrl(requestFrame); String url = SpdyHeaders.getUrl(spdyVersion, requestFrame);
HttpVersion version = SpdyHeaders.getVersion(requestFrame); HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
SpdyHeaders.removeMethod(requestFrame); SpdyHeaders.removeMethod(spdyVersion, requestFrame);
SpdyHeaders.removeUrl(requestFrame); SpdyHeaders.removeUrl(spdyVersion, requestFrame);
SpdyHeaders.removeVersion(requestFrame); SpdyHeaders.removeVersion(spdyVersion, requestFrame);
HttpRequest httpRequest = new DefaultHttpRequest(version, method, url); HttpRequest httpRequest = new DefaultHttpRequest(httpVersion, method, url);
for (Map.Entry<String, String> e: requestFrame.getHeaders()) {
httpRequest.addHeader(e.getKey(), e.getValue()); // 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 for (Map.Entry<String, String> e: requestFrame.getHeaders()) {
List<String> encodings = httpRequest.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); httpRequest.addHeader(e.getKey(), e.getValue());
encodings.remove(HttpHeaders.Values.CHUNKED);
if (encodings.isEmpty()) {
httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
} else {
httpRequest.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings);
} }
// The Connection and Keep-Alive headers are no longer valid // The Connection and Keep-Alive headers are no longer valid
HttpHeaders.setKeepAlive(httpRequest, true); HttpHeaders.setKeepAlive(httpRequest, true);
// Transfer-Encoding header is not valid
httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
return httpRequest; return httpRequest;
} }
private static HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame) private static HttpResponse createHttpResponse(int spdyVersion, SpdyHeaderBlock responseFrame)
throws Exception { throws Exception {
// Create the first line of the response from the name/value pairs // Create the first line of the response from the name/value pairs
HttpResponseStatus status = SpdyHeaders.getStatus(responseFrame); HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
HttpVersion version = SpdyHeaders.getVersion(responseFrame); HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
SpdyHeaders.removeStatus(responseFrame); SpdyHeaders.removeStatus(spdyVersion, responseFrame);
SpdyHeaders.removeVersion(responseFrame); SpdyHeaders.removeVersion(spdyVersion, responseFrame);
HttpResponse httpResponse = new DefaultHttpResponse(version, status); HttpResponse httpResponse = new DefaultHttpResponse(version, status);
for (Map.Entry<String, String> e: responseFrame.getHeaders()) { for (Map.Entry<String, String> e: responseFrame.getHeaders()) {
httpResponse.addHeader(e.getKey(), e.getValue()); 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 // The Connection and Keep-Alive headers are no longer valid
HttpHeaders.setKeepAlive(httpResponse, true); HttpHeaders.setKeepAlive(httpResponse, true);
// Transfer-Encoding header is not valid
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
httpResponse.removeHeader(HttpHeaders.Names.TRAILER);
return httpResponse; return httpResponse;
} }
} }

View File

@ -15,6 +15,8 @@
*/ */
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -51,8 +53,8 @@ import io.netty.handler.codec.http.HttpResponse;
* <tr> * <tr>
* <td>{@code "X-SPDY-Priority"}</td> * <td>{@code "X-SPDY-Priority"}</td>
* <td>The priority value for this request. * <td>The priority value for this request.
* The priority should be between 0 and 3 inclusive. * The priority should be between 0 and 7 inclusive.
* 0 represents the highest priority and 3 represents the lowest. * 0 represents the highest priority and 7 represents the lowest.
* This header is optional and defaults to 0.</td> * This header is optional and defaults to 0.</td>
* </tr> * </tr>
* </table> * </table>
@ -84,21 +86,32 @@ import io.netty.handler.codec.http.HttpResponse;
* </tr> * </tr>
* <tr> * <tr>
* <td>{@code "X-SPDY-Associated-To-Stream-ID"}</td> * <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>
* <tr> * <tr>
* <td>{@code "X-SPDY-Priority"}</td> * <td>{@code "X-SPDY-Priority"}</td>
* <td>The priority value for this resource. * <td>The priority value for this resource.
* The priority should be between 0 and 3 inclusive. * The priority should be between 0 and 7 inclusive.
* 0 represents the highest priority and 3 represents the lowest. * 0 represents the highest priority and 7 represents the lowest.
* This header is optional and defaults to 0.</td> * This header is optional and defaults to 0.</td>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@code "X-SPDY-URL"}</td> * <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> * </tr>
* </table> * </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> * <h3>Chunked Content</h3>
* *
* This encoder associates all {@link HttpChunk}s that it receives * 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 { public class SpdyHttpEncoder implements ChannelDownstreamHandler {
private final int spdyVersion;
private volatile int currentStreamID; 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) public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
@ -228,15 +252,17 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
throws Exception { throws Exception {
boolean chunked = httpMessage.isChunked(); 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 streamID = SpdyHttpHeaders.getStreamID(httpMessage);
int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage); int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage);
byte priority = SpdyHttpHeaders.getPriority(httpMessage); byte priority = SpdyHttpHeaders.getPriority(httpMessage);
String URL = SpdyHttpHeaders.getUrl(httpMessage); String URL = SpdyHttpHeaders.getUrl(httpMessage);
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
SpdyHttpHeaders.removeStreamID(httpMessage); SpdyHttpHeaders.removeStreamID(httpMessage);
SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage); SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage);
SpdyHttpHeaders.removePriority(httpMessage); SpdyHttpHeaders.removePriority(httpMessage);
SpdyHttpHeaders.removeUrl(httpMessage); SpdyHttpHeaders.removeUrl(httpMessage);
SpdyHttpHeaders.removeScheme(httpMessage);
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
// headers are not valid and MUST not be sent. // headers are not valid and MUST not be sent.
@ -246,24 +272,39 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority); 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 // Unfold the first line of the message into name/value pairs
SpdyHeaders.setVersion(spdySynStreamFrame, httpMessage.getProtocolVersion());
if (httpMessage instanceof HttpRequest) { if (httpMessage instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) httpMessage; HttpRequest httpRequest = (HttpRequest) httpMessage;
SpdyHeaders.setMethod(spdySynStreamFrame, httpRequest.getMethod()); SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
SpdyHeaders.setUrl(spdySynStreamFrame, httpRequest.getUri()); SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
} }
if (httpMessage instanceof HttpResponse) { if (httpMessage instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) httpMessage; HttpResponse httpResponse = (HttpResponse) httpMessage;
SpdyHeaders.setStatus(spdySynStreamFrame, httpResponse.getStatus()); SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
SpdyHeaders.setUrl(spdySynStreamFrame, URL); SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
spdySynStreamFrame.setUnidirectional(true); 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) { if (chunked) {
currentStreamID = streamID; currentStreamID = streamID;
spdySynStreamFrame.setLast(false); spdySynStreamFrame.setLast(false);
@ -290,14 +331,16 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); 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()) { for (Map.Entry<String, String> entry: httpResponse.getHeaders()) {
spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue()); 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) { if (chunked) {
currentStreamID = streamID; currentStreamID = streamID;
spdySynReplyFrame.setLast(false); spdySynReplyFrame.setLast(false);

View File

@ -46,6 +46,10 @@ public final class SpdyHttpHeaders {
* {@code "X-SPDY-URL"} * {@code "X-SPDY-URL"}
*/ */
public static final String URL = "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() { private Names() {
super(); super();
@ -144,4 +148,25 @@ public final class SpdyHttpHeaders {
public static void setUrl(HttpMessage message, String url) { public static void setUrl(HttpMessage message, String url) {
message.setHeader(Names.URL, 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);
}
} }

View File

@ -15,11 +15,20 @@
*/ */
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.channel.MessageEvent;
final class SpdySession { final class SpdySession {
private static final SpdyProtocolException STREAM_CLOSED = new SpdyProtocolException("Stream closed");
private final Map<Integer, StreamState> activeStreams = private final Map<Integer, StreamState> activeStreams =
new ConcurrentHashMap<Integer, StreamState>(); new ConcurrentHashMap<Integer, StreamState>();
@ -38,17 +47,35 @@ final class SpdySession {
return activeStreams.containsKey(new Integer(streamID)); 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) { if (!remoteSideClosed || !localSideClosed) {
activeStreams.put(new Integer(streamID), activeStreams.put(
new StreamState(remoteSideClosed, localSideClosed)); new Integer(streamID),
new StreamState(priority, remoteSideClosed, localSideClosed, sendWindowSize, receiveWindowSize));
} }
return; return;
} }
public void removeStream(int streamID) { public void removeStream(int streamID) {
activeStreams.remove(new Integer(streamID)); Integer StreamID = new Integer(streamID);
return; 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) { 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) { public boolean hasReceivedReply(int streamID) {
StreamState state = activeStreams.get(new Integer(streamID)); StreamState state = activeStreams.get(new Integer(streamID));
return (state != null) && state.hasReceivedReply(); 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 static final class StreamState {
private boolean remoteSideClosed; private final byte priority;
private boolean localSideClosed; private volatile boolean remoteSideClosed;
private volatile boolean localSideClosed;
private boolean receivedReply; 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.remoteSideClosed = remoteSideClosed;
this.localSideClosed = localSideClosed; this.localSideClosed = localSideClosed;
this.sendWindowSize = new AtomicInteger(sendWindowSize);
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
}
public byte getPriority() {
return priority;
} }
public boolean isRemoteSideClosed() { public boolean isRemoteSideClosed() {
@ -129,5 +223,50 @@ final class SpdySession {
public void receivedReply() { public void receivedReply() {
receivedReply = true; 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();
}
} }
} }

View File

@ -15,6 +15,8 @@
*/ */
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -46,6 +48,12 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
private volatile int localConcurrentStreams; private volatile int localConcurrentStreams;
private volatile int maxConcurrentStreams; 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 final AtomicInteger pings = new AtomicInteger();
private volatile boolean sentGoAwayFrame; private volatile boolean sentGoAwayFrame;
@ -54,18 +62,25 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
private volatile ChannelFuture closeSessionFuture; private volatile ChannelFuture closeSessionFuture;
private final boolean server; private final boolean server;
private final boolean flowControl;
/** /**
* Creates a new session handler. * Creates a new session handler.
* *
* @param server {@code true} if and only if this session handler should * @param version the protocol version
* handle the server endpoint of the connection. * @param server {@code true} if and only if this session handler should
* {@code false} if and only if this session handler should * handle the server endpoint of the connection.
* handle the client 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(); super();
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
this.server = server; this.server = server;
this.flowControl = version >= 3;
} }
@Override @Override
@ -78,41 +93,95 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
/* /*
* SPDY Data frame processing requirements: * SPDY Data frame processing requirements:
* *
* If an endpoint receives a data frame for a Stream-ID which does not exist, * If an endpoint receives a data frame for a Stream-ID which is not open
* it must return a RST_STREAM with error code INVALID_STREAM for the Stream-ID. * 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 * 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 * a SYN_REPLY on that stream, it is a protocol error, and the recipient must
* close the connection immediately. * 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, * 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 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, * If an endpoint receives a data frame after the stream is half-closed from the
* it must ignore the data received after the teardown. * 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; SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
int streamID = spdyDataFrame.getStreamID(); int streamID = spdyDataFrame.getStreamID();
// Check if we received a data frame for a Stream-ID which is not open // Check if we received a data frame for a Stream-ID which is not open
if (spdySession.isRemoteSideClosed(streamID)) { if (!spdySession.isActiveStream(streamID)) {
if (!sentGoAwayFrame) { if (streamID <= lastGoodStreamID) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
} else if (!sentGoAwayFrame) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
} }
return; 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 // Check if we received a data frame before receiving a SYN_REPLY
if (!isRemoteInitiatedID(streamID) && !spdySession.hasReceivedReply(streamID)) { if (!isRemoteInitiatedID(streamID) && !spdySession.hasReceivedReply(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
return; 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()) { if (spdyDataFrame.isLast()) {
// Close remote side of stream
halfCloseStream(streamID, true); halfCloseStream(streamID, true);
} }
@ -121,11 +190,15 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
/* /*
* SPDY SYN_STREAM frame processing requirements: * SPDY SYN_STREAM frame processing requirements:
* *
* If an endpoint receives a SYN_STREAM with a Stream-ID that is not monotonically * If an endpoint receives a SYN_STREAM with a Stream-ID that is less than
* increasing, it must issue a session error with the status PROTOCOL_ERROR. * 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 * 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. * 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; SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
@ -135,21 +208,22 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
if (spdySynStreamFrame.isInvalid() || if (spdySynStreamFrame.isInvalid() ||
!isRemoteInitiatedID(streamID) || !isRemoteInitiatedID(streamID) ||
spdySession.isActiveStream(streamID)) { spdySession.isActiveStream(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
return; return;
} }
// Stream-IDs must be monotonically increassing // Stream-IDs must be monotonically increasing
if (streamID < lastGoodStreamID) { if (streamID <= lastGoodStreamID) {
issueSessionError(ctx, e.getChannel(), e.getRemoteAddress()); issueSessionError(ctx, e.getChannel(), e.getRemoteAddress(), SpdySessionStatus.PROTOCOL_ERROR);
return; return;
} }
// Try to accept the stream // Try to accept the stream
byte priority = spdySynStreamFrame.getPriority();
boolean remoteSideClosed = spdySynStreamFrame.isLast(); boolean remoteSideClosed = spdySynStreamFrame.isLast();
boolean localSideClosed = spdySynStreamFrame.isUnidirectional(); boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
if (!acceptStream(streamID, remoteSideClosed, localSideClosed)) { if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.REFUSED_STREAM); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.REFUSED_STREAM);
return; return;
} }
@ -159,7 +233,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
* SPDY SYN_REPLY frame processing requirements: * SPDY SYN_REPLY frame processing requirements:
* *
* If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID * 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; SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
@ -169,19 +243,20 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
if (spdySynReplyFrame.isInvalid() || if (spdySynReplyFrame.isInvalid() ||
isRemoteInitiatedID(streamID) || isRemoteInitiatedID(streamID) ||
spdySession.isRemoteSideClosed(streamID)) { spdySession.isRemoteSideClosed(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
return; return;
} }
// Check if we have received multiple frames for the same Stream-ID // Check if we have received multiple frames for the same Stream-ID
if (spdySession.hasReceivedReply(streamID)) { if (spdySession.hasReceivedReply(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_IN_USE);
return; return;
} }
spdySession.receivedReply(streamID); spdySession.receivedReply(streamID);
// Close the remote side of the stream if this is the last frame
if (spdySynReplyFrame.isLast()) { if (spdySynReplyFrame.isLast()) {
// Close remote side of stream
halfCloseStream(streamID, true); halfCloseStream(streamID, true);
} }
@ -190,8 +265,10 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
/* /*
* SPDY RST_STREAM frame processing requirements: * SPDY RST_STREAM frame processing requirements:
* *
* After receiving a RST_STREAM on a stream, the receiver must not send additional * After receiving a RST_STREAM on a stream, the receiver must not send
* frames on that stream. * additional frames on that stream.
*
* An endpoint must not send a RST_STREAM in response to a RST_STREAM.
*/ */
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
@ -199,12 +276,29 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdySettingsFrame) { } else if (msg instanceof SpdySettingsFrame) {
/*
* Only concerned with MAX_CONCURRENT_STREAMS
*/
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg; 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) { } else if (msg instanceof SpdyPingFrame) {
@ -224,7 +318,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
return; 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) { if (pings.get() == 0) {
return; return;
} }
@ -241,14 +335,51 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
// Check if we received a valid HEADERS frame // Check if we received a valid HEADERS frame
if (spdyHeadersFrame.isInvalid()) { if (spdyHeadersFrame.isInvalid()) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
return; return;
} }
if (spdySession.isRemoteSideClosed(streamID)) { if (spdySession.isRemoteSideClosed(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM); issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
return; 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); super.messageReceived(ctx, e);
@ -260,7 +391,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause instanceof SpdyProtocolException) { if (cause instanceof SpdyProtocolException) {
issueSessionError(ctx, e.getChannel(), null); issueSessionError(ctx, e.getChannel(), null, SpdySessionStatus.PROTOCOL_ERROR);
} }
super.exceptionCaught(ctx, e); super.exceptionCaught(ctx, e);
@ -274,6 +405,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
case OPEN: case OPEN:
case CONNECTED: case CONNECTED:
case BOUND: 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) { if (Boolean.FALSE.equals(e.getValue()) || e.getValue() == null) {
sendGoAwayFrame(ctx, e); sendGoAwayFrame(ctx, e);
return; return;
@ -291,13 +429,85 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
if (msg instanceof SpdyDataFrame) { if (msg instanceof SpdyDataFrame) {
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; 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)) { if (spdySession.isLocalSideClosed(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION); e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return; 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()) { if (spdyDataFrame.isLast()) {
halfCloseStream(streamID, false); halfCloseStream(streamID, false);
} }
@ -305,9 +515,17 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdySynStreamFrame) { } else if (msg instanceof SpdySynStreamFrame) {
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; 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 remoteSideClosed = spdySynStreamFrame.isUnidirectional();
boolean localSideClosed = spdySynStreamFrame.isLast(); boolean localSideClosed = spdySynStreamFrame.isLast();
if (!acceptStream(spdySynStreamFrame.getStreamID(), remoteSideClosed, localSideClosed)) { if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION); e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return; return;
} }
@ -317,11 +535,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
int streamID = spdySynReplyFrame.getStreamID(); int streamID = spdySynReplyFrame.getStreamID();
// Frames must not be sent on half-closed streams
if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) { if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION); e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return; return;
} }
// Close the local side of the stream if this is the last frame
if (spdySynReplyFrame.isLast()) { if (spdySynReplyFrame.isLast()) {
halfCloseStream(streamID, false); halfCloseStream(streamID, false);
} }
@ -334,7 +554,28 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdySettingsFrame) { } else if (msg instanceof SpdySettingsFrame) {
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg; 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) { } else if (msg instanceof SpdyPingFrame) {
@ -348,7 +589,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdyGoAwayFrame) { } 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); e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return; return;
@ -357,34 +599,65 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
int streamID = spdyHeadersFrame.getStreamID(); int streamID = spdyHeadersFrame.getStreamID();
// Frames must not be sent on half-closed streams
if (spdySession.isLocalSideClosed(streamID)) { if (spdySession.isLocalSideClosed(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION); e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return; 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); 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( 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); 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( 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); removeStream(streamID);
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamID, status); 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; return server && !serverID || !server && serverID;
} }
private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) { private void updateConcurrentStreams(int newConcurrentStreams, boolean remote) {
int newConcurrentStreams = settings.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
if (remote) { if (remote) {
remoteConcurrentStreams = newConcurrentStreams; remoteConcurrentStreams = newConcurrentStreams;
} else { } 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( 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 // Cannot initiate any new streams after receiving or sending GOAWAY
if (receivedGoAwayFrame || sentGoAwayFrame) { if (receivedGoAwayFrame || sentGoAwayFrame) {
return false; return false;
} }
int maxConcurrentStreams = this.maxConcurrentStreams; // read volatile once
if (maxConcurrentStreams != 0 && if (maxConcurrentStreams != 0 &&
spdySession.numActiveStreams() >= maxConcurrentStreams) { spdySession.numActiveStreams() >= maxConcurrentStreams) {
return false; return false;
} }
spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed); spdySession.acceptStream(
streamID, priority, remoteSideClosed, localSideClosed, initialSendWindowSize, initialReceiveWindowSize);
if (isRemoteInitiatedID(streamID)) { if (isRemoteInitiatedID(streamID)) {
lastGoodStreamID = 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) { private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelStateEvent e) {
// Avoid NotYetConnectedException // Avoid NotYetConnectedException
if (!e.getChannel().isConnected()) { if (!e.getChannel().isConnected()) {
@ -465,7 +824,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
return; return;
} }
ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null); ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null, SpdySessionStatus.OK);
if (spdySession.noActiveStreams()) { if (spdySession.noActiveStreams()) {
future.addListener(new ClosingChannelFutureListener(ctx, e)); future.addListener(new ClosingChannelFutureListener(ctx, e));
} else { } else {
@ -475,18 +834,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} }
private synchronized ChannelFuture sendGoAwayFrame( private synchronized ChannelFuture sendGoAwayFrame(
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) { ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) {
if (!sentGoAwayFrame) { if (!sentGoAwayFrame) {
sentGoAwayFrame = true; sentGoAwayFrame = true;
SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamID, status);
ChannelFuture future = Channels.future(channel); ChannelFuture future = Channels.future(channel);
Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID), remoteAddress); Channels.write(ctx, future, spdyGoAwayFrame, remoteAddress);
return future; return future;
} }
return Channels.succeededFuture(channel); return Channels.succeededFuture(channel);
} }
private static final class ClosingChannelFutureListener implements ChannelFutureListener { private static final class ClosingChannelFutureListener implements ChannelFutureListener {
private final ChannelHandlerContext ctx; private final ChannelHandlerContext ctx;
private final ChannelStateEvent e; private final ChannelStateEvent e;

View File

@ -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();
}
}

View File

@ -22,13 +22,14 @@ import java.util.Set;
*/ */
public interface SpdySettingsFrame { public interface SpdySettingsFrame {
int SETTINGS_UPLOAD_BANDWIDTH = 1; int SETTINGS_UPLOAD_BANDWIDTH = 1;
int SETTINGS_DOWNLOAD_BANDWIDTH = 2; int SETTINGS_DOWNLOAD_BANDWIDTH = 2;
int SETTINGS_ROUND_TRIP_TIME = 3; int SETTINGS_ROUND_TRIP_TIME = 3;
int SETTINGS_MAX_CONCURRENT_STREAMS = 4; int SETTINGS_MAX_CONCURRENT_STREAMS = 4;
int SETTINGS_CURRENT_CWND = 5; int SETTINGS_CURRENT_CWND = 5;
int SETTINGS_DOWNLOAD_RETRANS_RATE = 6; int SETTINGS_DOWNLOAD_RETRANS_RATE = 6;
int SETTINGS_INITIAL_WINDOW_SIZE = 7; int SETTINGS_INITIAL_WINDOW_SIZE = 7;
int SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
/** /**
* Returns a {@code Set} of the setting IDs. * Returns a {@code Set} of the setting IDs.
@ -49,7 +50,7 @@ public interface SpdySettingsFrame {
/** /**
* Sets the value of the setting ID. * 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); void setValue(int ID, int value);

View File

@ -63,6 +63,30 @@ public class SpdyStreamStatus implements Comparable<SpdyStreamStatus> {
public static final SpdyStreamStatus FLOW_CONTROL_ERROR = public static final SpdyStreamStatus FLOW_CONTROL_ERROR =
new SpdyStreamStatus(7, "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. * Returns the {@link SpdyStreamStatus} represented by the specified code.
* If the specified code is a defined SPDY status code, a cached instance * 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; return INTERNAL_ERROR;
case 7: case 7:
return FLOW_CONTROL_ERROR; 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 + ')'); return new SpdyStreamStatus(code, "UNKNOWN (" + code + ')');

View File

@ -48,7 +48,7 @@ public interface SpdySynStreamFrame extends SpdyHeaderBlock {
/** /**
* Sets the priority of the stream. * 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); void setPriority(byte priority);

View File

@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package io.handler.codec.spdy; package io.netty.handler.codec.spdy;
/** /**
* A SPDY Protocol WINDOW_UPDATE Control Frame * A SPDY Protocol WINDOW_UPDATE Control Frame

View File

@ -17,6 +17,8 @@ package io.netty.handler.codec.spdy;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -47,15 +49,17 @@ import org.junit.Test;
public abstract class AbstractSocketSpdyEchoTest { public abstract class AbstractSocketSpdyEchoTest {
private static final Random random = new Random(); private static final Random random = new Random();
static final ChannelBuffer frames = ChannelBuffers.buffer(1176);
static final int ignoredBytes = 20; static final int ignoredBytes = 20;
private static ExecutorService executor; 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 // SPDY UNKNOWN Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(0xFFFF); frames.writeShort(0xFFFF);
frames.writeByte(0xFF); frames.writeByte(0xFF);
frames.writeMedium(4); frames.writeMedium(4);
@ -63,7 +67,7 @@ public abstract class AbstractSocketSpdyEchoTest {
// SPDY NOOP Frame // SPDY NOOP Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(5); frames.writeShort(5);
frames.writeInt(0); frames.writeInt(0);
@ -77,27 +81,39 @@ public abstract class AbstractSocketSpdyEchoTest {
// SPDY SYN_STREAM Frame // SPDY SYN_STREAM Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(1); frames.writeShort(1);
frames.writeByte(0x03); frames.writeByte(0x03);
frames.writeMedium(12); if (version < 3) {
frames.writeMedium(12);
} else {
frames.writeMedium(10);
}
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
frames.writeInt(random.nextInt() & 0x7FFFFFFF); frames.writeInt(random.nextInt() & 0x7FFFFFFF);
frames.writeShort(0x8000); frames.writeShort(0x8000);
frames.writeShort(0); if (version < 3) {
frames.writeShort(0);
}
// SPDY SYN_REPLY Frame // SPDY SYN_REPLY Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(2); frames.writeShort(2);
frames.writeByte(0x01); frames.writeByte(0x01);
frames.writeMedium(8); if (version < 3) {
frames.writeMedium(8);
} else {
frames.writeMedium(4);
}
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
frames.writeInt(0); if (version < 3) {
frames.writeInt(0);
}
// SPDY RST_STREAM Frame // SPDY RST_STREAM Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(3); frames.writeShort(3);
frames.writeInt(8); frames.writeInt(8);
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
@ -105,43 +121,58 @@ public abstract class AbstractSocketSpdyEchoTest {
// SPDY SETTINGS Frame // SPDY SETTINGS Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(4); frames.writeShort(4);
frames.writeByte(0x01); frames.writeByte(0x01);
frames.writeMedium(12); frames.writeMedium(12);
frames.writeInt(1); frames.writeInt(1);
frames.writeMedium(random.nextInt()); if (version < 3) {
frames.writeByte(0x03); frames.writeMedium(random.nextInt());
frames.writeByte(0x03);
} else {
frames.writeByte(0x03);
frames.writeMedium(random.nextInt());
}
frames.writeInt(random.nextInt()); frames.writeInt(random.nextInt());
// SPDY PING Frame // SPDY PING Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(6); frames.writeShort(6);
frames.writeInt(4); frames.writeInt(4);
frames.writeInt(random.nextInt()); frames.writeInt(random.nextInt());
// SPDY GOAWAY Frame // SPDY GOAWAY Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(7); frames.writeShort(7);
frames.writeInt(4); if (version < 3) {
frames.writeInt(4);
} else {
frames.writeInt(8);
}
frames.writeInt(random.nextInt() & 0x7FFFFFFF); frames.writeInt(random.nextInt() & 0x7FFFFFFF);
if (version >= 3) {
frames.writeInt(random.nextInt() | 0x01);
}
// SPDY HEADERS Frame // SPDY HEADERS Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(8); frames.writeShort(8);
frames.writeInt(4); frames.writeByte(0x01);
frames.writeMedium(4);
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
// SPDY WINDOW_UPDATE Frame // SPDY WINDOW_UPDATE Frame
frames.writeByte(0x80); frames.writeByte(0x80);
frames.writeByte(2); frames.writeByte(version);
frames.writeShort(9); frames.writeShort(9);
frames.writeInt(8); frames.writeInt(8);
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
return frames;
} }
@BeforeClass @BeforeClass
@ -159,14 +190,22 @@ public abstract class AbstractSocketSpdyEchoTest {
@Test(timeout = 10000) @Test(timeout = 10000)
public void testSpdyEcho() throws Throwable { 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)); ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor));
ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor));
EchoHandler sh = new EchoHandler(true); ChannelBuffer frames = createFrames(version);
EchoHandler ch = new EchoHandler(false);
sb.getPipeline().addLast("decoder", new SpdyFrameDecoder()); EchoHandler sh = new EchoHandler(frames, true);
sb.getPipeline().addLast("encoder", new SpdyFrameEncoder()); 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); sb.getPipeline().addLast("handler", sh);
cb.getPipeline().addLast("handler", ch); cb.getPipeline().addLast("handler", ch);
@ -216,11 +255,13 @@ public abstract class AbstractSocketSpdyEchoTest {
private class EchoHandler extends SimpleChannelUpstreamHandler { private class EchoHandler extends SimpleChannelUpstreamHandler {
volatile Channel channel; volatile Channel channel;
final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
final ChannelBuffer frames;
volatile int counter; volatile int counter;
final boolean server; final boolean server;
EchoHandler(boolean server) { EchoHandler(ChannelBuffer frames, boolean server) {
super(); super();
this.frames = frames;
this.server = server; this.server = server;
} }

View File

@ -95,10 +95,10 @@ public class SpdySessionHandlerTest {
assertHeaderBlock(spdyHeadersFrame, headers); assertHeaderBlock(spdyHeadersFrame, headers);
} }
private void testSpdySessionHandler(boolean server) { private void testSpdySessionHandler(int version, boolean server) {
DecoderEmbedder<Object> sessionHandler = DecoderEmbedder<Object> sessionHandler =
new DecoderEmbedder<Object>( new DecoderEmbedder<Object>(
new SpdySessionHandler(server), new EchoHandler(closeSignal, server)); new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server));
sessionHandler.pollAll(); sessionHandler.pollAll();
int localStreamID = server ? 1 : 2; int localStreamID = server ? 1 : 2;
@ -132,7 +132,7 @@ public class SpdySessionHandlerTest {
sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID)); sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID));
Assert.assertNull(sessionHandler.peek()); Assert.assertNull(sessionHandler.peek());
sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID)); sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID));
assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.PROTOCOL_ERROR); assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.STREAM_IN_USE);
Assert.assertNull(sessionHandler.peek()); Assert.assertNull(sessionHandler.peek());
remoteStreamID += 2; remoteStreamID += 2;
@ -252,12 +252,12 @@ public class SpdySessionHandlerTest {
@Test @Test
public void testSpdyClientSessionHandler() { public void testSpdyClientSessionHandler() {
testSpdySessionHandler(false); testSpdySessionHandler(2, false);
} }
@Test @Test
public void testSpdyServerSessionHandler() { public void testSpdyServerSessionHandler() {
testSpdySessionHandler(true); testSpdySessionHandler(2, true);
} }
// Echo Handler opens 4 half-closed streams on session connection // Echo Handler opens 4 half-closed streams on session connection