Port the HTTP / RTSP encoder and decoder from 4.0 branch as those are just faster.
This commit is contained in:
parent
addae7b8ad
commit
5106382b44
@ -19,8 +19,8 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.DecoderResult;
|
||||
import io.netty.handler.codec.ReplayingDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
|
||||
import java.util.List;
|
||||
@ -98,11 +98,7 @@ import static io.netty.buffer.ByteBufUtil.readBytes;
|
||||
* To implement the decoder of such a derived protocol, extend this class and
|
||||
* implement all abstract methods properly.
|
||||
*/
|
||||
public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
|
||||
static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
|
||||
static final int DEFAULT_MAX_HEADER_SIZE = 8192;
|
||||
static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
|
||||
public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecoder.State> {
|
||||
|
||||
private final int maxInitialLineLength;
|
||||
private final int maxHeaderSize;
|
||||
@ -110,12 +106,11 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
private final boolean chunkedSupported;
|
||||
protected final boolean validateHeaders;
|
||||
|
||||
private ByteBuf content;
|
||||
private HttpMessage message;
|
||||
private long chunkSize;
|
||||
private int headerSize;
|
||||
private int contentRead;
|
||||
private long contentLength = Long.MIN_VALUE;
|
||||
private State state = State.SKIP_CONTROL_CHARS;
|
||||
private final StringBuilder sb = new StringBuilder(128);
|
||||
|
||||
/**
|
||||
@ -144,7 +139,15 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
* {@code maxChunkSize (8192)}.
|
||||
*/
|
||||
protected HttpObjectDecoder() {
|
||||
this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_INITIAL_LINE_LENGTH, true, true);
|
||||
this(4096, 8192, 8192, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
protected HttpObjectDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
|
||||
this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,6 +157,8 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
|
||||
boolean chunkedSupported, boolean validateHeaders) {
|
||||
|
||||
super(State.SKIP_CONTROL_CHARS);
|
||||
|
||||
if (maxInitialLineLength <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxInitialLineLength must be a positive integer: " +
|
||||
@ -176,135 +181,115 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleDecode() {
|
||||
if (message == null && super.isSingleDecode()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
|
||||
switch (state) {
|
||||
switch (state()) {
|
||||
case SKIP_CONTROL_CHARS: {
|
||||
if (!skipControlCharacters(buffer)) {
|
||||
return;
|
||||
try {
|
||||
skipControlCharacters(buffer);
|
||||
checkpoint(State.READ_INITIAL);
|
||||
} finally {
|
||||
checkpoint();
|
||||
}
|
||||
state = State.READ_INITIAL;
|
||||
// FALL THROUGH
|
||||
}
|
||||
case READ_INITIAL: try {
|
||||
StringBuilder sb = this.sb;
|
||||
sb.setLength(0);
|
||||
|
||||
HttpMessage msg = splitInitialLine(sb, buffer, maxInitialLineLength);
|
||||
if (msg == null) {
|
||||
// not enough data
|
||||
String[] initialLine = splitInitialLine(readLine(buffer, maxInitialLineLength));
|
||||
if (initialLine.length < 3) {
|
||||
// Invalid initial line - ignore.
|
||||
checkpoint(State.SKIP_CONTROL_CHARS);
|
||||
return;
|
||||
}
|
||||
|
||||
message = msg;
|
||||
state = State.READ_HEADER;
|
||||
return;
|
||||
message = createMessage(initialLine);
|
||||
checkpoint(State.READ_HEADER);
|
||||
|
||||
} catch (Exception e) {
|
||||
out.add(invalidMessage(e));
|
||||
return;
|
||||
}
|
||||
case READ_HEADER: try {
|
||||
State nextState = readHeaders(buffer);
|
||||
if (nextState == state) {
|
||||
// was not able to consume whole header
|
||||
return;
|
||||
}
|
||||
state = nextState;
|
||||
|
||||
checkpoint(nextState);
|
||||
if (nextState == State.READ_CHUNK_SIZE) {
|
||||
if (!chunkedSupported) {
|
||||
throw new IllegalArgumentException("Chunked messages not supported");
|
||||
}
|
||||
out.add(message);
|
||||
// Chunked encoding - generate HttpMessage first. HttpChunks will follow.
|
||||
out.add(message);
|
||||
return;
|
||||
}
|
||||
if (nextState == State.SKIP_CONTROL_CHARS) {
|
||||
// No content is expected.
|
||||
out.add(message);
|
||||
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
reset();
|
||||
reset(out);
|
||||
return;
|
||||
}
|
||||
long contentLength = contentLength();
|
||||
long contentLength = HttpHeaders.getContentLength(message, -1);
|
||||
if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
|
||||
out.add(message);
|
||||
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
reset();
|
||||
content = Unpooled.EMPTY_BUFFER;
|
||||
reset(out);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (nextState) {
|
||||
case READ_FIXED_LENGTH_CONTENT:
|
||||
if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
|
||||
state = State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS;
|
||||
// Generate FullHttpMessage first. HttpChunks will follow.
|
||||
checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
|
||||
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT_AS_CHUNKS
|
||||
// state reads data chunk by chunk.
|
||||
chunkSize = contentLength;
|
||||
chunkSize = HttpHeaders.getContentLength(message, -1);
|
||||
out.add(message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case READ_VARIABLE_LENGTH_CONTENT:
|
||||
if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
|
||||
state = State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS;
|
||||
// Generate FullHttpMessage first. HttpChunks will follow.
|
||||
checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
|
||||
out.add(message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected state: " + nextState);
|
||||
}
|
||||
out.add(message);
|
||||
// We return here, this forces decode to be called again where we will decode the content
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
out.add(invalidMessage(e));
|
||||
return;
|
||||
}
|
||||
case READ_VARIABLE_LENGTH_CONTENT: {
|
||||
int toRead = buffer.readableBytes();
|
||||
if (toRead == 0) {
|
||||
// nothing to read
|
||||
return;
|
||||
}
|
||||
int toRead = actualReadableBytes();
|
||||
if (toRead > maxChunkSize) {
|
||||
toRead = maxChunkSize;
|
||||
}
|
||||
// TODO: Slice
|
||||
out.add(message);
|
||||
out.add(new DefaultHttpContent(readBytes(ctx.alloc(), buffer, toRead)));
|
||||
return;
|
||||
}
|
||||
case READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS: {
|
||||
// Keep reading data as a chunk until the end of connection is reached.
|
||||
int toRead = buffer.readableBytes();
|
||||
if (toRead == 0) {
|
||||
// nothing to read
|
||||
return;
|
||||
}
|
||||
int toRead = actualReadableBytes();
|
||||
if (toRead > maxChunkSize) {
|
||||
toRead = maxChunkSize;
|
||||
}
|
||||
// TODO: Slice
|
||||
ByteBuf content = readBytes(ctx.alloc(), buffer, toRead);
|
||||
if (!buffer.isReadable()) {
|
||||
out.add(new DefaultLastHttpContent(content));
|
||||
reset();
|
||||
out.add(new DefaultLastHttpContent(content, validateHeaders));
|
||||
return;
|
||||
}
|
||||
out.add(new DefaultHttpContent(content));
|
||||
return;
|
||||
}
|
||||
case READ_FIXED_LENGTH_CONTENT: {
|
||||
readFixedLengthContent(ctx, buffer, contentLength(), out);
|
||||
readFixedLengthContent(ctx, buffer, out);
|
||||
return;
|
||||
}
|
||||
case READ_FIXED_LENGTH_CONTENT_AS_CHUNKS: {
|
||||
long chunkSize = this.chunkSize;
|
||||
int toRead = buffer.readableBytes();
|
||||
int readLimit = actualReadableBytes();
|
||||
|
||||
// Check if the buffer is readable first as we use the readable byte count
|
||||
// to create the HttpChunk. This is needed as otherwise we may end up with
|
||||
@ -312,17 +297,17 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
// handled like it is the last HttpChunk.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/433
|
||||
if (toRead == 0) {
|
||||
if (readLimit == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int toRead = readLimit;
|
||||
if (toRead > maxChunkSize) {
|
||||
toRead = maxChunkSize;
|
||||
}
|
||||
if (toRead > chunkSize) {
|
||||
toRead = (int) chunkSize;
|
||||
}
|
||||
// TODO: Slice
|
||||
ByteBuf content = readBytes(ctx.alloc(), buffer, toRead);
|
||||
if (chunkSize > toRead) {
|
||||
chunkSize -= toRead;
|
||||
@ -333,8 +318,8 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
|
||||
if (chunkSize == 0) {
|
||||
// Read all content.
|
||||
out.add(new DefaultLastHttpContent(content));
|
||||
reset();
|
||||
out.add(new DefaultLastHttpContent(content, validateHeaders));
|
||||
return;
|
||||
}
|
||||
out.add(new DefaultHttpContent(content));
|
||||
@ -346,41 +331,32 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
*/
|
||||
case READ_CHUNK_SIZE: try {
|
||||
StringBuilder line = readLine(buffer, maxInitialLineLength);
|
||||
|
||||
if (line == null) {
|
||||
// Not enough data
|
||||
return;
|
||||
}
|
||||
|
||||
int chunkSize = getChunkSize(line.toString());
|
||||
this.chunkSize = chunkSize;
|
||||
if (chunkSize == 0) {
|
||||
state = State.READ_CHUNK_FOOTER;
|
||||
checkpoint(State.READ_CHUNK_FOOTER);
|
||||
return;
|
||||
} else if (chunkSize > maxChunkSize) {
|
||||
// A chunk is too large. Split them into multiple chunks again.
|
||||
state = State.READ_CHUNKED_CONTENT_AS_CHUNKS;
|
||||
checkpoint(State.READ_CHUNKED_CONTENT_AS_CHUNKS);
|
||||
} else {
|
||||
state = State.READ_CHUNKED_CONTENT;
|
||||
checkpoint(State.READ_CHUNKED_CONTENT);
|
||||
}
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
out.add(invalidChunk(e));
|
||||
return;
|
||||
}
|
||||
case READ_CHUNKED_CONTENT: {
|
||||
assert chunkSize <= Integer.MAX_VALUE;
|
||||
if (buffer.readableBytes() < chunkSize) {
|
||||
// not enough data
|
||||
return;
|
||||
}
|
||||
// TODO: Slice
|
||||
HttpContent chunk = new DefaultHttpContent(readBytes(ctx.alloc(), buffer, (int) chunkSize));
|
||||
state = State.READ_CHUNK_DELIMITER;
|
||||
checkpoint(State.READ_CHUNK_DELIMITER);
|
||||
out.add(chunk);
|
||||
return;
|
||||
}
|
||||
case READ_CHUNKED_CONTENT_AS_CHUNKS: {
|
||||
int toRead = buffer.readableBytes();
|
||||
assert chunkSize <= Integer.MAX_VALUE;
|
||||
int chunkSize = (int) this.chunkSize;
|
||||
int readLimit = actualReadableBytes();
|
||||
|
||||
// Check if the buffer is readable first as we use the readable byte count
|
||||
// to create the HttpChunk. This is needed as otherwise we may end up with
|
||||
@ -388,20 +364,17 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
// handled like it is the last HttpChunk.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/433
|
||||
if (toRead == 0) {
|
||||
if (readLimit == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert chunkSize <= Integer.MAX_VALUE;
|
||||
int chunkSize = (int) this.chunkSize;
|
||||
int toRead = chunkSize;
|
||||
if (toRead > maxChunkSize) {
|
||||
toRead = maxChunkSize;
|
||||
}
|
||||
|
||||
if (toRead > chunkSize) {
|
||||
toRead = chunkSize;
|
||||
if (toRead > readLimit) {
|
||||
toRead = readLimit;
|
||||
}
|
||||
|
||||
HttpContent chunk = new DefaultHttpContent(readBytes(ctx.alloc(), buffer, toRead));
|
||||
if (chunkSize > toRead) {
|
||||
chunkSize -= toRead;
|
||||
@ -412,49 +385,47 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
|
||||
if (chunkSize == 0) {
|
||||
// Read all content.
|
||||
state = State.READ_CHUNK_DELIMITER;
|
||||
checkpoint(State.READ_CHUNK_DELIMITER);
|
||||
}
|
||||
|
||||
out.add(chunk);
|
||||
return;
|
||||
}
|
||||
case READ_CHUNK_DELIMITER: {
|
||||
buffer.markReaderIndex();
|
||||
while (buffer.isReadable()) {
|
||||
for (;;) {
|
||||
byte next = buffer.readByte();
|
||||
if (next == HttpConstants.LF) {
|
||||
state = State.READ_CHUNK_SIZE;
|
||||
if (next == HttpConstants.CR) {
|
||||
if (buffer.readByte() == HttpConstants.LF) {
|
||||
checkpoint(State.READ_CHUNK_SIZE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Try again later with more data
|
||||
// TODO: Optimize
|
||||
buffer.resetReaderIndex();
|
||||
} else if (next == HttpConstants.LF) {
|
||||
checkpoint(State.READ_CHUNK_SIZE);
|
||||
return;
|
||||
} else {
|
||||
checkpoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
case READ_CHUNK_FOOTER: try {
|
||||
LastHttpContent trailer = readTrailingHeaders(buffer);
|
||||
if (trailer == null) {
|
||||
// not enough data
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxChunkSize == 0) {
|
||||
// Chunked encoding disabled.
|
||||
reset(out);
|
||||
return;
|
||||
} else {
|
||||
reset();
|
||||
// The last chunk, which is empty
|
||||
out.add(trailer);
|
||||
}
|
||||
reset();
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
out.add(invalidChunk(e));
|
||||
return;
|
||||
}
|
||||
case BAD_MESSAGE: {
|
||||
// Keep discarding until disconnection.
|
||||
buffer.skipBytes(buffer.readableBytes());
|
||||
buffer.skipBytes(actualReadableBytes());
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
@ -463,13 +434,6 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private long contentLength() {
|
||||
if (contentLength == Long.MIN_VALUE) {
|
||||
contentLength = HttpHeaders.getContentLength(message, -1);
|
||||
}
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
decode(ctx, in, out);
|
||||
@ -478,8 +442,13 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
if (message != null) {
|
||||
// Get the length of the content received so far for the last message.
|
||||
HttpMessage message = this.message;
|
||||
int actualContentLength;
|
||||
if (content != null) {
|
||||
actualContentLength = content.readableBytes();
|
||||
} else {
|
||||
actualContentLength = 0;
|
||||
}
|
||||
|
||||
int readable = in.readableBytes();
|
||||
// Check if the closure of the connection signifies the end of the content.
|
||||
boolean prematureClosure;
|
||||
if (isDecodingRequest()) {
|
||||
@ -489,15 +458,15 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
// Compare the length of the received content and the 'Content-Length' header.
|
||||
// If the 'Content-Length' header is absent, the length of the content is determined by the end of the
|
||||
// connection, so it is perfectly fine.
|
||||
long expectedContentLength = contentLength();
|
||||
prematureClosure = expectedContentLength >= 0 && contentRead + readable != expectedContentLength;
|
||||
long expectedContentLength = HttpHeaders.getContentLength(message, -1);
|
||||
prematureClosure = expectedContentLength >= 0 && actualContentLength != expectedContentLength;
|
||||
}
|
||||
|
||||
if (!prematureClosure) {
|
||||
if (readable == 0) {
|
||||
if (actualContentLength == 0) {
|
||||
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
} else {
|
||||
out.add(new DefaultLastHttpContent(readBytes(ctx.alloc(), in, readable)));
|
||||
out.add(new DefaultLastHttpContent(content, validateHeaders));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -530,14 +499,33 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
reset(null);
|
||||
}
|
||||
|
||||
private void reset(List<Object> out) {
|
||||
if (out != null) {
|
||||
HttpMessage message = this.message;
|
||||
ByteBuf content = this.content;
|
||||
LastHttpContent httpContent;
|
||||
|
||||
if (content == null || !content.isReadable()) {
|
||||
httpContent = LastHttpContent.EMPTY_LAST_CONTENT;
|
||||
} else {
|
||||
httpContent = new DefaultLastHttpContent(content, validateHeaders);
|
||||
}
|
||||
|
||||
out.add(message);
|
||||
out.add(httpContent);
|
||||
}
|
||||
|
||||
content = null;
|
||||
message = null;
|
||||
contentLength = Long.MIN_VALUE;
|
||||
contentRead = 0;
|
||||
state = State.SKIP_CONTROL_CHARS;
|
||||
|
||||
checkpoint(State.SKIP_CONTROL_CHARS);
|
||||
}
|
||||
|
||||
private HttpMessage invalidMessage(Exception cause) {
|
||||
state = State.BAD_MESSAGE;
|
||||
checkpoint(State.BAD_MESSAGE);
|
||||
if (message != null) {
|
||||
message.setDecoderResult(DecoderResult.failure(cause));
|
||||
} else {
|
||||
@ -548,237 +536,102 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
private HttpContent invalidChunk(Exception cause) {
|
||||
state = State.BAD_MESSAGE;
|
||||
checkpoint(State.BAD_MESSAGE);
|
||||
HttpContent chunk = new DefaultHttpContent(Unpooled.EMPTY_BUFFER);
|
||||
chunk.setDecoderResult(DecoderResult.failure(cause));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private static boolean skipControlCharacters(ByteBuf buffer) {
|
||||
while (buffer.isReadable()) {
|
||||
private static void skipControlCharacters(ByteBuf buffer) {
|
||||
for (;;) {
|
||||
char c = (char) buffer.readUnsignedByte();
|
||||
if (!Character.isISOControl(c) &&
|
||||
!Character.isWhitespace(c)) {
|
||||
buffer.readerIndex(buffer.readerIndex() - 1);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void readFixedLengthContent(ChannelHandlerContext ctx, ByteBuf buffer, long length, List<Object> out) {
|
||||
assert length <= Integer.MAX_VALUE;
|
||||
|
||||
private void readFixedLengthContent(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
|
||||
//we have a content-length so we just read the correct number of bytes
|
||||
long length = HttpHeaders.getContentLength(message, -1);
|
||||
assert length <= Integer.MAX_VALUE;
|
||||
int toRead = (int) length - contentRead;
|
||||
|
||||
int readableBytes = buffer.readableBytes();
|
||||
if (toRead > readableBytes) {
|
||||
toRead = readableBytes;
|
||||
if (toRead > actualReadableBytes()) {
|
||||
toRead = actualReadableBytes();
|
||||
}
|
||||
|
||||
contentRead += toRead;
|
||||
// TODO: Slice
|
||||
ByteBuf buf = readBytes(ctx.alloc(), buffer, toRead);
|
||||
if (contentRead < length) {
|
||||
out.add(new DefaultHttpContent(buf));
|
||||
if (length < contentRead) {
|
||||
out.add(message);
|
||||
out.add(new DefaultHttpContent(readBytes(ctx.alloc(), buffer, toRead)));
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(new DefaultLastHttpContent(buf));
|
||||
reset();
|
||||
if (content == null) {
|
||||
content = readBytes(ctx.alloc(), buffer, (int) length);
|
||||
} else {
|
||||
content.writeBytes(buffer, (int) length);
|
||||
}
|
||||
reset(out);
|
||||
}
|
||||
|
||||
private State readHeaders(ByteBuf buffer) {
|
||||
headerSize = 0;
|
||||
final HttpMessage message = this.message;
|
||||
if (!parseHeaders(message.headers(), buffer)) {
|
||||
return state;
|
||||
final HttpHeaders headers = message.headers();
|
||||
|
||||
StringBuilder line = readHeader(buffer);
|
||||
String name = null;
|
||||
String value = null;
|
||||
if (line.length() > 0) {
|
||||
headers.clear();
|
||||
do {
|
||||
char firstChar = line.charAt(0);
|
||||
if (name != null && (firstChar == ' ' || firstChar == '\t')) {
|
||||
value = value + ' ' + line.toString().trim();
|
||||
} else {
|
||||
if (name != null) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
// this means we consumed the header completly
|
||||
String[] header = splitHeader(line);
|
||||
name = header[0];
|
||||
value = header[1];
|
||||
}
|
||||
|
||||
line = readHeader(buffer);
|
||||
} while (line.length() > 0);
|
||||
|
||||
// Add the last header.
|
||||
if (name != null) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
State nextState;
|
||||
|
||||
if (isContentAlwaysEmpty(message)) {
|
||||
HttpHeaders.removeTransferEncodingChunked(message);
|
||||
return State.SKIP_CONTROL_CHARS;
|
||||
nextState = State.SKIP_CONTROL_CHARS;
|
||||
} else if (HttpHeaders.isTransferEncodingChunked(message)) {
|
||||
return State.READ_CHUNK_SIZE;
|
||||
} else if (contentLength() >= 0) {
|
||||
return State.READ_FIXED_LENGTH_CONTENT;
|
||||
nextState = State.READ_CHUNK_SIZE;
|
||||
} else if (HttpHeaders.getContentLength(message, -1) >= 0) {
|
||||
nextState = State.READ_FIXED_LENGTH_CONTENT;
|
||||
} else {
|
||||
return State.READ_VARIABLE_LENGTH_CONTENT;
|
||||
nextState = State.READ_VARIABLE_LENGTH_CONTENT;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
|
||||
private enum HeaderParseState {
|
||||
LINE_START,
|
||||
LINE_END,
|
||||
VALUE_START,
|
||||
VALUE_END,
|
||||
COMMA_END,
|
||||
NAME_START,
|
||||
NAME_END,
|
||||
HEADERS_END
|
||||
}
|
||||
|
||||
private boolean parseHeaders(HttpHeaders headers, ByteBuf buffer) {
|
||||
// mark the index before try to start parsing and reset the StringBuilder
|
||||
StringBuilder sb = this.sb;
|
||||
sb.setLength(0);
|
||||
buffer.markReaderIndex();
|
||||
|
||||
String name = null;
|
||||
HeaderParseState parseState = HeaderParseState.LINE_START;
|
||||
|
||||
loop:
|
||||
while (buffer.isReadable()) {
|
||||
// Abort decoding if the header part is too large.
|
||||
if (headerSize++ >= maxHeaderSize) {
|
||||
// TODO: Respond with Bad Request and discard the traffic
|
||||
// or close the connection.
|
||||
// No need to notify the upstream handlers - just log.
|
||||
// If decoding a response, just throw an exception.
|
||||
throw new TooLongFrameException(
|
||||
"HTTP header is larger than " +
|
||||
maxHeaderSize + " bytes.");
|
||||
}
|
||||
|
||||
char next = (char) buffer.readByte();
|
||||
|
||||
switch (parseState) {
|
||||
case LINE_START:
|
||||
if (HttpConstants.CR == next) {
|
||||
if (buffer.isReadable()) {
|
||||
next = (char) buffer.readByte();
|
||||
if (HttpConstants.LF == next) {
|
||||
parseState = HeaderParseState.HEADERS_END;
|
||||
break loop;
|
||||
} else {
|
||||
// consume
|
||||
}
|
||||
} else {
|
||||
// not enough data
|
||||
break loop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (HttpConstants.LF == next) {
|
||||
parseState = HeaderParseState.HEADERS_END;
|
||||
break loop;
|
||||
}
|
||||
parseState = HeaderParseState.NAME_START;
|
||||
// FALL THROUGH
|
||||
case NAME_START:
|
||||
if (next != ' ' && next != '\t') {
|
||||
// reset StringBuilder so it can be used to store the header name
|
||||
sb.setLength(0);
|
||||
parseState = HeaderParseState.NAME_END;
|
||||
sb.append(next);
|
||||
}
|
||||
break;
|
||||
case NAME_END:
|
||||
if (next == ':') {
|
||||
// store current content of StringBuilder as header name and reset it
|
||||
// so it can be used to store the header name
|
||||
name = sb.toString();
|
||||
sb.setLength(0);
|
||||
|
||||
parseState = HeaderParseState.VALUE_START;
|
||||
} else if (next == ' ') {
|
||||
// store current content of StringBuilder as header name and reset it
|
||||
// so it can be used to store the header name
|
||||
name = sb.toString();
|
||||
sb.setLength(0);
|
||||
|
||||
parseState = HeaderParseState.COMMA_END;
|
||||
} else {
|
||||
sb.append(next);
|
||||
}
|
||||
break;
|
||||
case COMMA_END:
|
||||
if (next == ':') {
|
||||
parseState = HeaderParseState.VALUE_START;
|
||||
}
|
||||
break;
|
||||
case VALUE_START:
|
||||
if (next != ' ' && next != '\t') {
|
||||
parseState = HeaderParseState.VALUE_END;
|
||||
sb.append(next);
|
||||
}
|
||||
break;
|
||||
case VALUE_END:
|
||||
if (HttpConstants.CR == next) {
|
||||
// ignore CR and use LF to detect line delimiter
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3
|
||||
break;
|
||||
}
|
||||
if (HttpConstants.LF == next) {
|
||||
// need to check for multi line header value
|
||||
parseState = HeaderParseState.LINE_END;
|
||||
break;
|
||||
}
|
||||
sb.append(next);
|
||||
break;
|
||||
case LINE_END:
|
||||
if (next == '\t' || next == ' ') {
|
||||
// This is a multine line header
|
||||
// skip char and move on
|
||||
sb.append(next);
|
||||
parseState = HeaderParseState.VALUE_START;
|
||||
break;
|
||||
}
|
||||
|
||||
// remove trailing white spaces
|
||||
int end = findEndOfString(sb);
|
||||
if (end + 1 < sb.length()) {
|
||||
sb.setLength(end);
|
||||
}
|
||||
|
||||
String value = sb.toString();
|
||||
if (value.length() == 1 && value.charAt(0) == HttpConstants.CR) {
|
||||
headers.add(name, "");
|
||||
} else {
|
||||
headers.add(name, value);
|
||||
}
|
||||
|
||||
parseState = HeaderParseState.LINE_START;
|
||||
// unread one byte to process it in LINE_START
|
||||
buffer.readerIndex(buffer.readerIndex() - 1);
|
||||
// mark the reader index on each line start so we can preserve already parsed headers
|
||||
buffer.markReaderIndex();
|
||||
case HEADERS_END:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseState != HeaderParseState.HEADERS_END) {
|
||||
// not enough data try again later
|
||||
buffer.resetReaderIndex();
|
||||
return false;
|
||||
} else {
|
||||
// reset header size
|
||||
headerSize = 0;
|
||||
buffer.markReaderIndex();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
|
||||
headerSize = 0;
|
||||
StringBuilder line = readHeader(buffer);
|
||||
if (line == null) {
|
||||
// not enough data
|
||||
return null;
|
||||
}
|
||||
// this means we consumed the header completly
|
||||
String lastHeader = null;
|
||||
if (line.length() > 0) {
|
||||
buffer.markReaderIndex();
|
||||
|
||||
LastHttpContent trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
|
||||
final HttpHeaders headers = trailer.trailingHeaders();
|
||||
headers.clear();
|
||||
|
||||
LastHttpContent trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
|
||||
do {
|
||||
char firstChar = line.charAt(0);
|
||||
if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
|
||||
List<String> current = headers.getAll(lastHeader);
|
||||
List<String> current = trailer.trailingHeaders().getAll(lastHeader);
|
||||
if (!current.isEmpty()) {
|
||||
int lastPos = current.size() - 1;
|
||||
String newString = current.get(lastPos) + line.toString().trim();
|
||||
@ -792,17 +645,12 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
if (!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
|
||||
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
|
||||
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
|
||||
headers.add(name, header[1]);
|
||||
trailer.trailingHeaders().add(name, header[1]);
|
||||
}
|
||||
lastHeader = name;
|
||||
}
|
||||
|
||||
line = readHeader(buffer);
|
||||
if (line == null) {
|
||||
// not enough data
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
} while (line.length() > 0);
|
||||
|
||||
return trailer;
|
||||
@ -815,26 +663,22 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
StringBuilder sb = this.sb;
|
||||
sb.setLength(0);
|
||||
int headerSize = this.headerSize;
|
||||
buffer.markReaderIndex();
|
||||
while (buffer.isReadable()) {
|
||||
|
||||
loop:
|
||||
for (;;) {
|
||||
char nextByte = (char) buffer.readByte();
|
||||
headerSize ++;
|
||||
|
||||
switch (nextByte) {
|
||||
case HttpConstants.CR:
|
||||
if (!buffer.isReadable()) {
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
nextByte = (char) buffer.readByte();
|
||||
headerSize ++;
|
||||
if (nextByte == HttpConstants.LF) {
|
||||
this.headerSize = headerSize;
|
||||
return sb;
|
||||
break loop;
|
||||
}
|
||||
break;
|
||||
case HttpConstants.LF:
|
||||
this.headerSize = headerSize;
|
||||
return sb;
|
||||
break loop;
|
||||
}
|
||||
|
||||
// Abort decoding if the header part is too large.
|
||||
@ -850,12 +694,13 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
|
||||
sb.append(nextByte);
|
||||
}
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
|
||||
this.headerSize = headerSize;
|
||||
return sb;
|
||||
}
|
||||
|
||||
protected abstract boolean isDecodingRequest();
|
||||
protected abstract HttpMessage createMessage(String first, String second, String third) throws Exception;
|
||||
protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
|
||||
protected abstract HttpMessage createInvalidMessage();
|
||||
|
||||
private static int getChunkSize(String hex) {
|
||||
@ -875,14 +720,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
StringBuilder sb = this.sb;
|
||||
sb.setLength(0);
|
||||
int lineLength = 0;
|
||||
buffer.markReaderIndex();
|
||||
|
||||
while (buffer.isReadable()) {
|
||||
while (true) {
|
||||
byte nextByte = buffer.readByte();
|
||||
if (nextByte == HttpConstants.CR) {
|
||||
if (!buffer.isReadable()) {
|
||||
break;
|
||||
}
|
||||
nextByte = buffer.readByte();
|
||||
if (nextByte == HttpConstants.LF) {
|
||||
return sb;
|
||||
@ -903,110 +743,29 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
sb.append((char) nextByte);
|
||||
}
|
||||
}
|
||||
buffer.resetReaderIndex();
|
||||
// TODO: Optimize this
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum InitialLineState {
|
||||
START_A,
|
||||
END_A,
|
||||
START_B,
|
||||
END_B,
|
||||
START_C,
|
||||
END_C
|
||||
}
|
||||
private static String[] splitInitialLine(StringBuilder sb) {
|
||||
int aStart;
|
||||
int aEnd;
|
||||
int bStart;
|
||||
int bEnd;
|
||||
int cStart;
|
||||
int cEnd;
|
||||
|
||||
private HttpMessage splitInitialLine(StringBuilder sb, ByteBuf buffer, int maxLineLength) throws Exception {
|
||||
InitialLineState state = InitialLineState.START_A;
|
||||
int aStart = 0;
|
||||
int aEnd = 0;
|
||||
int bStart = 0;
|
||||
int bEnd = 0;
|
||||
int cStart = 0;
|
||||
int cEnd = 0;
|
||||
aStart = findNonWhitespace(sb, 0);
|
||||
aEnd = findWhitespace(sb, aStart);
|
||||
|
||||
sb.setLength(0);
|
||||
int index = 0;
|
||||
int lineLength = 0;
|
||||
bStart = findNonWhitespace(sb, aEnd);
|
||||
bEnd = findWhitespace(sb, bStart);
|
||||
|
||||
buffer.markReaderIndex();
|
||||
cStart = findNonWhitespace(sb, bEnd);
|
||||
cEnd = findEndOfString(sb);
|
||||
|
||||
while (buffer.isReadable()) {
|
||||
char next = (char) buffer.readByte();
|
||||
|
||||
switch (state) {
|
||||
case START_A:
|
||||
case START_B:
|
||||
case START_C:
|
||||
if (!Character.isWhitespace(next)) {
|
||||
if (state == InitialLineState.START_A) {
|
||||
aStart = index;
|
||||
state = InitialLineState.END_A;
|
||||
} else if (state == InitialLineState.START_B) {
|
||||
bStart = index;
|
||||
state = InitialLineState.END_B;
|
||||
} else {
|
||||
cStart = index;
|
||||
state = InitialLineState.END_C;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case END_A:
|
||||
case END_B:
|
||||
if (Character.isWhitespace(next)) {
|
||||
if (state == InitialLineState.END_A) {
|
||||
aEnd = index;
|
||||
state = InitialLineState.START_B;
|
||||
} else {
|
||||
bEnd = index;
|
||||
state = InitialLineState.START_C;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case END_C:
|
||||
if (HttpConstants.CR == next) {
|
||||
if (!buffer.isReadable()) {
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
next = (char) buffer.readByte();
|
||||
if (HttpConstants.LF == next) {
|
||||
cEnd = index;
|
||||
return createMessage(
|
||||
return new String[] {
|
||||
sb.substring(aStart, aEnd),
|
||||
sb.substring(bStart, bEnd),
|
||||
cStart < cEnd? sb.substring(cStart, cEnd) : "");
|
||||
}
|
||||
index ++;
|
||||
|
||||
break;
|
||||
}
|
||||
if (HttpConstants.LF == next) {
|
||||
cEnd = index;
|
||||
return createMessage(
|
||||
sb.substring(aStart, aEnd),
|
||||
sb.substring(bStart, bEnd),
|
||||
cStart < cEnd? sb.substring(cStart, cEnd) : "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lineLength >= maxLineLength) {
|
||||
// TODO: Respond with Bad Request and discard the traffic
|
||||
// or close the connection.
|
||||
// No need to notify the upstream handlers - just log.
|
||||
// If decoding a response, just throw an exception.
|
||||
throw new TooLongFrameException(
|
||||
"An HTTP line is larger than " + maxLineLength +
|
||||
" bytes.");
|
||||
}
|
||||
lineLength ++;
|
||||
index ++;
|
||||
sb.append(next);
|
||||
}
|
||||
// reset index as we need to parse the line again once more data was received
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
cStart < cEnd? sb.substring(cStart, cEnd) : "" };
|
||||
}
|
||||
|
||||
private static String[] splitHeader(StringBuilder sb) {
|
||||
@ -1057,6 +816,16 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int findWhitespace(CharSequence sb, int offset) {
|
||||
int result;
|
||||
for (result = offset; result < sb.length(); result ++) {
|
||||
if (Character.isWhitespace(sb.charAt(result))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int findEndOfString(CharSequence sb) {
|
||||
int result;
|
||||
for (result = sb.length(); result > 0; result --) {
|
||||
|
@ -50,10 +50,6 @@ import io.netty.handler.codec.TooLongFrameException;
|
||||
* {@link HttpContent}s in your handler, insert {@link HttpObjectAggregator}
|
||||
* after this decoder in the {@link ChannelPipeline}.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code validateHeaders}</td>
|
||||
* <td>Specify if the headers should be validated during adding them for invalid chars.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*/
|
||||
public class HttpRequestDecoder extends HttpObjectDecoder {
|
||||
@ -71,26 +67,24 @@ public class HttpRequestDecoder extends HttpObjectDecoder {
|
||||
*/
|
||||
public HttpRequestDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
|
||||
this(maxInitialLineLength, maxHeaderSize, maxChunkSize, true);
|
||||
super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public HttpRequestDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createMessage(String first, String second, String third) throws Exception {
|
||||
protected HttpMessage createMessage(String[] initialLine) throws Exception {
|
||||
return new DefaultHttpRequest(
|
||||
HttpVersion.valueOf(third), HttpMethod.valueOf(first), second, validateHeaders);
|
||||
HttpVersion.valueOf(initialLine[2]),
|
||||
HttpMethod.valueOf(initialLine[0]), initialLine[1], validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createInvalidMessage() {
|
||||
return new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request");
|
||||
return new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request", validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,10 +51,6 @@ import io.netty.handler.codec.TooLongFrameException;
|
||||
* {@link HttpContent}s in your handler, insert {@link HttpObjectAggregator}
|
||||
* after this decoder in the {@link ChannelPipeline}.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code validateHeaders}</td>
|
||||
* <td>Specify if the headers should be validated during adding them for invalid chars.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h3>Decoding a response for a <tt>HEAD</tt> request</h3>
|
||||
@ -102,27 +98,24 @@ public class HttpResponseDecoder extends HttpObjectDecoder {
|
||||
*/
|
||||
public HttpResponseDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
|
||||
this(maxInitialLineLength, maxHeaderSize, maxChunkSize, true);
|
||||
super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public HttpResponseDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createMessage(String first, String second, String third) throws Exception {
|
||||
protected HttpMessage createMessage(String[] initialLine) {
|
||||
return new DefaultHttpResponse(
|
||||
HttpVersion.valueOf(first),
|
||||
new HttpResponseStatus(Integer.valueOf(second), third), validateHeaders);
|
||||
HttpVersion.valueOf(initialLine[0]),
|
||||
new HttpResponseStatus(Integer.valueOf(initialLine[1]), initialLine[2]), validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createInvalidMessage() {
|
||||
return new DefaultHttpResponse(HttpVersion.HTTP_1_0, UNKNOWN_STATUS);
|
||||
return new DefaultHttpResponse(HttpVersion.HTTP_1_0, UNKNOWN_STATUS, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,7 +63,12 @@ public abstract class RtspObjectDecoder extends HttpObjectDecoder {
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
protected RtspObjectDecoder(int maxInitialLineLength, int maxHeaderSize, int maxContentLength) {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength * 2, false, true);
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength * 2, false);
|
||||
}
|
||||
|
||||
protected RtspObjectDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxContentLength, boolean validateHeaders) {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength * 2, false, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,15 +65,20 @@ public class RtspRequestDecoder extends RtspObjectDecoder {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength);
|
||||
}
|
||||
|
||||
public RtspRequestDecoder(
|
||||
int maxInitialLineLength, int maxHeaderSize, int maxContentLength, boolean validateHeaders) {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createMessage(String first, String second, String third) throws Exception {
|
||||
return new DefaultHttpRequest(RtspVersions.valueOf(third),
|
||||
RtspMethods.valueOf(first), second);
|
||||
protected HttpMessage createMessage(String[] initialLine) throws Exception {
|
||||
return new DefaultHttpRequest(RtspVersions.valueOf(initialLine[2]),
|
||||
RtspMethods.valueOf(initialLine[0]), initialLine[1], validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createInvalidMessage() {
|
||||
return new DefaultHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, "/bad-request");
|
||||
return new DefaultHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, "/bad-request", validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -69,16 +69,21 @@ public class RtspResponseDecoder extends RtspObjectDecoder {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength);
|
||||
}
|
||||
|
||||
public RtspResponseDecoder(int maxInitialLineLength, int maxHeaderSize,
|
||||
int maxContentLength, boolean validateHeaders) {
|
||||
super(maxInitialLineLength, maxHeaderSize, maxContentLength, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createMessage(String first, String second, String third) throws Exception {
|
||||
protected HttpMessage createMessage(String[] initialLine) throws Exception {
|
||||
return new DefaultHttpResponse(
|
||||
RtspVersions.valueOf(first),
|
||||
new HttpResponseStatus(Integer.valueOf(second), third));
|
||||
RtspVersions.valueOf(initialLine[0]),
|
||||
new HttpResponseStatus(Integer.valueOf(initialLine[1]), initialLine[2]), validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpMessage createInvalidMessage() {
|
||||
return new DefaultHttpResponse(RtspVersions.RTSP_1_0, UNKNOWN_STATUS);
|
||||
return new DefaultHttpResponse(RtspVersions.RTSP_1_0, UNKNOWN_STATUS, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -133,28 +133,24 @@ public class HttpRequestDecoderTest {
|
||||
|
||||
// if header is done it should produce a HttpRequest
|
||||
boolean headerDone = a + amount == headerLength;
|
||||
Assert.assertEquals(headerDone, channel.writeInbound(Unpooled.wrappedBuffer(content, a, amount)));
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(content, a, amount));
|
||||
a += amount;
|
||||
}
|
||||
|
||||
for (int i = 8; i > 0; i--) {
|
||||
// Should produce HttpContent
|
||||
Assert.assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(content, content.length - i, 1)));
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(content, content.length - i, 1));
|
||||
}
|
||||
|
||||
HttpRequest req = (HttpRequest) channel.readInbound();
|
||||
Assert.assertNotNull(req);
|
||||
checkHeaders(req.headers());
|
||||
|
||||
for (int i = 8; i > 1; i--) {
|
||||
HttpContent c = (HttpContent) channel.readInbound();
|
||||
Assert.assertEquals(1, c.content().readableBytes());
|
||||
Assert.assertEquals(content[content.length - i], c.content().readByte());
|
||||
c.release();
|
||||
}
|
||||
LastHttpContent c = (LastHttpContent) channel.readInbound();
|
||||
Assert.assertEquals(1, c.content().readableBytes());
|
||||
Assert.assertEquals(content[content.length - 1], c.content().readByte());
|
||||
Assert.assertEquals(8, c.content().readableBytes());
|
||||
for (int i = 8; i > 1; i--) {
|
||||
Assert.assertEquals(content[content.length - i], c.content().readByte());
|
||||
}
|
||||
c.release();
|
||||
|
||||
Assert.assertFalse(channel.finish());
|
||||
|
@ -24,9 +24,11 @@ import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HttpResponseDecoderTest {
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void testResponseChunked() {
|
||||
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
|
||||
@ -108,6 +110,7 @@ public class HttpResponseDecoderTest {
|
||||
|
||||
assertNull(ch.readInbound());
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testLastResponseWithEmptyHeaderAndEmptyContent() {
|
||||
@ -268,22 +271,21 @@ public class HttpResponseDecoderTest {
|
||||
"Content-Length: 10\r\n" +
|
||||
"\r\n", CharsetUtil.US_ASCII));
|
||||
|
||||
HttpResponse res = (HttpResponse) ch.readInbound();
|
||||
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
|
||||
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
|
||||
byte[] data = new byte[10];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = (byte) i;
|
||||
}
|
||||
ch.writeInbound(Unpooled.wrappedBuffer(data, 0, data.length / 2));
|
||||
HttpContent content = (HttpContent) ch.readInbound();
|
||||
assertEquals(content.content().readableBytes(), 5);
|
||||
content.release();
|
||||
|
||||
ch.writeInbound(Unpooled.wrappedBuffer(data, 5, data.length / 2));
|
||||
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
|
||||
assertEquals(lastContent.content().readableBytes(), 5);
|
||||
lastContent.release();
|
||||
|
||||
HttpResponse res = (HttpResponse) ch.readInbound();
|
||||
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
|
||||
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
|
||||
|
||||
LastHttpContent content = (LastHttpContent) ch.readInbound();
|
||||
assertEquals(10, content.content().readableBytes());
|
||||
assertEquals(Unpooled.wrappedBuffer(data), content.content());
|
||||
content.release();
|
||||
|
||||
assertThat(ch.finish(), is(false));
|
||||
assertThat(ch.readInbound(), is(nullValue()));
|
||||
@ -309,28 +311,23 @@ public class HttpResponseDecoderTest {
|
||||
amount = header.length - a;
|
||||
}
|
||||
|
||||
// if header is done it should produce a HttpRequest
|
||||
boolean headerDone = a + amount == header.length;
|
||||
assertEquals(headerDone, ch.writeInbound(Unpooled.wrappedBuffer(header, a, amount)));
|
||||
ch.writeInbound(Unpooled.wrappedBuffer(header, a, amount));
|
||||
a += amount;
|
||||
}
|
||||
|
||||
HttpResponse res = (HttpResponse) ch.readInbound();
|
||||
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
|
||||
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
|
||||
|
||||
byte[] data = new byte[10];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = (byte) i;
|
||||
}
|
||||
ch.writeInbound(Unpooled.wrappedBuffer(data, 0, data.length / 2));
|
||||
HttpContent content = (HttpContent) ch.readInbound();
|
||||
assertEquals(content.content().readableBytes(), 5);
|
||||
content.release();
|
||||
|
||||
ch.writeInbound(Unpooled.wrappedBuffer(data, 5, data.length / 2));
|
||||
|
||||
HttpResponse res = (HttpResponse) ch.readInbound();
|
||||
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
|
||||
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
|
||||
|
||||
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
|
||||
assertEquals(lastContent.content().readableBytes(), 5);
|
||||
assertEquals(10, lastContent.content().readableBytes());
|
||||
assertEquals(Unpooled.wrappedBuffer(data), lastContent.content());
|
||||
lastContent.release();
|
||||
|
||||
assertThat(ch.finish(), is(false));
|
||||
|
Loading…
x
Reference in New Issue
Block a user