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:
Brendt Lucas 2015-09-04 22:17:29 +01:00 committed by Norman Maurer
parent 58dc7f7902
commit 7049d8debb
6 changed files with 120 additions and 20 deletions

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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;