Add validateHeaders and headersToLowerCase options for SPDY
Motivation: Related to issue #4185. HTTP has the option to disable header validation for optimisation purposes. Introduce the same option for SPDY headers. Also, optimise SpdyHttpEncoder by allowing the user to specify whether or not the encoder needs to convert header names to lowercase. Modifications: Added flags for validation and conversion. Result: SpdyHeader validation and conversion can be disabled.
This commit is contained in:
parent
58dc7f7902
commit
7049d8debb
@ -36,7 +36,13 @@ public class DefaultSpdyHeaders extends DefaultHeaders<CharSequence> implements
|
||||
};
|
||||
|
||||
public DefaultSpdyHeaders() {
|
||||
super(CASE_INSENSITIVE_HASHER, HeaderValueConverterAndValidator.INSTANCE, SpydNameValidator);
|
||||
this(true);
|
||||
}
|
||||
|
||||
public DefaultSpdyHeaders(boolean validate) {
|
||||
super(CASE_INSENSITIVE_HASHER,
|
||||
validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE,
|
||||
validate ? SpydNameValidator : NameValidator.NOT_NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -269,8 +275,8 @@ public class DefaultSpdyHeaders extends DefaultHeaders<CharSequence> implements
|
||||
ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
|
||||
}
|
||||
|
||||
private static final class HeaderValueConverterAndValidator extends CharSequenceValueConverter {
|
||||
public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
private static class HeaderValueConverter extends CharSequenceValueConverter {
|
||||
public static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
@ -281,6 +287,16 @@ public class DefaultSpdyHeaders extends DefaultHeaders<CharSequence> implements
|
||||
seq = value.toString();
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
|
||||
public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
final CharSequence seq = super.convertObject(value);
|
||||
SpdyCodecUtil.validateHeaderValue(seq);
|
||||
return seq;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
|
||||
|
||||
private boolean invalid;
|
||||
private boolean truncated;
|
||||
private final SpdyHeaders headers = new DefaultSpdyHeaders();
|
||||
private final SpdyHeaders headers;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -35,7 +35,18 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
|
||||
* @param streamId the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyHeadersFrame(int streamId) {
|
||||
this(streamId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param validate validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public DefaultSpdyHeadersFrame(int streamId, boolean validate) {
|
||||
super(streamId);
|
||||
headers = new DefaultSpdyHeaders(validate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -32,6 +32,16 @@ public class DefaultSpdySynReplyFrame extends DefaultSpdyHeadersFrame
|
||||
super(streamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public DefaultSpdySynReplyFrame(int streamId, boolean validateHeaders) {
|
||||
super(streamId, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynReplyFrame setStreamId(int streamId) {
|
||||
super.setStreamId(streamId);
|
||||
|
@ -35,7 +35,19 @@ public class DefaultSpdySynStreamFrame extends DefaultSpdyHeadersFrame
|
||||
* @param priority the priority of the stream
|
||||
*/
|
||||
public DefaultSpdySynStreamFrame(int streamId, int associatedStreamId, byte priority) {
|
||||
super(streamId);
|
||||
this(streamId, associatedStreamId, priority, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param associatedStreamId the Associated-To-Stream-ID of this frame
|
||||
* @param priority the priority of the stream
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public DefaultSpdySynStreamFrame(int streamId, int associatedStreamId, byte priority, boolean validateHeaders) {
|
||||
super(streamId, validateHeaders);
|
||||
setAssociatedStreamId(associatedStreamId);
|
||||
setPriority(priority);
|
||||
}
|
||||
|
@ -47,35 +47,62 @@ public class SpdyFrameCodec extends ByteToMessageDecoder
|
||||
|
||||
private ChannelHandlerContext ctx;
|
||||
private boolean read;
|
||||
private final boolean validateHeaders;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version} and
|
||||
* Creates a new instance with the specified {@code version},
|
||||
* {@code validateHeaders (true)}, 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(SpdyVersion version) {
|
||||
this(version, 8192, 16384, 6, 15, 8);
|
||||
this(version, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder and encoder options.
|
||||
* Creates a new instance with the specified {@code version},
|
||||
* {@code validateHeaders}, 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(SpdyVersion version, boolean validateHeaders) {
|
||||
this(version, 8192, 16384, 6, 15, 8, validateHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version}, {@code validateHeaders (true)},
|
||||
* decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
SpdyVersion version, int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel) {
|
||||
this(version, maxChunkSize, maxHeaderSize, compressionLevel, windowBits, memLevel, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version}, {@code validateHeaders},
|
||||
* decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
SpdyVersion version, int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel, boolean validateHeaders) {
|
||||
this(version, maxChunkSize,
|
||||
SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize),
|
||||
SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel));
|
||||
SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel), validateHeaders);
|
||||
}
|
||||
|
||||
protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize,
|
||||
SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder) {
|
||||
SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder,
|
||||
boolean validateHeaders) {
|
||||
spdyFrameDecoder = new SpdyFrameDecoder(version, this, maxChunkSize);
|
||||
spdyFrameEncoder = new SpdyFrameEncoder(version);
|
||||
this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder;
|
||||
this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder;
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -274,7 +301,8 @@ public class SpdyFrameCodec extends ByteToMessageDecoder
|
||||
@Override
|
||||
public void readSynStreamFrame(
|
||||
int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) {
|
||||
SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
|
||||
spdySynStreamFrame.setLast(last);
|
||||
spdySynStreamFrame.setUnidirectional(unidirectional);
|
||||
spdyHeadersFrame = spdySynStreamFrame;
|
||||
@ -282,7 +310,7 @@ public class SpdyFrameCodec extends ByteToMessageDecoder
|
||||
|
||||
@Override
|
||||
public void readSynReplyFrame(int streamId, boolean last) {
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
|
||||
spdySynReplyFrame.setLast(last);
|
||||
spdyHeadersFrame = spdySynReplyFrame;
|
||||
}
|
||||
@ -335,7 +363,7 @@ public class SpdyFrameCodec extends ByteToMessageDecoder
|
||||
|
||||
@Override
|
||||
public void readHeadersFrame(int streamId, boolean last) {
|
||||
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
|
||||
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
|
||||
spdyHeadersFrame.setLast(last);
|
||||
}
|
||||
|
||||
|
@ -123,15 +123,32 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
|
||||
private int currentStreamId;
|
||||
|
||||
private final boolean validateHeaders;
|
||||
private final boolean headersToLowerCase;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
*/
|
||||
public SpdyHttpEncoder(SpdyVersion version) {
|
||||
this(version, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
* @param headersToLowerCase convert header names to lowercase. In a controlled environment,
|
||||
* one can disable the conversion.
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public SpdyHttpEncoder(SpdyVersion version, boolean headersToLowerCase, boolean validateHeaders) {
|
||||
if (version == null) {
|
||||
throw new NullPointerException("version");
|
||||
}
|
||||
this.headersToLowerCase = headersToLowerCase;
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -172,12 +189,14 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
out.add(spdyDataFrame);
|
||||
} else {
|
||||
// Create SPDY HEADERS frame out of trailers
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId);
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId, validateHeaders);
|
||||
spdyHeadersFrame.setLast(true);
|
||||
Iterator<Entry<CharSequence, CharSequence>> itr = trailers.iteratorCharSequence();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<CharSequence, CharSequence> entry = itr.next();
|
||||
spdyHeadersFrame.headers().add(AsciiString.of(entry.getKey()).toLowerCase(), entry.getValue());
|
||||
final CharSequence headerName =
|
||||
headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
|
||||
spdyHeadersFrame.headers().add(headerName, entry.getValue());
|
||||
}
|
||||
|
||||
// Write DATA frame and append HEADERS frame
|
||||
@ -217,7 +236,7 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
|
||||
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
|
||||
|
||||
// Unfold the first line of the message into name/value pairs
|
||||
SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
|
||||
@ -240,7 +259,9 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
Iterator<Entry<CharSequence, CharSequence>> itr = httpHeaders.iteratorCharSequence();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<CharSequence, CharSequence> entry = itr.next();
|
||||
frameHeaders.add(AsciiString.of(entry.getKey()).toLowerCase(), entry.getValue());
|
||||
final CharSequence headerName =
|
||||
headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
|
||||
frameHeaders.add(headerName, entry.getValue());
|
||||
}
|
||||
currentStreamId = spdySynStreamFrame.streamId();
|
||||
if (associatedToStreamId == 0) {
|
||||
@ -268,9 +289,9 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame;
|
||||
if (SpdyCodecUtil.isServerId(streamId)) {
|
||||
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
|
||||
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
|
||||
} else {
|
||||
spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||
spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
|
||||
}
|
||||
SpdyHeaders frameHeaders = spdyHeadersFrame.headers();
|
||||
// Unfold the first line of the response into name/value pairs
|
||||
@ -281,7 +302,9 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
Iterator<Entry<CharSequence, CharSequence>> itr = httpHeaders.iteratorCharSequence();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<CharSequence, CharSequence> entry = itr.next();
|
||||
spdyHeadersFrame.headers().add(AsciiString.of(entry.getKey()).toLowerCase(), entry.getValue());
|
||||
final CharSequence headerName =
|
||||
headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
|
||||
spdyHeadersFrame.headers().add(headerName, entry.getValue());
|
||||
}
|
||||
|
||||
currentStreamId = streamId;
|
||||
|
Loading…
Reference in New Issue
Block a user