Merge pull request #353 from jpinner/spdy_3

SPDY/3 Support
This commit is contained in:
Jeff Pinner 2012-05-21 23:18:38 -07:00
commit 21eada0a6a
27 changed files with 1801 additions and 330 deletions

View File

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

View File

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

View File

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

View File

@ -19,7 +19,8 @@ import org.jboss.netty.buffer.ChannelBuffer;
final class SpdyCodecUtil {
static final int SPDY_VERSION = 2;
static final int SPDY_MIN_VERSION = 2;
static final int SPDY_MAX_VERSION = 3;
static final int SPDY_HEADER_TYPE_OFFSET = 2;
static final int SPDY_HEADER_FLAGS_OFFSET = 4;
@ -39,6 +40,7 @@ final class SpdyCodecUtil {
static final int SPDY_GOAWAY_FRAME = 7;
static final int SPDY_HEADERS_FRAME = 8;
static final int SPDY_WINDOW_UPDATE_FRAME = 9;
static final int SPDY_CREDENTIAL_FRAME = 10;
static final byte SPDY_FLAG_FIN = 0x01;
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;
@ -52,7 +54,188 @@ final class SpdyCodecUtil {
static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field
// Zlib Dictionary
private static final String SPDY_DICT_S =
static final byte[] SPDY_DICT = {
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - -
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - -
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t -
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - -
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e -
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e -
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - -
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t -
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - -
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - -
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - -
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - -
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - -
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - -
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h -
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - -
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e -
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - -
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f -
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - -
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - -
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a -
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - -
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - -
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - -
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r -
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e -
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g -
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a -
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - -
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t -
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - -
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e -
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 -
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 -
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e -
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t -
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v -
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u -
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - -
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t -
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 -
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - -
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 -
};
private static final String SPDY2_DICT_S =
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
@ -66,19 +249,19 @@ final class SpdyCodecUtil {
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
".1statusversionurl ";
static final byte[] SPDY_DICT;
static final byte[] SPDY2_DICT;
static {
byte[] SPDY_DICT_ = null;
byte[] SPDY2_DICT_ = null;
try {
SPDY_DICT_ = SPDY_DICT_S.getBytes("US-ASCII");
SPDY2_DICT_ = SPDY2_DICT_S.getBytes("US-ASCII");
// dictionary is null terminated
SPDY_DICT_[SPDY_DICT_.length - 1] = (byte) 0;
SPDY2_DICT_[SPDY2_DICT_.length - 1] = (byte) 0;
} catch (Exception e) {
SPDY_DICT_ = new byte[1];
SPDY2_DICT_ = new byte[1];
}
SPDY_DICT = SPDY_DICT_;
SPDY2_DICT = SPDY2_DICT_;
}

View File

@ -33,33 +33,34 @@ public class SpdyFrameCodec implements ChannelUpstreamHandler,
/**
* Creates a new instance with the default decoder and encoder options
* ({@code maxChunkSize (8192)}, {@code maxFrameSize (65536)},
* ({@code version (2)}, {@code maxChunkSize (8192)},
* {@code maxHeaderSize (16384)}, {@code compressionLevel (6)},
* {@code windowBits (15)}, and {@code memLevel (8)}).
*/
public SpdyFrameCodec() {
this(8192, 65536, 16384, 6, 15, 8);
}
/**
* Creates a new instance with the specified decoder and encoder options.
*/
public SpdyFrameCodec(
int maxChunkSize, int maxHeaderSize,
int compressionLevel, int windowBits, int memLevel) {
decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize);
encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel);
}
/**
* Creates a new instance with the specified decoder and encoder options.
*/
@Deprecated
public SpdyFrameCodec() {
this(2);
}
/**
* Creates a new instance with the specified {@code version} and
* the default decoder and encoder options
* ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
* {@code compressionLevel (6)}, {@code windowBits (15)},
* and {@code memLevel (8)}).
*/
public SpdyFrameCodec(int version) {
this(version, 8192, 16384, 6, 15, 8);
}
/**
* Creates a new instance with the specified decoder and encoder options.
*/
public SpdyFrameCodec(
int maxChunkSize, @SuppressWarnings("unused") int maxFrameSize, int maxHeaderSize,
int version, int maxChunkSize, int maxHeaderSize,
int compressionLevel, int windowBits, int memLevel) {
decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize);
encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel);
decoder = new SpdyFrameDecoder(version, maxChunkSize, maxHeaderSize);
encoder = new SpdyFrameEncoder(version, compressionLevel, windowBits, memLevel);
}
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)

View File

@ -30,11 +30,11 @@ import org.jboss.netty.handler.codec.frame.TooLongFrameException;
*/
public class SpdyFrameDecoder extends FrameDecoder {
private final int spdyVersion;
private final int maxChunkSize;
private final int maxHeaderSize;
private final SpdyHeaderBlockDecompressor headerBlockDecompressor =
SpdyHeaderBlockDecompressor.newInstance();
private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
private State state;
private SpdySettingsFrame spdySettingsFrame;
@ -64,27 +64,31 @@ public class SpdyFrameDecoder extends FrameDecoder {
}
/**
* Creates a new instance with the default {@code maxChunkSize (8192)}
* and {@code maxHeaderSize (16384)}.
*/
public SpdyFrameDecoder() {
this(8192, 16384);
}
/**
* Creates a new instance with the specified parameters.
* Creates a new instance with the default {@code version (2)},
* {@code maxChunkSize (8192)}, and {@code maxHeaderSize (16384)}.
*/
@Deprecated
public SpdyFrameDecoder(
int maxChunkSize, @SuppressWarnings("unused") int maxFrameSize, int maxHeaderSize) {
this(maxChunkSize, maxHeaderSize);
public SpdyFrameDecoder() {
this(2);
}
/**
* Creates a new instance with the specified {@code version} and the default
* {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}.
*/
public SpdyFrameDecoder(int version) {
this(version, 8192, 16384);
}
/**
* Creates a new instance with the specified parameters.
*/
public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) {
public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
super(false);
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
if (maxChunkSize <= 0) {
throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " + maxChunkSize);
@ -93,8 +97,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
throw new IllegalArgumentException(
"maxHeaderSize must be a positive integer: " + maxHeaderSize);
}
spdyVersion = version;
this.maxChunkSize = maxChunkSize;
this.maxHeaderSize = maxHeaderSize;
headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
state = State.READ_COMMON_HEADER;
}
@ -117,7 +123,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
case READ_COMMON_HEADER:
state = readCommonHeader(buffer);
if (state == State.FRAME_ERROR) {
if (version != SPDY_VERSION) {
if (version != spdyVersion) {
fireProtocolException(ctx, "Unsupported version: " + version);
} else {
fireInvalidControlFrameException(ctx);
@ -183,13 +189,21 @@ public class SpdyFrameDecoder extends FrameDecoder {
int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
for (int i = 0; i < readableEntries; i ++) {
int ID;
byte ID_flags;
if (version < 3) {
// Chromium Issue 79156
// SPDY setting ids are not written in network byte order
// Read id assuming the architecture is little endian
int ID = buffer.readByte() & 0xFF |
ID = buffer.readByte() & 0xFF |
(buffer.readByte() & 0xFF) << 8 |
(buffer.readByte() & 0xFF) << 16;
byte ID_flags = buffer.readByte();
ID_flags = buffer.readByte();
} else {
ID_flags = buffer.readByte();
ID = getUnsignedMedium(buffer, buffer.readerIndex());
buffer.skipBytes(3);
}
int value = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
@ -335,7 +349,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
type = getUnsignedShort(buffer, typeOffset);
// Check version first then validity
if (version != SPDY_VERSION || !isValidControlFrameHeader()) {
if (version != spdyVersion || !isValidControlFrameHeader()) {
return State.FRAME_ERROR;
}
@ -372,6 +386,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
private Object readControlFrame(ChannelBuffer buffer) {
int streamID;
int statusCode;
switch (type) {
case SPDY_RST_STREAM_FRAME:
if (buffer.readableBytes() < 8) {
@ -379,7 +394,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
}
streamID = getUnsignedInt(buffer, buffer.readerIndex());
int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
@ -395,14 +410,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
return new DefaultSpdyPingFrame(ID);
case SPDY_GOAWAY_FRAME:
if (buffer.readableBytes() < 4) {
int minLength = (version < 3) ? 4 : 8;
if (buffer.readableBytes() < minLength) {
return null;
}
int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
if (version < 3) {
return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
}
statusCode = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
return new DefaultSpdyGoAwayFrame(lastGoodStreamID, statusCode);
case SPDY_WINDOW_UPDATE_FRAME:
if (buffer.readableBytes() < 8) {
@ -421,22 +444,27 @@ public class SpdyFrameDecoder extends FrameDecoder {
}
private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) {
int minLength;
int streamID;
switch (type) {
case SPDY_SYN_STREAM_FRAME:
if (buffer.readableBytes() < 12) {
minLength = (version < 3) ? 12 : 10;
if (buffer.readableBytes() < minLength) {
return null;
}
int offset = buffer.readerIndex();
streamID = getUnsignedInt(buffer, offset);
int associatedToStreamID = getUnsignedInt(buffer, offset + 4);
byte priority = (byte) (buffer.getByte(offset + 8) >> 6 & 0x03);
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
if (version < 3) {
priority >>= 1;
}
buffer.skipBytes(10);
length -= 10;
// SPDY/2 requires 16-bits of padding for empty header blocks
if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
buffer.skipBytes(2);
length = 0;
}
@ -449,16 +477,23 @@ public class SpdyFrameDecoder extends FrameDecoder {
return spdySynStreamFrame;
case SPDY_SYN_REPLY_FRAME:
if (buffer.readableBytes() < 8) {
minLength = (version < 3) ? 8 : 4;
if (buffer.readableBytes() < minLength) {
return null;
}
streamID = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(6);
length -= 6;
buffer.skipBytes(4);
length -= 4;
// SPDY/2 has 16-bits of unused space
if (version < 3) {
buffer.skipBytes(2);
length -= 2;
}
// SPDY/2 requires 16-bits of padding for empty header blocks
if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
buffer.skipBytes(2);
length = 0;
}
@ -469,9 +504,12 @@ public class SpdyFrameDecoder extends FrameDecoder {
return spdySynReplyFrame;
case SPDY_HEADERS_FRAME:
// Protocol allows length 4 frame when there are no name/value pairs
int minLength = length == 4 ? 4 : 8;
if (buffer.readableBytes() < minLength) {
if (buffer.readableBytes() < 4) {
return null;
}
// SPDY/2 allows length 4 frame when there are no name/value pairs
if (version < 3 && length > 4 && buffer.readableBytes() < 8) {
return null;
}
@ -479,16 +517,22 @@ public class SpdyFrameDecoder extends FrameDecoder {
buffer.skipBytes(4);
length -= 4;
// SPDY/2 requires 16-bits of padding for empty header blocks
if (length == 4 && buffer.getShort(buffer.readerIndex() + 2) == 0) {
buffer.skipBytes(4);
length = 0;
} else if (length != 0) {
// SPDY/2 has 16-bits of unused space
if (version < 3 && length != 0) {
buffer.skipBytes(2);
length -= 2;
}
return new DefaultSpdyHeadersFrame(streamID);
// SPDY/2 requires 16-bits of padding for empty header blocks
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
buffer.skipBytes(2);
length = 0;
}
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
return spdyHeadersFrame;
default:
throw new Error("Shouldn't reach here.");
@ -505,7 +549,11 @@ public class SpdyFrameDecoder extends FrameDecoder {
}
private int readLengthField() {
if (version < 3) {
return decompressed.readUnsignedShort();
} else {
return decompressed.readInt();
}
}
private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
@ -527,7 +575,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
return;
}
int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields
int lengthFieldSize = (version < 3) ? 2 : 4;
if (numHeaders == -1) {
// Read number of Name/Value pairs
@ -535,6 +583,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
return;
}
numHeaders = readLengthField();
if (numHeaders < 0) {
spdyHeaderBlock.setInvalid();
return;
}
}
while (numHeaders > 0) {
@ -550,7 +602,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
int nameLength = readLengthField();
// Recipients of a zero-length name must issue a stream error
if (nameLength == 0) {
if (nameLength <= 0) {
spdyHeaderBlock.setInvalid();
return;
}
@ -585,7 +637,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
int valueLength = readLengthField();
// Recipients of illegal value fields must issue a stream error
if (valueLength == 0) {
if (valueLength <= 0) {
spdyHeaderBlock.setInvalid();
return;
}
@ -638,10 +690,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
private boolean isValidControlFrameHeader() {
switch (type) {
case SPDY_SYN_STREAM_FRAME:
return length >= 12;
return (version < 3) ? length >= 12 : length >= 10;
case SPDY_SYN_REPLY_FRAME:
return length >= 8;
return (version < 3) ? length >= 8 : length >= 4;
case SPDY_RST_STREAM_FRAME:
return flags == 0 && length == 8;
@ -656,14 +708,19 @@ public class SpdyFrameDecoder extends FrameDecoder {
return length == 4;
case SPDY_GOAWAY_FRAME:
return length == 4;
return (version < 3) ? length == 4 : length == 8;
case SPDY_HEADERS_FRAME:
if (version < 3) {
return length == 4 || length >= 8;
} else {
return length >= 4;
}
case SPDY_WINDOW_UPDATE_FRAME:
return length == 8;
case SPDY_CREDENTIAL_FRAME:
default:
return true;
}
@ -682,6 +739,7 @@ public class SpdyFrameDecoder extends FrameDecoder {
return true;
case SPDY_NOOP_FRAME:
case SPDY_CREDENTIAL_FRAME:
default:
return false;
}
@ -725,6 +783,10 @@ public class SpdyFrameDecoder extends FrameDecoder {
case SPDY_WINDOW_UPDATE_FRAME:
message = "Received invalid WINDOW_UPDATE control frame";
break;
case SPDY_CREDENTIAL_FRAME:
message = "Received invalid CREDENTIAL control frame";
break;
}
fireProtocolException(ctx, message);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,12 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
private final ZStream z = new ZStream();
public SpdyHeaderBlockJZlibCompressor(int compressionLevel, int windowBits, int memLevel) {
public SpdyHeaderBlockJZlibCompressor(
int version, int compressionLevel, int windowBits, int memLevel) {
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
@ -45,8 +50,12 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
if (resultCode != JZlib.Z_OK) {
throw new CompressionException(
"failed to initialize an SPDY header block deflater: " + resultCode);
} else {
if (version < 3) {
resultCode = z.deflateSetDictionary(SPDY2_DICT, SPDY2_DICT.length);
} else {
resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length);
}
if (resultCode != JZlib.Z_OK) {
throw new CompressionException(
"failed to set the SPDY dictionary: " + resultCode);

View File

@ -26,14 +26,22 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
private final byte[] out = new byte[8192];
private final Deflater compressor;
public SpdyHeaderBlockZlibCompressor(int compressionLevel) {
public SpdyHeaderBlockZlibCompressor(int version, int compressionLevel) {
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
}
compressor = new Deflater(compressionLevel);
if (version < 3) {
compressor.setDictionary(SPDY2_DICT);
} else {
compressor.setDictionary(SPDY_DICT);
}
}
@Override
public void setInput(ChannelBuffer decompressed) {

View File

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

View File

@ -27,7 +27,7 @@ import org.jboss.netty.handler.codec.http.HttpVersion;
/**
* Provides the constants for the standard SPDY HTTP header names and commonly
* used utility methods that access an {@link SpdyHeaderBlock}.
* used utility methods that access a {@link SpdyHeaderBlock}.
* @apiviz.stereotype static
*/
public class SpdyHeaders {
@ -37,6 +37,41 @@ public class SpdyHeaders {
* @apiviz.stereotype static
*/
public static final class HttpNames {
/**
* {@code ":host"}
*/
public static final String HOST = ":host";
/**
* {@code ":method"}
*/
public static final String METHOD = ":method";
/**
* {@code ":path"}
*/
public static final String PATH = ":path";
/**
* {@code ":scheme"}
*/
public static final String SCHEME = ":scheme";
/**
* {@code ":status"}
*/
public static final String STATUS = ":status";
/**
* {@code ":version"}
*/
public static final String VERSION = ":version";
private HttpNames() {
super();
}
}
/**
* SPDY/2 HTTP header names
* @apiviz.stereotype static
*/
public static final class Spdy2HttpNames {
/**
* {@code "method"}
*/
@ -58,7 +93,7 @@ public class SpdyHeaders {
*/
public static final String VERSION = "version";
private HttpNames() {
private Spdy2HttpNames() {
super();
}
}
@ -115,64 +150,182 @@ public class SpdyHeaders {
}
/**
* Removes the {@code "method"} header.
* Removes the SPDY host header.
*/
public static void removeMethod(SpdyHeaderBlock block) {
block.removeHeader(HttpNames.METHOD);
public static void removeHost(SpdyHeaderBlock block) {
block.removeHeader(HttpNames.HOST);
}
/**
* Returns the {@link HttpMethod} represented by the {@code "method"} header.
* Returns the SPDY host header.
*/
public static 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.
*/
@Deprecated
public static void removeMethod(SpdyHeaderBlock block) {
removeMethod(2, block);
}
/**
* 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.
*/
@Deprecated
public static HttpMethod getMethod(SpdyHeaderBlock block) {
return getMethod(2, block);
}
/**
* Returns the {@link HttpMethod} represented by the HTTP method header.
*/
public static HttpMethod getMethod(int spdyVersion, SpdyHeaderBlock block) {
try {
if (spdyVersion < 3) {
return HttpMethod.valueOf(block.getHeader(Spdy2HttpNames.METHOD));
} else {
return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
}
} catch (Exception e) {
return null;
}
}
/**
* Sets the {@code "method"} header.
* Sets the HTTP method header.
*/
@Deprecated
public static void setMethod(SpdyHeaderBlock block, HttpMethod method) {
setMethod(2, block, method);
}
/**
* Sets the HTTP method header.
*/
public static void setMethod(int spdyVersion, SpdyHeaderBlock block, HttpMethod method) {
if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.METHOD, method.getName());
} else {
block.setHeader(HttpNames.METHOD, method.getName());
}
}
/**
* Removes the {@code "scheme"} header.
* Removes the URL scheme header.
*/
@Deprecated
public static void removeScheme(SpdyHeaderBlock block) {
removeMethod(2, block);
}
/**
* Removes the URL scheme header.
*/
public static void removeScheme(int spdyVersion, SpdyHeaderBlock block) {
if (spdyVersion < 2) {
block.removeHeader(Spdy2HttpNames.SCHEME);
} else {
block.removeHeader(HttpNames.SCHEME);
}
}
/**
* Returns the value of the {@code "scheme"} header.
* Returns the value of the URL scheme header.
*/
@Deprecated
public static String getScheme(SpdyHeaderBlock block) {
return getScheme(2, block);
}
/**
* Returns the value of the URL scheme header.
*/
public static String getScheme(int spdyVersion, SpdyHeaderBlock block) {
if (spdyVersion < 3) {
return block.getHeader(Spdy2HttpNames.SCHEME);
} else {
return block.getHeader(HttpNames.SCHEME);
}
/**
* Sets the {@code "scheme"} header.
*/
public static void setScheme(SpdyHeaderBlock block, String value) {
block.setHeader(HttpNames.SCHEME, value);
}
/**
* Removes the {@code "status"} header.
* Sets the URL scheme header.
*/
@Deprecated
public static void setScheme(SpdyHeaderBlock block, String scheme) {
setScheme(2, block, scheme);
}
/**
* Sets the URL scheme header.
*/
public static void setScheme(int spdyVersion, SpdyHeaderBlock block, String scheme) {
if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.SCHEME, scheme);
} else {
block.setHeader(HttpNames.SCHEME, scheme);
}
}
/**
* Removes the HTTP response status header.
*/
@Deprecated
public static void removeStatus(SpdyHeaderBlock block) {
removeMethod(2, block);
}
/**
* Removes the HTTP response status header.
*/
public static void removeStatus(int spdyVersion, SpdyHeaderBlock block) {
if (spdyVersion < 3) {
block.removeHeader(Spdy2HttpNames.STATUS);
} else {
block.removeHeader(HttpNames.STATUS);
}
}
/**
* Returns the {@link HttpResponseStatus} represented by the {@code "status"} header.
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
*/
@Deprecated
public static HttpResponseStatus getStatus(SpdyHeaderBlock block) {
return getStatus(2, block);
}
/**
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
*/
public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeaderBlock block) {
try {
String status = block.getHeader(HttpNames.STATUS);
String status;
if (spdyVersion < 3) {
status = block.getHeader(Spdy2HttpNames.STATUS);
} else {
status = block.getHeader(HttpNames.STATUS);
}
int space = status.indexOf(' ');
if (space == -1) {
return HttpResponseStatus.valueOf(Integer.parseInt(status));
@ -180,7 +333,7 @@ public class SpdyHeaders {
int code = Integer.parseInt(status.substring(0, space));
String reasonPhrase = status.substring(space + 1);
HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
if (responseStatus.getReasonPhrase().equals(responseStatus)) {
if (responseStatus.getReasonPhrase().equals(reasonPhrase)) {
return responseStatus;
} else {
return new HttpResponseStatus(code, reasonPhrase);
@ -192,56 +345,140 @@ public class SpdyHeaders {
}
/**
* Sets the {@code "status"} header.
* Sets the HTTP response status header.
*/
@Deprecated
public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) {
setStatus(2, block, status);
}
/**
* Sets the HTTP response status header.
*/
public static void setStatus(int spdyVersion, SpdyHeaderBlock block, HttpResponseStatus status) {
if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.STATUS, status.toString());
} else {
block.setHeader(HttpNames.STATUS, status.toString());
}
}
/**
* Removes the {@code "url"} header.
* Removes the URL path header.
*/
@Deprecated
public static void removeUrl(SpdyHeaderBlock block) {
block.removeHeader(HttpNames.URL);
removeUrl(2, block);
}
/**
* Returns the value of the {@code "url"} header.
* Removes the URL path header.
*/
public static void removeUrl(int spdyVersion, SpdyHeaderBlock block) {
if (spdyVersion < 3) {
block.removeHeader(Spdy2HttpNames.URL);
} else {
block.removeHeader(HttpNames.PATH);
}
}
/**
* Returns the value of the URL path header.
*/
@Deprecated
public static String getUrl(SpdyHeaderBlock block) {
return block.getHeader(HttpNames.URL);
return getUrl(2, block);
}
/**
* Sets the {@code "url"} header.
* Returns the value of the URL path header.
*/
public static void setUrl(SpdyHeaderBlock block, String value) {
block.setHeader(HttpNames.URL, value);
public static String getUrl(int spdyVersion, SpdyHeaderBlock block) {
if (spdyVersion < 3) {
return block.getHeader(Spdy2HttpNames.URL);
} else {
return block.getHeader(HttpNames.PATH);
}
}
/**
* Removes the {@code "version"} header.
* Sets the URL path header.
*/
@Deprecated
public static void setUrl(SpdyHeaderBlock block, String path) {
setUrl(2, block, path);
}
/**
* Sets the URL path header.
*/
public static void setUrl(int spdyVersion, SpdyHeaderBlock block, String path) {
if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.URL, path);
} else {
block.setHeader(HttpNames.PATH, path);
}
}
/**
* Removes the HTTP version header.
*/
@Deprecated
public static void removeVersion(SpdyHeaderBlock block) {
removeVersion(2, block);
}
/**
* Removes the HTTP version header.
*/
public static void removeVersion(int spdyVersion, SpdyHeaderBlock block) {
if (spdyVersion < 3) {
block.removeHeader(Spdy2HttpNames.VERSION);
} else {
block.removeHeader(HttpNames.VERSION);
}
}
/**
* Returns the {@link HttpVersion} represented by the {@code "version"} header.
* Returns the {@link HttpVersion} represented by the HTTP version header.
*/
@Deprecated
public static HttpVersion getVersion(SpdyHeaderBlock block) {
return getVersion(2, block);
}
/**
* Returns the {@link HttpVersion} represented by the HTTP version header.
*/
public static HttpVersion getVersion(int spdyVersion, SpdyHeaderBlock block) {
try {
if (spdyVersion < 3) {
return HttpVersion.valueOf(block.getHeader(Spdy2HttpNames.VERSION));
} else {
return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
}
} catch (Exception e) {
return null;
}
}
/**
* Sets the {@code "version"} header.
* Sets the HTTP version header.
*/
public static void setVersion(SpdyHeaderBlock block, HttpVersion version) {
block.setHeader(HttpNames.VERSION, version.getText());
@Deprecated
public static void setVersion(SpdyHeaderBlock block, HttpVersion httpVersion) {
setVersion(2, block, httpVersion);
}
/**
* Sets the HTTP version header.
*/
public static void setVersion(int spdyVersion, SpdyHeaderBlock block, HttpVersion httpVersion) {
if (spdyVersion < 3) {
block.setHeader(Spdy2HttpNames.VERSION, httpVersion.getText());
} else {
block.setHeader(HttpNames.VERSION, httpVersion.getText());
}
}

View File

@ -29,4 +29,15 @@ public interface SpdyHeadersFrame extends SpdyHeaderBlock {
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
*/
void setStreamID(int streamID);
/**
* Returns {@code true} if this frame is the last frame to be transmitted
* on the stream.
*/
boolean isLast();
/**
* Sets if this frame is the last frame to be transmitted on the stream.
*/
void setLast(boolean last);
}

View File

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

View File

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

View File

@ -15,6 +15,8 @@
*/
package org.jboss.netty.handler.codec.spdy;
import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
import java.util.List;
import java.util.Map;
@ -51,8 +53,8 @@ import org.jboss.netty.handler.codec.http.HttpResponse;
* <tr>
* <td>{@code "X-SPDY-Priority"}</td>
* <td>The priority value for this request.
* The priority should be between 0 and 3 inclusive.
* 0 represents the highest priority and 3 represents the lowest.
* The priority should be between 0 and 7 inclusive.
* 0 represents the highest priority and 7 represents the lowest.
* This header is optional and defaults to 0.</td>
* </tr>
* </table>
@ -84,21 +86,32 @@ import org.jboss.netty.handler.codec.http.HttpResponse;
* </tr>
* <tr>
* <td>{@code "X-SPDY-Associated-To-Stream-ID"}</td>
* <td>The Stream-ID of the request that inititated this pushed resource.</td>
* <td>The Stream-ID of the request that initiated this pushed resource.</td>
* </tr>
* <tr>
* <td>{@code "X-SPDY-Priority"}</td>
* <td>The priority value for this resource.
* The priority should be between 0 and 3 inclusive.
* 0 represents the highest priority and 3 represents the lowest.
* The priority should be between 0 and 7 inclusive.
* 0 represents the highest priority and 7 represents the lowest.
* This header is optional and defaults to 0.</td>
* </tr>
* <tr>
* <td>{@code "X-SPDY-URL"}</td>
* <td>The full URL for the resource being pushed.</td>
* <td>The absolute path for the resource being pushed.</td>
* </tr>
* </table>
*
* <h3>Required Annotations</h3>
*
* SPDY requires that all Requests and Pushed Resources contain
* an HTTP "Host" header.
*
* <h3>Optional Annotations</h3>
*
* Requests and Pushed Resources must contain a SPDY scheme header.
* This can be set via the {@code "X-SPDY-Scheme"} header but otherwise
* defaults to "https" as that is the most common SPDY deployment.
*
* <h3>Chunked Content</h3>
*
* This encoder associates all {@link HttpChunk}s that it receives
@ -112,11 +125,31 @@ import org.jboss.netty.handler.codec.http.HttpResponse;
*/
public class SpdyHttpEncoder implements ChannelDownstreamHandler {
private final int spdyVersion;
private volatile int currentStreamID;
/**
* Creates a new instance for the SPDY/2 protocol
*/
@Deprecated
public SpdyHttpEncoder() {
this(2);
}
/**
* 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)
throws Exception {
if (!(evt instanceof MessageEvent)) {
@ -228,15 +261,17 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
throws Exception {
boolean chunked = httpMessage.isChunked();
// Get the Stream-ID, Associated-To-Stream-ID, Priority, and URL from the headers
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
int streamID = SpdyHttpHeaders.getStreamID(httpMessage);
int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage);
byte priority = SpdyHttpHeaders.getPriority(httpMessage);
String URL = SpdyHttpHeaders.getUrl(httpMessage);
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
SpdyHttpHeaders.removeStreamID(httpMessage);
SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage);
SpdyHttpHeaders.removePriority(httpMessage);
SpdyHttpHeaders.removeUrl(httpMessage);
SpdyHttpHeaders.removeScheme(httpMessage);
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
// headers are not valid and MUST not be sent.
@ -246,24 +281,39 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
}
// Unfold the first line of the message into name/value pairs
SpdyHeaders.setVersion(spdySynStreamFrame, httpMessage.getProtocolVersion());
if (httpMessage instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) httpMessage;
SpdyHeaders.setMethod(spdySynStreamFrame, httpRequest.getMethod());
SpdyHeaders.setUrl(spdySynStreamFrame, httpRequest.getUri());
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
}
if (httpMessage instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) httpMessage;
SpdyHeaders.setStatus(spdySynStreamFrame, httpResponse.getStatus());
SpdyHeaders.setUrl(spdySynStreamFrame, URL);
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
spdySynStreamFrame.setUnidirectional(true);
}
// Replace the HTTP host header with the SPDY host header
if (spdyVersion >= 3) {
String host = HttpHeaders.getHost(httpMessage);
httpMessage.removeHeader(HttpHeaders.Names.HOST);
SpdyHeaders.setHost(spdySynStreamFrame, host);
}
// Set the SPDY scheme header
if (scheme == null) {
scheme = "https";
}
SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
// Transfer the remaining HTTP headers
for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
}
if (chunked) {
currentStreamID = streamID;
spdySynStreamFrame.setLast(false);
@ -290,14 +340,16 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler {
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
// Unfold the first line of the response into name/value pairs
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
// Transfer the remaining HTTP headers
for (Map.Entry<String, String> entry: httpResponse.getHeaders()) {
spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue());
}
// Unfold the first line of the repsonse into name/value pairs
SpdyHeaders.setStatus(spdySynReplyFrame, httpResponse.getStatus());
SpdyHeaders.setVersion(spdySynReplyFrame, httpResponse.getProtocolVersion());
if (chunked) {
currentStreamID = streamID;
spdySynReplyFrame.setLast(false);

View File

@ -46,6 +46,10 @@ public final class SpdyHttpHeaders {
* {@code "X-SPDY-URL"}
*/
public static final String URL = "X-SPDY-URL";
/**
* {@code "X-SPDY-Scheme"}
*/
public static final String SCHEME = "X-SPDY-Scheme";
private Names() {
super();
@ -144,4 +148,25 @@ public final class SpdyHttpHeaders {
public static void setUrl(HttpMessage message, String url) {
message.setHeader(Names.URL, url);
}
/**
* Removes the {@code "X-SPDY-Scheme"} header.
*/
public static void removeScheme(HttpMessage message) {
message.removeHeader(Names.SCHEME);
}
/**
* Returns the value of the {@code "X-SPDY-Scheme"} header.
*/
public static String getScheme(HttpMessage message) {
return message.getHeader(Names.SCHEME);
}
/**
* Sets the {@code "X-SPDY-Scheme"} header.
*/
public static void setScheme(HttpMessage message, String scheme) {
message.setHeader(Names.URL, scheme);
}
}

View File

@ -15,11 +15,20 @@
*/
package org.jboss.netty.handler.codec.spdy;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.netty.channel.MessageEvent;
final class SpdySession {
private static final SpdyProtocolException STREAM_CLOSED = new SpdyProtocolException("Stream closed");
private final Map<Integer, StreamState> activeStreams =
new ConcurrentHashMap<Integer, StreamState>();
@ -38,15 +47,34 @@ final class SpdySession {
return activeStreams.containsKey(new Integer(streamID));
}
public void acceptStream(int streamID, boolean remoteSideClosed, boolean localSideClosed) {
// Stream-IDs should be iterated in priority order
public Set<Integer> getActiveStreams() {
TreeSet<Integer> StreamIDs = new TreeSet<Integer>(new PriorityComparator());
StreamIDs.addAll(activeStreams.keySet());
return StreamIDs;
}
public void acceptStream(
int streamID, byte priority, boolean remoteSideClosed, boolean localSideClosed,
int sendWindowSize, int receiveWindowSize) {
if (!remoteSideClosed || !localSideClosed) {
activeStreams.put(new Integer(streamID),
new StreamState(remoteSideClosed, localSideClosed));
activeStreams.put(
new Integer(streamID),
new StreamState(priority, remoteSideClosed, localSideClosed, sendWindowSize, receiveWindowSize));
}
}
public void removeStream(int streamID) {
activeStreams.remove(new Integer(streamID));
Integer StreamID = new Integer(streamID);
StreamState state = activeStreams.get(StreamID);
activeStreams.remove(StreamID);
if (state != null) {
MessageEvent e = state.removePendingWrite();
while (e != null) {
e.getFuture().setFailure(STREAM_CLOSED);
e = state.removePendingWrite();
}
}
}
public boolean isRemoteSideClosed(int streamID) {
@ -81,6 +109,11 @@ final class SpdySession {
}
}
/*
* hasReceivedReply and receivedReply are only called from messageReceived
* no need to synchronize access to the StreamState
*/
public boolean hasReceivedReply(int streamID) {
StreamState state = activeStreams.get(new Integer(streamID));
return state != null && state.hasReceivedReply();
@ -93,15 +126,77 @@ final class SpdySession {
}
}
public int getSendWindowSize(int streamID) {
StreamState state = activeStreams.get(new Integer(streamID));
return (state != null) ? state.getSendWindowSize() : -1;
}
public int updateSendWindowSize(int streamID, int deltaWindowSize) {
StreamState state = activeStreams.get(new Integer(streamID));
return (state != null) ? state.updateSendWindowSize(deltaWindowSize) : -1;
}
public int updateReceiveWindowSize(int streamID, int deltaWindowSize) {
StreamState state = activeStreams.get(new Integer(streamID));
if (deltaWindowSize > 0) {
state.setReceiveWindowSizeLowerBound(0);
}
return (state != null) ? state.updateReceiveWindowSize(deltaWindowSize) : -1;
}
public int getReceiveWindowSizeLowerBound(int streamID) {
StreamState state = activeStreams.get(new Integer(streamID));
return (state != null) ? state.getReceiveWindowSizeLowerBound() : 0;
}
public void updateAllReceiveWindowSizes(int deltaWindowSize) {
for (StreamState state: activeStreams.values()) {
state.updateReceiveWindowSize(deltaWindowSize);
if (deltaWindowSize < 0) {
state.setReceiveWindowSizeLowerBound(deltaWindowSize);
}
}
}
public boolean putPendingWrite(int streamID, MessageEvent evt) {
StreamState state = activeStreams.get(new Integer(streamID));
return state != null && state.putPendingWrite(evt);
}
public MessageEvent getPendingWrite(int streamID) {
StreamState state = activeStreams.get(new Integer(streamID));
return (state != null) ? state.getPendingWrite() : null;
}
public MessageEvent removePendingWrite(int streamID) {
StreamState state = activeStreams.get(new Integer(streamID));
return (state != null) ? state.removePendingWrite() : null;
}
private static final class StreamState {
private boolean remoteSideClosed;
private boolean localSideClosed;
private final byte priority;
private volatile boolean remoteSideClosed;
private volatile boolean localSideClosed;
private boolean receivedReply;
private AtomicInteger sendWindowSize;
private AtomicInteger receiveWindowSize;
private volatile int receiveWindowSizeLowerBound;
private ConcurrentLinkedQueue<MessageEvent> pendingWriteQueue =
new ConcurrentLinkedQueue<MessageEvent>();
public StreamState(boolean remoteSideClosed, boolean localSideClosed) {
public StreamState(
byte priority, boolean remoteSideClosed, boolean localSideClosed,
int sendWindowSize, int receiveWindowSize) {
this.priority = priority;
this.remoteSideClosed = remoteSideClosed;
this.localSideClosed = localSideClosed;
this.sendWindowSize = new AtomicInteger(sendWindowSize);
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
}
public byte getPriority() {
return priority;
}
public boolean isRemoteSideClosed() {
@ -127,5 +222,50 @@ final class SpdySession {
public void receivedReply() {
receivedReply = true;
}
public int getSendWindowSize() {
return sendWindowSize.get();
}
public int updateSendWindowSize(int deltaWindowSize) {
return sendWindowSize.addAndGet(deltaWindowSize);
}
public int updateReceiveWindowSize(int deltaWindowSize) {
return receiveWindowSize.addAndGet(deltaWindowSize);
}
public int getReceiveWindowSizeLowerBound() {
return receiveWindowSizeLowerBound;
}
public void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {
this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound;
}
public boolean putPendingWrite(MessageEvent evt) {
return pendingWriteQueue.offer(evt);
}
public MessageEvent getPendingWrite() {
return pendingWriteQueue.peek();
}
public MessageEvent removePendingWrite() {
return pendingWriteQueue.poll();
}
}
private final class PriorityComparator implements Comparator<Integer> {
public PriorityComparator() {
super();
}
public int compare(Integer id1, Integer id2) {
StreamState state1 = activeStreams.get(id1);
StreamState state2 = activeStreams.get(id2);
return state1.getPriority() - state2.getPriority();
}
}
}

View File

@ -15,6 +15,8 @@
*/
package org.jboss.netty.handler.codec.spdy;
import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicInteger;
@ -46,6 +48,12 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
private volatile int localConcurrentStreams;
private volatile int maxConcurrentStreams;
private static final int DEFAULT_WINDOW_SIZE = 64 * 1024; // 64 KB default initial window size
private volatile int initialSendWindowSize = DEFAULT_WINDOW_SIZE;
private volatile int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;
private final Object flowControlLock = new Object();
private final AtomicInteger pings = new AtomicInteger();
private volatile boolean sentGoAwayFrame;
@ -54,18 +62,38 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
private volatile ChannelFuture closeSessionFuture;
private final boolean server;
private final boolean flowControl;
/**
* Creates a new session handler.
* Creates a new SPDY/2 session handler.
*
* @param server {@code true} if and only if this session handler should
* handle the server endpoint of the connection.
* {@code false} if and only if this session handler should
* handle the client endpoint of the connection.
*/
@Deprecated
public SpdySessionHandler(boolean server) {
this(2, server);
}
/**
* Creates a new session handler.
*
* @param version the protocol version
* @param server {@code true} if and only if this session handler should
* handle the server endpoint of the connection.
* {@code false} if and only if this session handler should
* handle the client endpoint of the connection.
*/
public SpdySessionHandler(int version, boolean server) {
super();
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
this.server = server;
this.flowControl = version >= 3;
}
@Override
@ -78,41 +106,95 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
/*
* SPDY Data frame processing requirements:
*
* If an endpoint receives a data frame for a Stream-ID which does not exist,
* it must return a RST_STREAM with error code INVALID_STREAM for the Stream-ID.
* If an endpoint receives a data frame for a Stream-ID which is not open
* and the endpoint has not sent a GOAWAY frame, it must issue a stream error
* with the error code INVALID_STREAM for the Stream-ID.
*
* If an endpoint which created the stream receives a data frame before receiving
* a SYN_REPLY on that stream, it is a protocol error, and the receiver should
* close the connection immediately.
* a SYN_REPLY on that stream, it is a protocol error, and the recipient must
* issue a stream error with the status code PROTOCOL_ERROR for the Stream-ID.
*
* If an endpoint receives multiple data frames for invalid Stream-IDs,
* it may terminate the session.
* it may close the session.
*
* If an endpoint refuses a stream it must ignore any data frames for that stream.
*
* If an endpoint receives data on a stream which has already been torn down,
* it must ignore the data received after the teardown.
* If an endpoint receives a data frame after the stream is half-closed from the
* sender, it must send a RST_STREAM frame with the status STREAM_ALREADY_CLOSED.
*
* If an endpoint receives a data frame after the stream is closed, it must send
* a RST_STREAM frame with the status PROTOCOL_ERROR.
*/
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
int streamID = spdyDataFrame.getStreamID();
// Check if we received a data frame for a Stream-ID which is not open
if (spdySession.isRemoteSideClosed(streamID)) {
if (!sentGoAwayFrame) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
if (!spdySession.isActiveStream(streamID)) {
if (streamID <= lastGoodStreamID) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
} else if (!sentGoAwayFrame) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
}
return;
}
// Check if we received a data frame for a stream which is half-closed
if (spdySession.isRemoteSideClosed(streamID)) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_ALREADY_CLOSED);
return;
}
// Check if we received a data frame before receiving a SYN_REPLY
if (!isRemoteInitiatedID(streamID) && !spdySession.hasReceivedReply(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
return;
}
/*
* SPDY Data frame flow control processing requirements:
*
* Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
*/
if (flowControl) {
// Update receive window size
int deltaWindowSize = -1 * spdyDataFrame.getData().readableBytes();
int newWindowSize = spdySession.updateReceiveWindowSize(streamID, deltaWindowSize);
// Window size can become negative if we sent a SETTINGS frame that reduces the
// size of the transfer window after the peer has written data frames.
// The value is bounded by the length that SETTINGS frame decrease the window.
// This difference is stored for the session when writing the SETTINGS frame
// and is cleared once we send a WINDOW_UPDATE frame.
if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamID)) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.FLOW_CONTROL_ERROR);
return;
}
// Window size became negative due to sender writing frame before receiving SETTINGS
// Send data frames upstream in initialReceiveWindowSize chunks
if (newWindowSize < 0) {
while (spdyDataFrame.getData().readableBytes() > initialReceiveWindowSize) {
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID);
partialDataFrame.setData(spdyDataFrame.getData().readSlice(initialReceiveWindowSize));
Channels.fireMessageReceived(ctx, partialDataFrame, e.getRemoteAddress());
}
}
// Send a WINDOW_UPDATE frame if less than half the window size remains
if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) {
deltaWindowSize = initialReceiveWindowSize - newWindowSize;
spdySession.updateReceiveWindowSize(streamID, deltaWindowSize);
SpdyWindowUpdateFrame spdyWindowUpdateFrame =
new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize);
Channels.write(
ctx, Channels.future(e.getChannel()), spdyWindowUpdateFrame, e.getRemoteAddress());
}
}
// Close the remote side of the stream if this is the last frame
if (spdyDataFrame.isLast()) {
// Close remote side of stream
halfCloseStream(streamID, true);
}
@ -121,11 +203,15 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
/*
* SPDY SYN_STREAM frame processing requirements:
*
* If an endpoint receives a SYN_STREAM with a Stream-ID that is not monotonically
* increasing, it must issue a session error with the status PROTOCOL_ERROR.
* If an endpoint receives a SYN_STREAM with a Stream-ID that is less than
* any previously received SYN_STREAM, it must issue a session error with
* the status PROTOCOL_ERROR.
*
* If an endpoint receives multiple SYN_STREAM frames with the same active
* Stream-ID, it must issue a stream error with the status code PROTOCOL_ERROR.
*
* The recipient can reject a stream by sending a stream error with the
* status code REFUSED_STREAM.
*/
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
@ -135,21 +221,22 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
if (spdySynStreamFrame.isInvalid() ||
!isRemoteInitiatedID(streamID) ||
spdySession.isActiveStream(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
return;
}
// Stream-IDs must be monotonically increassing
if (streamID < lastGoodStreamID) {
issueSessionError(ctx, e.getChannel(), e.getRemoteAddress());
// Stream-IDs must be monotonically increasing
if (streamID <= lastGoodStreamID) {
issueSessionError(ctx, e.getChannel(), e.getRemoteAddress(), SpdySessionStatus.PROTOCOL_ERROR);
return;
}
// Try to accept the stream
byte priority = spdySynStreamFrame.getPriority();
boolean remoteSideClosed = spdySynStreamFrame.isLast();
boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
if (!acceptStream(streamID, remoteSideClosed, localSideClosed)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.REFUSED_STREAM);
if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.REFUSED_STREAM);
return;
}
@ -159,7 +246,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
* SPDY SYN_REPLY frame processing requirements:
*
* If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
* it must issue a stream error with the status code PROTOCOL_ERROR.
* it must issue a stream error with the status code STREAM_IN_USE.
*/
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
@ -169,19 +256,20 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
if (spdySynReplyFrame.isInvalid() ||
isRemoteInitiatedID(streamID) ||
spdySession.isRemoteSideClosed(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
return;
}
// Check if we have received multiple frames for the same Stream-ID
if (spdySession.hasReceivedReply(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_IN_USE);
return;
}
spdySession.receivedReply(streamID);
// Close the remote side of the stream if this is the last frame
if (spdySynReplyFrame.isLast()) {
// Close remote side of stream
halfCloseStream(streamID, true);
}
@ -190,8 +278,10 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
/*
* SPDY RST_STREAM frame processing requirements:
*
* After receiving a RST_STREAM on a stream, the receiver must not send additional
* frames on that stream.
* After receiving a RST_STREAM on a stream, the receiver must not send
* additional frames on that stream.
*
* An endpoint must not send a RST_STREAM in response to a RST_STREAM.
*/
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
@ -199,12 +289,29 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdySettingsFrame) {
/*
* Only concerned with MAX_CONCURRENT_STREAMS
*/
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
updateConcurrentStreams(spdySettingsFrame, true);
int newConcurrentStreams =
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
if (newConcurrentStreams >= 0) {
updateConcurrentStreams(newConcurrentStreams, true);
}
// Persistence flag are inconsistent with the use of SETTINGS to communicate
// the initial window size. Remove flags from the sender requesting that the
// value be persisted. Remove values that the sender indicates are persisted.
if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
}
spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
if (flowControl) {
int newInitialWindowSize =
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
if (newInitialWindowSize >= 0) {
updateInitialSendWindowSize(newInitialWindowSize);
}
}
} else if (msg instanceof SpdyPingFrame) {
@ -224,7 +331,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
return;
}
// Note: only checks that there are outstanding pings since uniqueness is not inforced
// Note: only checks that there are outstanding pings since uniqueness is not enforced
if (pings.get() == 0) {
return;
}
@ -241,14 +348,51 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
// Check if we received a valid HEADERS frame
if (spdyHeadersFrame.isInvalid()) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR);
return;
}
if (spdySession.isRemoteSideClosed(streamID)) {
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM);
return;
}
// Close the remote side of the stream if this is the last frame
if (spdyHeadersFrame.isLast()) {
halfCloseStream(streamID, true);
}
} else if (msg instanceof SpdyWindowUpdateFrame) {
/*
* SPDY WINDOW_UPDATE frame processing requirements:
*
* Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
* must send a RST_STREAM with the status code FLOW_CONTROL_ERROR.
*
* Sender should ignore all WINDOW_UPDATE frames associated with a stream
* after sending the last frame for the stream.
*/
if (flowControl) {
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
int streamID = spdyWindowUpdateFrame.getStreamID();
int deltaWindowSize = spdyWindowUpdateFrame.getDeltaWindowSize();
// Ignore frames for half-closed streams
if (spdySession.isLocalSideClosed(streamID)) {
return;
}
// Check for numerical overflow
if (spdySession.getSendWindowSize(streamID) > Integer.MAX_VALUE - deltaWindowSize) {
issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.FLOW_CONTROL_ERROR);
return;
}
updateSendWindowSize(ctx, streamID, deltaWindowSize);
}
return;
}
super.messageReceived(ctx, e);
@ -260,7 +404,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
Throwable cause = e.getCause();
if (cause instanceof SpdyProtocolException) {
issueSessionError(ctx, e.getChannel(), null);
issueSessionError(ctx, e.getChannel(), null, SpdySessionStatus.PROTOCOL_ERROR);
}
super.exceptionCaught(ctx, e);
@ -274,6 +418,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
case OPEN:
case CONNECTED:
case BOUND:
/*
* SPDY connection requirements:
*
* When either endpoint closes the transport-level connection,
* it must first send a GOAWAY frame.
*/
if (Boolean.FALSE.equals(e.getValue()) || e.getValue() == null) {
sendGoAwayFrame(ctx, e);
return;
@ -291,13 +442,85 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
if (msg instanceof SpdyDataFrame) {
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
int streamID = spdyDataFrame.getStreamID();
final int streamID = spdyDataFrame.getStreamID();
// Frames must not be sent on half-closed streams
if (spdySession.isLocalSideClosed(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
}
/*
* SPDY Data frame flow control processing requirements:
*
* Sender must not send a data frame with data length greater
* than the transfer window size.
*
* After sending each data frame, the sender decrements its
* transfer window size by the amount of data transmitted.
*
* When the window size becomes less than or equal to 0, the
* sender must pause transmitting data frames.
*/
if (flowControl) {
synchronized (flowControlLock) {
int dataLength = spdyDataFrame.getData().readableBytes();
int sendWindowSize = spdySession.getSendWindowSize(streamID);
if (sendWindowSize >= dataLength) {
// Window size is large enough to send entire data frame
spdySession.updateSendWindowSize(streamID, -1 * dataLength);
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the stream on write failures that leaves the transfer window in a corrupt state.
final SocketAddress remoteAddress = e.getRemoteAddress();
final ChannelHandlerContext context = ctx;
e.getFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
}
}
});
} else if (sendWindowSize > 0) {
// Stream is not stalled but we cannot send the entire frame
spdySession.updateSendWindowSize(streamID, -1 * sendWindowSize);
// Create a partial data frame whose length is the current window size
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID);
partialDataFrame.setData(spdyDataFrame.getData().readSlice(sendWindowSize));
// Enqueue the remaining data (will be the first frame queued)
spdySession.putPendingWrite(streamID, e);
ChannelFuture writeFuture = Channels.future(e.getChannel());
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the stream on write failures that leaves the transfer window in a corrupt state.
final SocketAddress remoteAddress = e.getRemoteAddress();
final ChannelHandlerContext context = ctx;
e.getFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
}
}
});
Channels.write(ctx, writeFuture, partialDataFrame, remoteAddress);
return;
} else {
// Stream is stalled -- enqueue Data frame and return
spdySession.putPendingWrite(streamID, e);
return;
}
}
}
// Close the local side of the stream if this is the last frame
if (spdyDataFrame.isLast()) {
halfCloseStream(streamID, false);
}
@ -305,9 +528,17 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdySynStreamFrame) {
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
int streamID = spdySynStreamFrame.getStreamID();
if (isRemoteInitiatedID(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
}
byte priority = spdySynStreamFrame.getPriority();
boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
boolean localSideClosed = spdySynStreamFrame.isLast();
if (!acceptStream(spdySynStreamFrame.getStreamID(), remoteSideClosed, localSideClosed)) {
if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
}
@ -317,11 +548,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
int streamID = spdySynReplyFrame.getStreamID();
// Frames must not be sent on half-closed streams
if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
}
// Close the local side of the stream if this is the last frame
if (spdySynReplyFrame.isLast()) {
halfCloseStream(streamID, false);
}
@ -334,7 +567,28 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdySettingsFrame) {
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
updateConcurrentStreams(spdySettingsFrame, false);
int newConcurrentStreams =
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
if (newConcurrentStreams >= 0) {
updateConcurrentStreams(newConcurrentStreams, false);
}
// Persistence flag are inconsistent with the use of SETTINGS to communicate
// the initial window size. Remove flags from the sender requesting that the
// value be persisted. Remove values that the sender indicates are persisted.
if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
}
spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
if (flowControl) {
int newInitialWindowSize =
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
if (newInitialWindowSize >= 0) {
updateInitialReceiveWindowSize(newInitialWindowSize);
}
}
} else if (msg instanceof SpdyPingFrame) {
@ -348,7 +602,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
} else if (msg instanceof SpdyGoAwayFrame) {
// Should send a CLOSE ChannelStateEvent
// Why is this being sent? Intercept it and fail the write.
// Should have sent a CLOSE ChannelStateEvent
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
@ -357,34 +612,65 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
int streamID = spdyHeadersFrame.getStreamID();
// Frames must not be sent on half-closed streams
if (spdySession.isLocalSideClosed(streamID)) {
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
}
// Close the local side of the stream if this is the last frame
if (spdyHeadersFrame.isLast()) {
halfCloseStream(streamID, false);
}
} else if (msg instanceof SpdyWindowUpdateFrame) {
// Why is this being sent? Intercept it and fail the write.
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
return;
}
ctx.sendDownstream(evt);
}
/*
* Error Handling
* SPDY Session Error Handling:
*
* When a session error occurs, the endpoint encountering the error must first
* send a GOAWAY frame with the Stream-ID of the most recently received stream
* from the remote endpoint, and the error code for why the session is terminating.
*
* After sending the GOAWAY frame, the endpoint must close the TCP connection.
*/
private void issueSessionError(
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) {
ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress);
ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress, status);
future.addListener(ChannelFutureListener.CLOSE);
}
// Send a RST_STREAM frame in response to an incoming MessageEvent
// Only called in the upstream direction
/*
* SPDY Stream Error Handling:
*
* Upon a stream error, the endpoint must send a RST_STREAM frame which contains
* the Stream-ID for the stream where the error occurred and the error status which
* caused the error.
*
* After sending the RST_STREAM, the stream is closed to the sending endpoint.
*
* Note: this is only called by the worker thread
*/
private void issueStreamError(
ChannelHandlerContext ctx, MessageEvent e, int streamID, SpdyStreamStatus status) {
ChannelHandlerContext ctx, SocketAddress remoteAddress, int streamID, SpdyStreamStatus status) {
boolean fireMessageReceived = !spdySession.isRemoteSideClosed(streamID);
removeStream(streamID);
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamID, status);
Channels.write(ctx, Channels.future(e.getChannel()), spdyRstStreamFrame, e.getRemoteAddress());
Channels.write(ctx, Channels.future(ctx.getChannel()), spdyRstStreamFrame, remoteAddress);
if (fireMessageReceived) {
Channels.fireMessageReceived(ctx, spdyRstStreamFrame, remoteAddress);
}
}
/*
@ -396,8 +682,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
return server && !serverID || !server && serverID;
}
private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) {
int newConcurrentStreams = settings.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
private void updateConcurrentStreams(int newConcurrentStreams, boolean remote) {
if (remote) {
remoteConcurrentStreams = newConcurrentStreams;
} else {
@ -422,18 +707,37 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
}
}
// need to synchronize accesses to sentGoAwayFrame and lastGoodStreamID
// need to synchronize to prevent new streams from being created while updating active streams
private synchronized void updateInitialSendWindowSize(int newInitialWindowSize) {
int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;
initialSendWindowSize = newInitialWindowSize;
for (Integer StreamID: spdySession.getActiveStreams()) {
spdySession.updateSendWindowSize(StreamID.intValue(), deltaWindowSize);
}
}
// need to synchronize to prevent new streams from being created while updating active streams
private synchronized void updateInitialReceiveWindowSize(int newInitialWindowSize) {
int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;
initialReceiveWindowSize = newInitialWindowSize;
spdySession.updateAllReceiveWindowSizes(deltaWindowSize);
}
// need to synchronize accesses to sentGoAwayFrame, lastGoodStreamID, and initial window sizes
private synchronized boolean acceptStream(
int streamID, boolean remoteSideClosed, boolean localSideClosed) {
int streamID, byte priority, boolean remoteSideClosed, boolean localSideClosed) {
// Cannot initiate any new streams after receiving or sending GOAWAY
if (receivedGoAwayFrame || sentGoAwayFrame) {
return false;
}
int maxConcurrentStreams = this.maxConcurrentStreams; // read volatile once
if (maxConcurrentStreams != 0 &&
spdySession.numActiveStreams() >= maxConcurrentStreams) {
return false;
}
spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed);
spdySession.acceptStream(
streamID, priority, remoteSideClosed, localSideClosed, initialSendWindowSize, initialReceiveWindowSize);
if (isRemoteInitiatedID(streamID)) {
lastGoodStreamID = streamID;
}
@ -458,6 +762,74 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
}
}
private void updateSendWindowSize(ChannelHandlerContext ctx, final int streamID, int deltaWindowSize) {
synchronized (flowControlLock) {
int newWindowSize = spdySession.updateSendWindowSize(streamID, deltaWindowSize);
while (newWindowSize > 0) {
// Check if we have unblocked a stalled stream
MessageEvent e = spdySession.getPendingWrite(streamID);
if (e == null) {
break;
}
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) e.getMessage();
int dataFrameSize = spdyDataFrame.getData().readableBytes();
if (newWindowSize >= dataFrameSize) {
// Window size is large enough to send entire data frame
spdySession.removePendingWrite(streamID);
newWindowSize = spdySession.updateSendWindowSize(streamID, -1 * dataFrameSize);
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the stream on write failures that leaves the transfer window in a corrupt state.
final SocketAddress remoteAddress = e.getRemoteAddress();
final ChannelHandlerContext context = ctx;
e.getFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
}
}
});
// Close the local side of the stream if this is the last frame
if (spdyDataFrame.isLast()) {
halfCloseStream(streamID, false);
}
Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress());
} else {
// We can send a partial frame
spdySession.updateSendWindowSize(streamID, -1 * newWindowSize);
// Create a partial data frame whose length is the current window size
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID);
partialDataFrame.setData(spdyDataFrame.getData().readSlice(newWindowSize));
ChannelFuture writeFuture = Channels.future(e.getChannel());
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the stream on write failures that leaves the transfer window in a corrupt state.
final SocketAddress remoteAddress = e.getRemoteAddress();
final ChannelHandlerContext context = ctx;
e.getFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR);
}
}
});
Channels.write(ctx, writeFuture, partialDataFrame, remoteAddress);
newWindowSize = 0;
}
}
}
}
private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelStateEvent e) {
// Avoid NotYetConnectedException
if (!e.getChannel().isConnected()) {
@ -465,7 +837,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
return;
}
ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null);
ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null, SpdySessionStatus.OK);
if (spdySession.noActiveStreams()) {
future.addListener(new ClosingChannelFutureListener(ctx, e));
} else {
@ -475,18 +847,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
}
private synchronized ChannelFuture sendGoAwayFrame(
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) {
if (!sentGoAwayFrame) {
sentGoAwayFrame = true;
SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamID, status);
ChannelFuture future = Channels.future(channel);
Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID), remoteAddress);
Channels.write(ctx, future, spdyGoAwayFrame, remoteAddress);
return future;
}
return Channels.succeededFuture(channel);
}
private static final class ClosingChannelFutureListener implements ChannelFutureListener {
private final ChannelHandlerContext ctx;
private final ChannelStateEvent e;

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 org.jboss.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

@ -29,6 +29,7 @@ public interface SpdySettingsFrame {
int SETTINGS_CURRENT_CWND = 5;
int SETTINGS_DOWNLOAD_RETRANS_RATE = 6;
int SETTINGS_INITIAL_WINDOW_SIZE = 7;
int SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
/**
* Returns a {@code Set} of the setting IDs.
@ -49,7 +50,7 @@ public interface SpdySettingsFrame {
/**
* Sets the value of the setting ID.
* The ID must be positive and cannot exceeed 16777215.
* The ID must be positive and cannot exceed 16777215.
*/
void setValue(int ID, int value);

View File

@ -63,6 +63,30 @@ public class SpdyStreamStatus implements Comparable<SpdyStreamStatus> {
public static final SpdyStreamStatus FLOW_CONTROL_ERROR =
new SpdyStreamStatus(7, "FLOW_CONTROL_ERROR");
/**
* 8 Stream In Use
*/
public static final SpdyStreamStatus STREAM_IN_USE =
new SpdyStreamStatus(8, "STREAM_IN_USE");
/**
* 9 Stream Already Closed
*/
public static final SpdyStreamStatus STREAM_ALREADY_CLOSED =
new SpdyStreamStatus(9, "STREAM_ALREADY_CLOSED");
/**
* 10 Invalid Credentials
*/
public static final SpdyStreamStatus INVALID_CREDENTIALS =
new SpdyStreamStatus(10, "INVALID_CREDENTIALS");
/**
* 11 Frame Too Large
*/
public static final SpdyStreamStatus FRAME_TOO_LARGE =
new SpdyStreamStatus(11, "FRAME_TOO_LARGE");
/**
* Returns the {@link SpdyStreamStatus} represented by the specified code.
* If the specified code is a defined SPDY status code, a cached instance
@ -89,6 +113,14 @@ public class SpdyStreamStatus implements Comparable<SpdyStreamStatus> {
return INTERNAL_ERROR;
case 7:
return FLOW_CONTROL_ERROR;
case 8:
return STREAM_IN_USE;
case 9:
return STREAM_ALREADY_CLOSED;
case 10:
return INVALID_CREDENTIALS;
case 11:
return FRAME_TOO_LARGE;
}
return new SpdyStreamStatus(code, "UNKNOWN (" + code + ')');

View File

@ -48,7 +48,7 @@ public interface SpdySynStreamFrame extends SpdyHeaderBlock {
/**
* Sets the priority of the stream.
* The priority must be between 0 and 3 inclusive.
* The priority must be between 0 and 7 inclusive.
*/
void setPriority(byte priority);

View File

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

View File

@ -132,7 +132,7 @@ public class SpdySessionHandlerTest {
sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID));
Assert.assertNull(sessionHandler.peek());
sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID));
assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.PROTOCOL_ERROR);
assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.STREAM_IN_USE);
Assert.assertNull(sessionHandler.peek());
remoteStreamID += 2;