[#527] Add a new property to HttpMessage to help clarify its transfer encoding
- Add an enum: HttpTransferEncoding - consists of SINGLE, STREAMED, and CHUNKED - Add HttpMessage.transferEncoding - replaces is/setChunked()
This commit is contained in:
parent
c22b559dfa
commit
602f976e41
@ -31,7 +31,7 @@ public class DefaultHttpMessage implements HttpMessage {
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
private HttpVersion version;
|
||||
private ByteBuf content = Unpooled.EMPTY_BUFFER;
|
||||
private boolean chunked;
|
||||
private HttpTransferEncoding te = HttpTransferEncoding.SINGLE;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -61,19 +61,31 @@ public class DefaultHttpMessage implements HttpMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChunked() {
|
||||
if (chunked) {
|
||||
return true;
|
||||
} else {
|
||||
return HttpCodecUtil.isTransferEncodingChunked(this);
|
||||
}
|
||||
public HttpTransferEncoding getTransferEncoding() {
|
||||
return te;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChunked(boolean chunked) {
|
||||
this.chunked = chunked;
|
||||
if (chunked) {
|
||||
public void setTransferEncoding(HttpTransferEncoding te) {
|
||||
if (te == null) {
|
||||
throw new NullPointerException("te (transferEncoding)");
|
||||
}
|
||||
this.te = te;
|
||||
switch (te) {
|
||||
case SINGLE:
|
||||
HttpCodecUtil.removeTransferEncodingChunked(this);
|
||||
break;
|
||||
case STREAMED:
|
||||
HttpCodecUtil.removeTransferEncodingChunked(this);
|
||||
setContent(Unpooled.EMPTY_BUFFER);
|
||||
break;
|
||||
case CHUNKED:
|
||||
if (!HttpCodecUtil.isTransferEncodingChunked(this)) {
|
||||
addHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
|
||||
}
|
||||
removeHeader(HttpHeaders.Names.CONTENT_LENGTH);
|
||||
setContent(Unpooled.EMPTY_BUFFER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,10 +99,12 @@ public class DefaultHttpMessage implements HttpMessage {
|
||||
if (content == null) {
|
||||
content = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
if (content.readable() && isChunked()) {
|
||||
|
||||
if (!getTransferEncoding().isSingle() && content.readable()) {
|
||||
throw new IllegalArgumentException(
|
||||
"non-empty content disallowed if this.chunked == true");
|
||||
"non-empty content disallowed if this.transferEncoding != SINGLE");
|
||||
}
|
||||
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@ -134,7 +148,11 @@ public class DefaultHttpMessage implements HttpMessage {
|
||||
|
||||
@Override
|
||||
public ByteBuf getContent() {
|
||||
return content;
|
||||
if (getTransferEncoding() == HttpTransferEncoding.SINGLE) {
|
||||
return content;
|
||||
} else {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -145,8 +163,8 @@ public class DefaultHttpMessage implements HttpMessage {
|
||||
buf.append(getProtocolVersion().getText());
|
||||
buf.append(", keepAlive: ");
|
||||
buf.append(HttpHeaders.isKeepAlive(this));
|
||||
buf.append(", chunked: ");
|
||||
buf.append(isChunked());
|
||||
buf.append(", transferEncoding: ");
|
||||
buf.append(getTransferEncoding());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
appendHeaders(buf);
|
||||
|
@ -68,8 +68,8 @@ public class DefaultHttpRequest extends DefaultHttpMessage implements HttpReques
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append("(chunked: ");
|
||||
buf.append(isChunked());
|
||||
buf.append("(transferEncoding: ");
|
||||
buf.append(getTransferEncoding());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(getMethod().toString());
|
||||
|
@ -52,8 +52,8 @@ public class DefaultHttpResponse extends DefaultHttpMessage implements HttpRespo
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append("(chunked: ");
|
||||
buf.append(isChunked());
|
||||
buf.append("(transferEncoding: ");
|
||||
buf.append(getTransferEncoding());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(getProtocolVersion().getText());
|
||||
|
@ -26,7 +26,6 @@ import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
@ -128,29 +127,27 @@ public class HttpChunkAggregator extends MessageToMessageDecoder<Object, HttpMes
|
||||
ctx.write(CONTINUE.duplicate());
|
||||
}
|
||||
|
||||
if (m.isChunked()) {
|
||||
// A chunked message - remove 'Transfer-Encoding' header,
|
||||
// initialize the cumulative buffer, and wait for incoming chunks.
|
||||
List<String> encodings = m.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
encodings.remove(HttpHeaders.Values.CHUNKED);
|
||||
if (encodings.isEmpty()) {
|
||||
m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
}
|
||||
m.setChunked(false);
|
||||
switch (m.getTransferEncoding()) {
|
||||
case SINGLE:
|
||||
this.currentMessage = null;
|
||||
return m;
|
||||
case STREAMED:
|
||||
case CHUNKED:
|
||||
// A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
|
||||
m.setTransferEncoding(HttpTransferEncoding.SINGLE);
|
||||
m.setContent(Unpooled.compositeBuffer(maxCumulationBufferComponents));
|
||||
this.currentMessage = m;
|
||||
return null;
|
||||
} else {
|
||||
// Not a chunked message - pass through.
|
||||
this.currentMessage = null;
|
||||
return m;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
} else if (msg instanceof HttpChunk) {
|
||||
// Sanity check
|
||||
if (currentMessage == null) {
|
||||
throw new IllegalStateException(
|
||||
"received " + HttpChunk.class.getSimpleName() +
|
||||
" without " + HttpMessage.class.getSimpleName());
|
||||
" without " + HttpMessage.class.getSimpleName() +
|
||||
" or last message's transfer encoding was 'SINGLE'");
|
||||
}
|
||||
|
||||
// Merge the received chunk into the content of the current message.
|
||||
|
@ -92,7 +92,7 @@ public class HttpClientCodec extends CombinedChannelHandler {
|
||||
|
||||
if (failOnMissingResponse) {
|
||||
// check if the request is chunked if so do not increment
|
||||
if (msg instanceof HttpRequest && !((HttpRequest) msg).isChunked()) {
|
||||
if (msg instanceof HttpRequest && ((HttpMessage) msg).getTransferEncoding().isSingle()) {
|
||||
requestResponseCounter.incrementAndGet();
|
||||
} else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) {
|
||||
// increment as its the last chunk
|
||||
@ -127,8 +127,8 @@ public class HttpClientCodec extends CombinedChannelHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if its a HttpMessage and its not chunked
|
||||
if (msg instanceof HttpMessage && !((HttpMessage) msg).isChunked()) {
|
||||
// check if it's an HttpMessage and its transfer encoding is SINGLE.
|
||||
if (msg instanceof HttpMessage && ((HttpMessage) msg).getTransferEncoding().isSingle()) {
|
||||
requestResponseCounter.decrementAndGet();
|
||||
} else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) {
|
||||
requestResponseCounter.decrementAndGet();
|
||||
|
@ -154,7 +154,11 @@ final class HttpCodecUtil {
|
||||
static void removeTransferEncodingChunked(HttpMessage m) {
|
||||
List<String> values = m.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
values.remove(HttpHeaders.Values.CHUNKED);
|
||||
m.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, values);
|
||||
if (values.isEmpty()) {
|
||||
m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
} else {
|
||||
m.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, values);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isContentLengthSet(HttpMessage m) {
|
||||
|
@ -73,7 +73,8 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<Object,
|
||||
contentEncoding = HttpHeaders.Values.IDENTITY;
|
||||
}
|
||||
|
||||
boolean hasContent = m.isChunked() || m.getContent().readable();
|
||||
boolean hasContent =
|
||||
m.getTransferEncoding().isMultiple() || m.getContent().readable();
|
||||
if (hasContent && (decoder = newContentDecoder(contentEncoding)) != null) {
|
||||
// Decode the content and remove or replace the existing headers
|
||||
// so that the message looks like a decoded message.
|
||||
@ -81,7 +82,7 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<Object,
|
||||
HttpHeaders.Names.CONTENT_ENCODING,
|
||||
getTargetContentEncoding(contentEncoding));
|
||||
|
||||
if (!m.isChunked()) {
|
||||
if (m.getTransferEncoding().isSingle()) {
|
||||
ByteBuf content = m.getContent();
|
||||
// Decode the content
|
||||
ByteBuf newContent = Unpooled.buffer();
|
||||
|
@ -96,7 +96,7 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpMessa
|
||||
throw new IllegalStateException("cannot send more responses than requests");
|
||||
}
|
||||
|
||||
boolean hasContent = m.isChunked() || m.getContent().readable();
|
||||
boolean hasContent = m.getTransferEncoding().isMultiple() || m.getContent().readable();
|
||||
if (!hasContent) {
|
||||
return m;
|
||||
}
|
||||
@ -114,7 +114,7 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpMessa
|
||||
HttpHeaders.Names.CONTENT_ENCODING,
|
||||
result.getTargetContentEncoding());
|
||||
|
||||
if (!m.isChunked()) {
|
||||
if (m.getTransferEncoding().isSingle()) {
|
||||
ByteBuf content = m.getContent();
|
||||
// Encode the content.
|
||||
ByteBuf newContent = Unpooled.buffer();
|
||||
|
@ -94,7 +94,8 @@ public interface HttpMessage {
|
||||
/**
|
||||
* Returns the content of this {@link HttpMessage}.
|
||||
*
|
||||
* If there is no content or {@link #isChunked()} returns {@code true},
|
||||
* If there is no content or {@link #getTransferEncoding()} returns
|
||||
* {@link HttpTransferEncoding#STREAMED} or {@link HttpTransferEncoding#CHUNKED},
|
||||
* an {@link Unpooled#EMPTY_BUFFER} is returned.
|
||||
*
|
||||
* @return A {@link ByteBuf} containing this {@link HttpMessage}'s content
|
||||
@ -171,43 +172,31 @@ public interface HttpMessage {
|
||||
void clearHeaders();
|
||||
|
||||
/**
|
||||
* Checks to see if this {@link HttpMessage} is broken into multiple "chunks"
|
||||
*
|
||||
* If this returns true, it means that this {@link HttpMessage}
|
||||
* actually has no content - The {@link HttpChunk}s (which are generated
|
||||
* by the {@link HttpMessageDecoder} consecutively) contain the actual content.
|
||||
* <p>
|
||||
* Please note that this method will keep returning {@code true} if the
|
||||
* {@code "Transfer-Encoding"} of this message is {@code "chunked"}, even if
|
||||
* you attempt to override this property by calling {@link #setChunked(boolean)}
|
||||
* with {@code false}.
|
||||
* </p>
|
||||
*
|
||||
* @return True if this message is chunked, otherwise false
|
||||
* Returns the transfer encoding of this {@link HttpMessage}.
|
||||
* <ul>
|
||||
* <li>{@link HttpTransferEncoding#CHUNKED} - an HTTP message whose {@code "Transfer-Encoding"}
|
||||
* is {@code "chunked"}.</li>
|
||||
* <li>{@link HttpTransferEncoding#STREAMED} - an HTTP message which is not chunked, but
|
||||
* is followed by {@link HttpChunk}s that represent its content. {@link #getContent()}
|
||||
* returns an empty buffer.</li>
|
||||
* <li>{@link HttpTransferEncoding#SINGLE} - a self-contained HTTP message which is not chunked
|
||||
* and {@link #getContent()} returns the full content.</li>
|
||||
* </ul>
|
||||
*/
|
||||
boolean isChunked();
|
||||
HttpTransferEncoding getTransferEncoding();
|
||||
|
||||
/**
|
||||
* Sets the boolean defining if this {@link HttpMessage} is chunked.
|
||||
*
|
||||
* <p>
|
||||
* If this is set to true, it means that this initial {@link HttpMessage}
|
||||
* does not contain any content - The content is contained by multiple
|
||||
* {@link HttpChunk}s, which are generated by the {@link HttpMessageDecoder}
|
||||
* consecutively.
|
||||
*
|
||||
* Because of this, the content of this {@link HttpMessage} becomes
|
||||
* {@link Unpooled#EMPTY_BUFFER}
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Even if this method is called with {@code false}, {@link #isChunked()}
|
||||
* will keep returning {@code true} if the {@code "Transfer-Encoding"} of
|
||||
* this message is {@code "chunked"}.
|
||||
* </p>
|
||||
*
|
||||
* @param chunked True if this message is to be delivered in chunks,
|
||||
* otherwise false.
|
||||
* Sets the transfer encoding of this {@link HttpMessage}.
|
||||
* <ul>
|
||||
* <li>If set to {@link HttpTransferEncoding#CHUNKED}, the {@code "Transfer-Encoding: chunked"}
|
||||
* header is set and the {@code "Content-Length"} header and the content of this message are
|
||||
* removed automatically.</li>
|
||||
* <li>If set to {@link HttpTransferEncoding#STREAMED}, the {@code "Transfer-Encoding: chunked"}
|
||||
* header and the content of this message are removed automatically.</li>
|
||||
* <li>If set to {@link HttpTransferEncoding#SINGLE}, the {@code "Transfer-Encoding: chunked"}
|
||||
* header is removed automatically.</li>
|
||||
* </ul>
|
||||
* For more information about what {@link HttpTransferEncoding} means, see {@link #getTransferEncoding()}.
|
||||
*/
|
||||
void setChunked(boolean chunked);
|
||||
void setTransferEncoding(HttpTransferEncoding te);
|
||||
}
|
||||
|
@ -191,15 +191,10 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<Object, HttpMe
|
||||
State nextState = readHeaders(buffer);
|
||||
checkpoint(nextState);
|
||||
if (nextState == State.READ_CHUNK_SIZE) {
|
||||
// Chunked encoding
|
||||
message.setChunked(true);
|
||||
// Generate HttpMessage first. HttpChunks will follow.
|
||||
// Chunked encoding - generate HttpMessage first. HttpChunks will follow.
|
||||
return message;
|
||||
} else if (nextState == State.SKIP_CONTROL_CHARS) {
|
||||
// No content is expected.
|
||||
// Remove the headers which are not supposed to be present not
|
||||
// to confuse subsequent handlers.
|
||||
message.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
return message;
|
||||
} else {
|
||||
long contentLength = HttpHeaders.getContentLength(message, -1);
|
||||
@ -213,7 +208,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<Object, HttpMe
|
||||
if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
|
||||
// Generate HttpMessage first. HttpChunks will follow.
|
||||
checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
|
||||
message.setChunked(true);
|
||||
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
|
||||
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT_AS_CHUNKS
|
||||
// state reads data chunk by chunk.
|
||||
chunkSize = HttpHeaders.getContentLength(message, -1);
|
||||
@ -224,7 +219,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<Object, HttpMe
|
||||
if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
|
||||
// Generate HttpMessage first. HttpChunks will follow.
|
||||
checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
|
||||
message.setChunked(true);
|
||||
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
|
||||
return message;
|
||||
}
|
||||
break;
|
||||
@ -240,8 +235,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<Object, HttpMe
|
||||
if (toRead > maxChunkSize) {
|
||||
toRead = maxChunkSize;
|
||||
}
|
||||
if (!message.isChunked()) {
|
||||
message.setChunked(true);
|
||||
|
||||
if (message.getTransferEncoding() != HttpTransferEncoding.STREAMED) {
|
||||
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
|
||||
return new Object[] {message, new DefaultHttpChunk(buffer.readBytes(toRead))};
|
||||
} else {
|
||||
return new DefaultHttpChunk(buffer.readBytes(toRead));
|
||||
@ -464,8 +460,8 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<Object, HttpMe
|
||||
}
|
||||
contentRead += toRead;
|
||||
if (length < contentRead) {
|
||||
if (!message.isChunked()) {
|
||||
message.setChunked(true);
|
||||
if (message.getTransferEncoding() != HttpTransferEncoding.STREAMED) {
|
||||
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
|
||||
return new Object[] {message, new DefaultHttpChunk(read(buffer, toRead))};
|
||||
} else {
|
||||
return new DefaultHttpChunk(read(buffer, toRead));
|
||||
@ -533,14 +529,10 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<Object, HttpMe
|
||||
State nextState;
|
||||
|
||||
if (isContentAlwaysEmpty(message)) {
|
||||
message.setTransferEncoding(HttpTransferEncoding.SINGLE);
|
||||
nextState = State.SKIP_CONTROL_CHARS;
|
||||
} else if (message.isChunked()) {
|
||||
// HttpMessage.isChunked() returns true when either:
|
||||
// 1) HttpMessage.setChunked(true) was called or
|
||||
// 2) 'Transfer-Encoding' is 'chunked'.
|
||||
// Because this decoder did not call HttpMessage.setChunked(true)
|
||||
// yet, HttpMessage.isChunked() should return true only when
|
||||
// 'Transfer-Encoding' is 'chunked'.
|
||||
} else if (HttpCodecUtil.isTransferEncodingChunked(message)) {
|
||||
message.setTransferEncoding(HttpTransferEncoding.CHUNKED);
|
||||
nextState = State.READ_CHUNK_SIZE;
|
||||
} else if (HttpHeaders.getContentLength(message, -1) >= 0) {
|
||||
nextState = State.READ_FIXED_LENGTH_CONTENT;
|
||||
|
@ -21,11 +21,8 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -47,7 +44,7 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
private static final ByteBuf LAST_CHUNK =
|
||||
copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII);
|
||||
|
||||
private boolean transferEncodingChunked;
|
||||
private HttpTransferEncoding lastTE;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -64,26 +61,14 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
|
||||
if (msg instanceof HttpMessage) {
|
||||
HttpMessage m = (HttpMessage) msg;
|
||||
boolean contentMustBeEmpty;
|
||||
if (m.isChunked()) {
|
||||
// if Content-Length is set then the message can't be HTTP chunked
|
||||
if (HttpCodecUtil.isContentLengthSet(m)) {
|
||||
contentMustBeEmpty = false;
|
||||
transferEncodingChunked = false;
|
||||
HttpCodecUtil.removeTransferEncodingChunked(m);
|
||||
} else {
|
||||
// check if the Transfer-Encoding is set to chunked already.
|
||||
// if not add the header to the message
|
||||
if (!HttpCodecUtil.isTransferEncodingChunked(m)) {
|
||||
m.addHeader(Names.TRANSFER_ENCODING, Values.CHUNKED);
|
||||
}
|
||||
contentMustBeEmpty = true;
|
||||
transferEncodingChunked = true;
|
||||
}
|
||||
} else {
|
||||
transferEncodingChunked = contentMustBeEmpty = HttpCodecUtil.isTransferEncodingChunked(m);
|
||||
}
|
||||
HttpTransferEncoding te = m.getTransferEncoding();
|
||||
lastTE = te;
|
||||
// Calling setTransferEncoding() will sanitize the headers and the content.
|
||||
// For example, it will remove the cases such as 'Transfer-Encoding' and 'Content-Length'
|
||||
// coexist. It also removes the content if the transferEncoding is not SINGLE.
|
||||
m.setTransferEncoding(te);
|
||||
|
||||
// Encode the message.
|
||||
out.markWriterIndex();
|
||||
encodeInitialLine(out, m);
|
||||
encodeHeaders(out, m);
|
||||
@ -91,20 +76,25 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
out.writeByte(LF);
|
||||
|
||||
ByteBuf content = m.getContent();
|
||||
if (content.readable()) {
|
||||
if (contentMustBeEmpty) {
|
||||
out.resetWriterIndex();
|
||||
throw new IllegalArgumentException(
|
||||
"HttpMessage.content must be empty if Transfer-Encoding is chunked.");
|
||||
} else {
|
||||
out.writeBytes(content, content.readerIndex(), content.readableBytes());
|
||||
}
|
||||
}
|
||||
out.writeBytes(content, content.readerIndex(), content.readableBytes());
|
||||
} else if (msg instanceof HttpChunk) {
|
||||
HttpChunk chunk = (HttpChunk) msg;
|
||||
if (transferEncodingChunked) {
|
||||
HttpTransferEncoding te = lastTE;
|
||||
if (te == null) {
|
||||
throw new IllegalArgumentException("HttpChunk must follow an HttpMessage.");
|
||||
}
|
||||
|
||||
switch (te) {
|
||||
case SINGLE:
|
||||
throw new IllegalArgumentException(
|
||||
"The transfer encoding of the last encoded HttpMessage is SINGLE.");
|
||||
case STREAMED: {
|
||||
ByteBuf content = chunk.getContent();
|
||||
out.writeBytes(content, content.readerIndex(), content.readableBytes());
|
||||
break;
|
||||
}
|
||||
case CHUNKED:
|
||||
if (chunk.isLast()) {
|
||||
transferEncodingChunked = false;
|
||||
if (chunk instanceof HttpChunkTrailer) {
|
||||
out.writeByte((byte) '0');
|
||||
out.writeByte(CR);
|
||||
@ -125,11 +115,6 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
out.writeByte(CR);
|
||||
out.writeByte(LF);
|
||||
}
|
||||
} else {
|
||||
if (!chunk.isLast()) {
|
||||
ByteBuf chunkContent = chunk.getContent();
|
||||
out.writeBytes(chunkContent, chunkContent.readerIndex(), chunkContent.readableBytes());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedMessageTypeException(msg, HttpMessage.class, HttpChunk.class);
|
||||
@ -137,27 +122,18 @@ public abstract class HttpMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
}
|
||||
|
||||
private static void encodeHeaders(ByteBuf buf, HttpMessage message) {
|
||||
try {
|
||||
for (Map.Entry<String, String> h: message.getHeaders()) {
|
||||
encodeHeader(buf, h.getKey(), h.getValue());
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw (Error) new Error().initCause(e);
|
||||
for (Map.Entry<String, String> h: message.getHeaders()) {
|
||||
encodeHeader(buf, h.getKey(), h.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static void encodeTrailingHeaders(ByteBuf buf, HttpChunkTrailer trailer) {
|
||||
try {
|
||||
for (Map.Entry<String, String> h: trailer.getHeaders()) {
|
||||
encodeHeader(buf, h.getKey(), h.getValue());
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw (Error) new Error().initCause(e);
|
||||
for (Map.Entry<String, String> h: trailer.getHeaders()) {
|
||||
encodeHeader(buf, h.getKey(), h.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static void encodeHeader(ByteBuf buf, String header, String value)
|
||||
throws UnsupportedEncodingException {
|
||||
private static void encodeHeader(ByteBuf buf, String header, String value) {
|
||||
buf.writeBytes(header.getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeByte(COLON);
|
||||
buf.writeByte(SP);
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
public enum HttpTransferEncoding {
|
||||
CHUNKED(false),
|
||||
STREAMED(false),
|
||||
SINGLE(true);
|
||||
|
||||
private final boolean single;
|
||||
|
||||
private HttpTransferEncoding(boolean single) {
|
||||
this.single = single;
|
||||
}
|
||||
|
||||
public boolean isMultiple() {
|
||||
return !single;
|
||||
}
|
||||
|
||||
public boolean isSingle() {
|
||||
return single;
|
||||
}
|
||||
}
|
@ -218,7 +218,7 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object, Object> {
|
||||
|
||||
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
|
||||
throws Exception {
|
||||
boolean chunked = httpMessage.isChunked();
|
||||
boolean chunked = httpMessage.getTransferEncoding().isMultiple();
|
||||
|
||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamId(httpMessage);
|
||||
@ -286,7 +286,7 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<Object, Object> {
|
||||
|
||||
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
|
||||
throws Exception {
|
||||
boolean chunked = httpResponse.isChunked();
|
||||
boolean chunked = httpResponse.getTransferEncoding().isMultiple();
|
||||
|
||||
// Get the Stream-ID from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamId(httpResponse);
|
||||
|
@ -38,7 +38,7 @@ public class HttpChunkAggregatorTest {
|
||||
|
||||
HttpMessage message = new DefaultHttpMessage(HttpVersion.HTTP_1_1);
|
||||
HttpHeaders.setHeader(message, "X-Test", true);
|
||||
message.setChunked(true);
|
||||
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
|
||||
HttpChunk chunk1 = new DefaultHttpChunk(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||
HttpChunk chunk2 = new DefaultHttpChunk(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||
HttpChunk chunk3 = new DefaultHttpChunk(Unpooled.EMPTY_BUFFER);
|
||||
@ -59,7 +59,7 @@ public class HttpChunkAggregatorTest {
|
||||
|
||||
}
|
||||
|
||||
private void checkContentBuffer(HttpMessage aggregatedMessage) {
|
||||
private static void checkContentBuffer(HttpMessage aggregatedMessage) {
|
||||
CompositeByteBuf buffer = (CompositeByteBuf) aggregatedMessage.getContent();
|
||||
assertEquals(2, buffer.numComponents());
|
||||
List<ByteBuf> buffers = buffer.decompose(0, buffer.capacity());
|
||||
@ -76,7 +76,7 @@ public class HttpChunkAggregatorTest {
|
||||
EmbeddedMessageChannel embedder = new EmbeddedMessageChannel(aggr);
|
||||
HttpMessage message = new DefaultHttpMessage(HttpVersion.HTTP_1_1);
|
||||
HttpHeaders.setHeader(message, "X-Test", true);
|
||||
message.setChunked(true);
|
||||
message.setTransferEncoding(HttpTransferEncoding.CHUNKED);
|
||||
HttpChunk chunk1 = new DefaultHttpChunk(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||
HttpChunk chunk2 = new DefaultHttpChunk(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||
HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
|
||||
@ -107,7 +107,7 @@ public class HttpChunkAggregatorTest {
|
||||
HttpChunkAggregator aggr = new HttpChunkAggregator(4);
|
||||
EmbeddedMessageChannel embedder = new EmbeddedMessageChannel(aggr);
|
||||
HttpMessage message = new DefaultHttpMessage(HttpVersion.HTTP_1_1);
|
||||
message.setChunked(true);
|
||||
message.setTransferEncoding(HttpTransferEncoding.STREAMED);
|
||||
HttpChunk chunk1 = new DefaultHttpChunk(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||
HttpChunk chunk2 = new DefaultHttpChunk(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||
assertFalse(embedder.writeInbound(message));
|
||||
|
@ -24,7 +24,7 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpServerCodecTest {
|
||||
|
||||
|
||||
/**
|
||||
* Testcase for https://github.com/netty/netty/issues/433
|
||||
*/
|
||||
@ -45,8 +45,7 @@ public class HttpServerCodecTest {
|
||||
decoderEmbedder.finish();
|
||||
|
||||
HttpMessage httpMessage = (HttpMessage) decoderEmbedder.readInbound();
|
||||
Assert.assertTrue(httpMessage.isChunked());
|
||||
|
||||
Assert.assertSame(HttpTransferEncoding.STREAMED, httpMessage.getTransferEncoding());
|
||||
|
||||
boolean empty = true;
|
||||
int totalBytesPolled = 0;
|
||||
|
@ -44,7 +44,7 @@ public class HttpSnoopClientHandler extends ChannelInboundMessageHandlerAdapter<
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
if (response.isChunked()) {
|
||||
if (response.getTransferEncoding().isMultiple()) {
|
||||
readingChunks = true;
|
||||
System.out.println("CHUNKED CONTENT {");
|
||||
} else {
|
||||
|
@ -84,7 +84,7 @@ public class HttpSnoopServerHandler extends ChannelInboundMessageHandlerAdapter<
|
||||
buf.append("\r\n");
|
||||
}
|
||||
|
||||
if (request.isChunked()) {
|
||||
if (request.getTransferEncoding().isMultiple()) {
|
||||
readingChunks = true;
|
||||
} else {
|
||||
ByteBuf content = request.getContent();
|
||||
|
Loading…
Reference in New Issue
Block a user