From 2e98566916fae22c9dd78eb8ce5f04d87076d691 Mon Sep 17 00:00:00 2001
From: Trustin Lee
Date: Wed, 4 Jun 2014 18:34:57 +0900
Subject: [PATCH] Introduce MessageAggregator and DecoderResultProvider
Motivation:
We have different message aggregator implementations for different
protocols, but they are very similar with each other. They all stems
from HttpObjectAggregator. If we provide an abstract class that provide
generic message aggregation functionality, we will remove their code
duplication.
Modifications:
- Add MessageAggregator which provides generic message aggregation
- Reimplement all existing aggregators using MessageAggregator
- Add DecoderResultProvider interface and extend it wherever possible so
that MessageAggregator respects the state of the decoded message
Result:
Less code duplication
---
.../codec/http/ComposedLastHttpContent.java | 2 +-
.../codec/http/DefaultHttpContent.java | 2 +-
.../handler/codec/http/DefaultHttpObject.java | 2 +-
.../codec/http/DefaultHttpRequest.java | 2 +-
.../codec/http/DefaultHttpResponse.java | 4 +-
.../netty/handler/codec/http/HttpHeaders.java | 13 +-
.../netty/handler/codec/http/HttpObject.java | 15 +-
.../codec/http/HttpObjectAggregator.java | 320 ++++----------
.../codec/http/HttpServerUpgradeHandler.java | 41 +-
.../handler/codec/http/LastHttpContent.java | 2 +-
.../multipart/HttpPostRequestEncoder.java | 4 +-
.../websocketx/WebSocketFrameAggregator.java | 134 +++---
.../codec/http/HttpInvalidMessageTest.java | 12 +-
.../codec/http/HttpResponseDecoderTest.java | 6 +-
.../AbstractMemcacheObjectAggregator.java | 100 ++---
.../BinaryMemcacheObjectAggregator.java | 118 +----
.../stomp/DefaultStompContentSubframe.java | 2 +-
.../stomp/DefaultStompHeadersSubframe.java | 2 +-
.../handler/codec/stomp/StompSubframe.java | 15 +-
.../codec/stomp/StompSubframeAggregator.java | 131 ++----
.../handler/codec/DecoderResultProvider.java | 33 ++
.../codec/MessageAggregationException.java | 39 ++
.../handler/codec/MessageAggregator.java | 408 ++++++++++++++++++
.../file/HttpStaticFileServerHandler.java | 2 +-
.../http/snoop/HttpSnoopServerHandler.java | 4 +-
.../server/WebSocketServerHandler.java | 2 +-
.../autobahn/AutobahnServerHandler.java | 10 +-
27 files changed, 745 insertions(+), 680 deletions(-)
create mode 100644 codec/src/main/java/io/netty/handler/codec/DecoderResultProvider.java
create mode 100644 codec/src/main/java/io/netty/handler/codec/MessageAggregationException.java
create mode 100644 codec/src/main/java/io/netty/handler/codec/MessageAggregator.java
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/ComposedLastHttpContent.java b/codec-http/src/main/java/io/netty/handler/codec/http/ComposedLastHttpContent.java
index f38687efd5..162805246a 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/ComposedLastHttpContent.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/ComposedLastHttpContent.java
@@ -70,7 +70,7 @@ final class ComposedLastHttpContent implements LastHttpContent {
}
@Override
- public DecoderResult getDecoderResult() {
+ public DecoderResult decoderResult() {
return result;
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpContent.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpContent.java
index b366cb43a8..18f6a6b794 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpContent.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpContent.java
@@ -92,6 +92,6 @@ public class DefaultHttpContent extends DefaultHttpObject implements HttpContent
@Override
public String toString() {
return StringUtil.simpleClassName(this) +
- "(data: " + content() + ", decoderResult: " + getDecoderResult() + ')';
+ "(data: " + content() + ", decoderResult: " + decoderResult() + ')';
}
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpObject.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpObject.java
index 11f3c23c55..24eade9be7 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpObject.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpObject.java
@@ -26,7 +26,7 @@ public class DefaultHttpObject implements HttpObject {
}
@Override
- public DecoderResult getDecoderResult() {
+ public DecoderResult decoderResult() {
return decoderResult;
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java
index 1c4078246c..8ca421f596 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java
@@ -95,7 +95,7 @@ public class DefaultHttpRequest extends DefaultHttpMessage implements HttpReques
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append("(decodeResult: ");
- buf.append(getDecoderResult());
+ buf.append(decoderResult());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append(getMethod());
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java
index 90f9a64dbc..fb5dd67250 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java
@@ -74,12 +74,12 @@ public class DefaultHttpResponse extends DefaultHttpMessage implements HttpRespo
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append("(decodeResult: ");
- buf.append(getDecoderResult());
+ buf.append(decoderResult());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append(getProtocolVersion().text());
buf.append(' ');
- buf.append(getStatus().toString());
+ buf.append(getStatus());
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java
index b27f7e772a..f9b6a5fd9f 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java
@@ -36,7 +36,7 @@ import static io.netty.handler.codec.http.HttpConstants.*;
*/
public abstract class HttpHeaders implements Iterable> {
- private static final byte[] HEADER_SEPERATOR = { HttpConstants.COLON, HttpConstants.SP };
+ private static final byte[] HEADER_SEPERATOR = { COLON, SP };
private static final byte[] CRLF = { CR, LF };
public static final HttpHeaders EMPTY_HEADERS = new HttpHeaders() {
@@ -722,7 +722,7 @@ public abstract class HttpHeaders implements Iterable>
try {
return Integer.parseInt(value);
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException ignored) {
return defaultValue;
}
}
@@ -783,7 +783,7 @@ public abstract class HttpHeaders implements Iterable>
try {
return HttpHeaderDateFormat.get().parse(value);
- } catch (ParseException e) {
+ } catch (ParseException ignored) {
return defaultValue;
}
}
@@ -865,7 +865,7 @@ public abstract class HttpHeaders implements Iterable>
if (contentLength != null) {
try {
return Long.parseLong(contentLength);
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException ignored) {
return defaultValue;
}
}
@@ -1408,7 +1408,12 @@ public abstract class HttpHeaders implements Iterable>
if (headers == null) {
throw new NullPointerException("headers");
}
+
clear();
+ if (headers.isEmpty()) {
+ return this;
+ }
+
for (Map.Entry e: headers) {
add(e.getKey(), e.getValue());
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObject.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObject.java
index f28a785cb8..9168d4a6c8 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObject.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObject.java
@@ -15,17 +15,6 @@
*/
package io.netty.handler.codec.http;
-import io.netty.handler.codec.DecoderResult;
+import io.netty.handler.codec.DecoderResultProvider;
-public interface HttpObject {
- /**
- * Returns the result of decoding this message.
- */
- DecoderResult getDecoderResult();
-
- /**
- * Updates the result of decoding this message. This method is supposed to be invoked by {@link HttpObjectDecoder}.
- * Do not call this method unless you know what you are doing.
- */
- void setDecoderResult(DecoderResult result);
-}
+public interface HttpObject extends DecoderResultProvider { }
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectAggregator.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectAggregator.java
index f07e880e98..4343ab76f1 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectAggregator.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectAggregator.java
@@ -15,24 +15,18 @@
*/
package io.netty.handler.codec.http;
-import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
-import io.netty.handler.codec.DecoderResult;
-import io.netty.handler.codec.MessageToMessageDecoder;
+import io.netty.handler.codec.MessageAggregator;
import io.netty.handler.codec.TooLongFrameException;
-import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
-import java.util.List;
-
-import static io.netty.handler.codec.http.HttpHeaders.*;
-
/**
* A {@link ChannelHandler} that aggregates an {@link HttpMessage}
* and its following {@link HttpContent}s into a single {@link FullHttpRequest}
@@ -52,9 +46,8 @@ import static io.netty.handler.codec.http.HttpHeaders.*;
* Be aware that you need to have the {@link HttpResponseEncoder} or {@link HttpRequestEncoder}
* before the {@link HttpObjectAggregator} in the {@link ChannelPipeline}.
*/
-public class HttpObjectAggregator extends MessageToMessageDecoder {
-
- private static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
+public class HttpObjectAggregator
+ extends MessageAggregator {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpObjectAggregator.class);
@@ -64,16 +57,9 @@ public class HttpObjectAggregator extends MessageToMessageDecoder {
HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, Unpooled.EMPTY_BUFFER);
static {
- TOO_LARGE.headers().set(Names.CONTENT_LENGTH, 0);
+ TOO_LARGE.headers().set(HttpHeaders.Names.CONTENT_LENGTH, 0);
}
- private final int maxContentLength;
- private FullHttpMessage currentMessage;
- private boolean handlingOversizedMessage;
-
- private int maxCumulationBufferComponents = DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS;
- private ChannelHandlerContext ctx;
-
/**
* Creates a new instance.
*
@@ -84,195 +70,90 @@ public class HttpObjectAggregator extends MessageToMessageDecoder {
* will be called.
*/
public HttpObjectAggregator(int maxContentLength) {
- if (maxContentLength <= 0) {
- throw new IllegalArgumentException(
- "maxContentLength must be a positive integer: " +
- maxContentLength);
- }
- this.maxContentLength = maxContentLength;
+ super(maxContentLength);
}
- /**
- * Returns the maximum number of components in the cumulation buffer. If the number of
- * the components in the cumulation buffer exceeds this value, the components of the
- * cumulation buffer are consolidated into a single component, involving memory copies.
- * The default value of this property is {@value #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}.
- */
- public final int getMaxCumulationBufferComponents() {
- return maxCumulationBufferComponents;
+ @Override
+ protected boolean isStartMessage(HttpObject msg) throws Exception {
+ return msg instanceof HttpMessage;
}
- /**
- * Sets the maximum number of components in the cumulation buffer. If the number of
- * the components in the cumulation buffer exceeds this value, the components of the
- * cumulation buffer are consolidated into a single component, involving memory copies.
- * The default value of this property is {@value #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}
- * and its minimum allowed value is {@code 2}.
- */
- public final void setMaxCumulationBufferComponents(int maxCumulationBufferComponents) {
- if (maxCumulationBufferComponents < 2) {
- throw new IllegalArgumentException(
- "maxCumulationBufferComponents: " + maxCumulationBufferComponents +
- " (expected: >= 2)");
- }
+ @Override
+ protected boolean isContentMessage(HttpObject msg) throws Exception {
+ return msg instanceof HttpContent;
+ }
- if (ctx == null) {
- this.maxCumulationBufferComponents = maxCumulationBufferComponents;
+ @Override
+ protected boolean isLastContentMessage(HttpContent msg) throws Exception {
+ return msg instanceof LastHttpContent;
+ }
+
+ @Override
+ protected boolean isAggregated(HttpObject msg) throws Exception {
+ return msg instanceof FullHttpMessage;
+ }
+
+ @Override
+ protected boolean hasContentLength(HttpMessage start) throws Exception {
+ return HttpHeaders.isContentLengthSet(start);
+ }
+
+ @Override
+ protected long contentLength(HttpMessage start) throws Exception {
+ return HttpHeaders.getContentLength(start);
+ }
+
+ @Override
+ protected Object newContinueResponse(HttpMessage start) throws Exception {
+ if (HttpHeaders.is100ContinueExpected(start)) {
+ return CONTINUE;
} else {
- throw new IllegalStateException(
- "decoder properties cannot be changed once the decoder is added to a pipeline.");
+ return null;
}
}
@Override
- protected void decode(final ChannelHandlerContext ctx, HttpObject msg, List
+ *
+ * @param the type that covers both start message and content message
+ * @param the type of the start message
+ * @param the type of the content message (must be a subtype of {@link ByteBufHolder})
+ * @param the type of the aggregated message (must be a subtype of {@code S} and {@link ByteBufHolder})
+ */
+public abstract class MessageAggregator
+ extends MessageToMessageDecoder {
+
+ private static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
+
+ private final int maxContentLength;
+ private O currentMessage;
+ private boolean handlingOversizedMessage;
+
+ private int maxCumulationBufferComponents = DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS;
+ private ChannelHandlerContext ctx;
+ private ChannelFutureListener continueResponseWriteListener;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxContentLength
+ * the maximum length of the aggregated content.
+ * If the length of the aggregated content exceeds this value,
+ * {@link #handleOversizedMessage(ChannelHandlerContext, Object)} will be called.
+ */
+ protected MessageAggregator(int maxContentLength) {
+ validateMaxContentLength(maxContentLength);
+ this.maxContentLength = maxContentLength;
+ }
+
+ protected MessageAggregator(int maxContentLength, Class extends I> inboundMessageType) {
+ super(inboundMessageType);
+ validateMaxContentLength(maxContentLength);
+ this.maxContentLength = maxContentLength;
+ }
+
+ private static void validateMaxContentLength(int maxContentLength) {
+ if (maxContentLength <= 0) {
+ throw new IllegalArgumentException("maxContentLength must be a positive integer: " + maxContentLength);
+ }
+ }
+
+ @Override
+ public boolean acceptInboundMessage(Object msg) throws Exception {
+ // No need to match last and full types because they are subset of first and middle types.
+ if (!super.acceptInboundMessage(msg)) {
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ I in = (I) msg;
+
+ return (isContentMessage(in) || isStartMessage(in)) && !isAggregated(in);
+ }
+
+ /**
+ * Returns {@code true} if and only if the specified message is a start message. Typically, this method is
+ * implemented as a single {@code return} statement with {@code instanceof}:
+ *
+ * return msg instanceof MyStartMessage;
+ *
+ */
+ protected abstract boolean isStartMessage(I msg) throws Exception;
+
+ /**
+ * Returns {@code true} if and only if the specified message is a content message. Typically, this method is
+ * implemented as a single {@code return} statement with {@code instanceof}:
+ *
+ * return msg instanceof MyContentMessage;
+ *
+ */
+ protected abstract boolean isContentMessage(I msg) throws Exception;
+
+ /**
+ * Returns {@code true} if and only if the specified message is the last content message. Typically, this method is
+ * implemented as a single {@code return} statement with {@code instanceof}:
+ *
+ */
+ protected abstract boolean isLastContentMessage(C msg) throws Exception;
+
+ /**
+ * Returns {@code true} if and only if the specified message is already aggregated. If this method returns
+ * {@code true}, this handler will simply forward the message to the next handler as-is.
+ */
+ protected abstract boolean isAggregated(I msg) throws Exception;
+
+ /**
+ * Returns the maximum allowed length of the aggregated message.
+ */
+ public final int maxContentLength() {
+ return maxContentLength;
+ }
+
+ /**
+ * Returns the maximum number of components in the cumulation buffer. If the number of
+ * the components in the cumulation buffer exceeds this value, the components of the
+ * cumulation buffer are consolidated into a single component, involving memory copies.
+ * The default value of this property is {@value #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}.
+ */
+ public final int maxCumulationBufferComponents() {
+ return maxCumulationBufferComponents;
+ }
+
+ /**
+ * Sets the maximum number of components in the cumulation buffer. If the number of
+ * the components in the cumulation buffer exceeds this value, the components of the
+ * cumulation buffer are consolidated into a single component, involving memory copies.
+ * The default value of this property is {@value #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}
+ * and its minimum allowed value is {@code 2}.
+ */
+ public final void setMaxCumulationBufferComponents(int maxCumulationBufferComponents) {
+ if (maxCumulationBufferComponents < 2) {
+ throw new IllegalArgumentException(
+ "maxCumulationBufferComponents: " + maxCumulationBufferComponents +
+ " (expected: >= 2)");
+ }
+
+ if (ctx == null) {
+ this.maxCumulationBufferComponents = maxCumulationBufferComponents;
+ } else {
+ throw new IllegalStateException(
+ "decoder properties cannot be changed once the decoder is added to a pipeline.");
+ }
+ }
+
+ public final boolean isHandlingOversizedMessage() {
+ return handlingOversizedMessage;
+ }
+
+ protected final ChannelHandlerContext ctx() {
+ if (ctx == null) {
+ throw new IllegalStateException("not added to a pipeline yet");
+ }
+ return ctx;
+ }
+
+ @Override
+ protected void decode(final ChannelHandlerContext ctx, I msg, List out) throws Exception {
+ O currentMessage = this.currentMessage;
+
+ if (isStartMessage(msg)) {
+ handlingOversizedMessage = false;
+ if (currentMessage != null) {
+ throw new MessageAggregationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ S m = (S) msg;
+
+ // if content length is set, preemptively close if it's too large
+ if (hasContentLength(m)) {
+ if (contentLength(m) > maxContentLength) {
+ // handle oversized message
+ invokeHandleOversizedMessage(ctx, m);
+ return;
+ }
+ }
+
+ // Send the continue response if necessary (e.g. 'Expect: 100-continue' header)
+ Object continueResponse = newContinueResponse(m);
+ if (continueResponse != null) {
+ // Cache the write listener for reuse.
+ ChannelFutureListener listener = continueResponseWriteListener;
+ if (listener == null) {
+ continueResponseWriteListener = listener = new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ if (!future.isSuccess()) {
+ ctx.fireExceptionCaught(future.cause());
+ }
+ }
+ };
+ }
+ ctx.writeAndFlush(continueResponse).addListener(listener);
+ }
+
+ if (m instanceof DecoderResultProvider && !((DecoderResultProvider) m).decoderResult().isSuccess()) {
+ O aggregated;
+ if (m instanceof ByteBufHolder && ((ByteBufHolder) m).content().isReadable()) {
+ aggregated = beginAggregation(m, ((ByteBufHolder) m).content().retain());
+ } else {
+ aggregated = beginAggregation(m, Unpooled.EMPTY_BUFFER);
+ }
+ finishAggregation(aggregated);
+ out.add(aggregated);
+ this.currentMessage = null;
+ return;
+ }
+
+ // A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
+ CompositeByteBuf content = Unpooled.compositeBuffer(maxCumulationBufferComponents);
+ if (m instanceof ByteBufHolder) {
+ appendPartialContent(content, ((ByteBufHolder) m).content());
+ }
+ this.currentMessage = beginAggregation(m, content);
+
+ } else if (isContentMessage(msg)) {
+ @SuppressWarnings("unchecked")
+ final C m = (C) msg;
+ final ByteBuf partialContent = ((ByteBufHolder) msg).content();
+ final boolean isLastContentMessage = isLastContentMessage(m);
+ if (handlingOversizedMessage) {
+ if (isLastContentMessage) {
+ this.currentMessage = null;
+ }
+ // already detect the too long frame so just discard the content
+ return;
+ }
+
+ if (currentMessage == null) {
+ throw new MessageAggregationException();
+ }
+
+ // Merge the received chunk into the content of the current message.
+ CompositeByteBuf content = (CompositeByteBuf) currentMessage.content();
+
+ // Handle oversized message.
+ if (content.readableBytes() > maxContentLength - partialContent.readableBytes()) {
+ // By convention, full message type extends first message type.
+ @SuppressWarnings("unchecked")
+ S s = (S) currentMessage;
+ invokeHandleOversizedMessage(ctx, s);
+ return;
+ }
+
+ // Append the content of the chunk.
+ appendPartialContent(content, partialContent);
+
+ // Give the subtypes a chance to merge additional information such as trailing headers.
+ aggregate(currentMessage, m);
+
+ final boolean last;
+ if (m instanceof DecoderResultProvider) {
+ DecoderResult decoderResult = ((DecoderResultProvider) m).decoderResult();
+ if (!decoderResult.isSuccess()) {
+ if (currentMessage instanceof DecoderResultProvider) {
+ ((DecoderResultProvider) currentMessage).setDecoderResult(
+ DecoderResult.failure(decoderResult.cause()));
+ }
+ last = true;
+ } else {
+ last = isLastContentMessage;
+ }
+ } else {
+ last = isLastContentMessage;
+ }
+
+ if (last) {
+ finishAggregation(currentMessage);
+
+ // All done
+ out.add(currentMessage);
+ this.currentMessage = null;
+ }
+ } else {
+ throw new MessageAggregationException();
+ }
+ }
+
+ private static void appendPartialContent(CompositeByteBuf content, ByteBuf partialContent) {
+ if (partialContent.isReadable()) {
+ partialContent.retain();
+ content.addComponent(partialContent);
+ content.writerIndex(content.writerIndex() + partialContent.readableBytes());
+ }
+ }
+
+ /**
+ * Returns {@code true} if and only if the specified start message already contains the information about the
+ * length of the whole content.
+ */
+ protected abstract boolean hasContentLength(S start) throws Exception;
+
+ /**
+ * Retrieves the length of the whole content from the specified start message. This method is invoked only when
+ * {@link #hasContentLength(Object)} returned {@code true}.
+ */
+ protected abstract long contentLength(S start) throws Exception;
+
+ /**
+ * Returns the 'continue response' for the specified start message if necessary. For example, this method is
+ * useful to handle an HTTP 100-continue header.
+ *
+ * @return the 'continue response', or {@code null} if there's no message to send
+ */
+ protected abstract Object newContinueResponse(S start) throws Exception;
+
+ /**
+ * Creates a new aggregated message from the specified start message and the specified content. If the start
+ * message implements {@link ByteBufHolder}, its content is appended to the specified {@code content}.
+ * This aggregator will continue to append the received content to the specified {@code content}.
+ */
+ protected abstract O beginAggregation(S start, ByteBuf content) throws Exception;
+
+ /**
+ * Transfers the information provided by the specified content message to the specified aggregated message.
+ * Note that the content of the specified content message has been appended to the content of the specified
+ * aggregated message already, so that you don't need to. Use this method to transfer the additional information
+ * that the content message provides to {@code aggregated}.
+ */
+ protected void aggregate(O aggregated, C content) throws Exception { }
+
+ /**
+ * Invoked when the specified {@code aggregated} message is about to be passed to the next handler in the pipeline.
+ */
+ protected void finishAggregation(O aggregated) throws Exception { }
+
+ private void invokeHandleOversizedMessage(ChannelHandlerContext ctx, S oversized) throws Exception {
+ handlingOversizedMessage = true;
+ currentMessage = null;
+ try {
+ handleOversizedMessage(ctx, oversized);
+ } finally {
+ // Release the message in case it is a full one.
+ ReferenceCountUtil.release(oversized);
+ }
+ }
+
+ /**
+ * Invoked when an incoming request exceeds the maximum content length. The default behvaior is to trigger an
+ * {@code exceptionCaught()} event with a {@link TooLongFrameException}.
+ *
+ * @param ctx the {@link ChannelHandlerContext}
+ * @param oversized the accumulated message up to this point, whose type is {@code S} or {@code O}
+ */
+ protected void handleOversizedMessage(ChannelHandlerContext ctx, S oversized) throws Exception {
+ ctx.fireExceptionCaught(
+ new TooLongFrameException("content length exceeded " + maxContentLength() + " bytes."));
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ // release current message if it is not null as it may be a left-over
+ if (currentMessage != null) {
+ currentMessage.release();
+ currentMessage = null;
+ }
+
+ super.channelInactive(ctx);
+ }
+
+ @Override
+ public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ super.handlerRemoved(ctx);
+
+ // release current message if it is not null as it may be a left-over as there is not much more we can do in
+ // this case
+ if (currentMessage != null) {
+ currentMessage.release();
+ currentMessage = null;
+ }
+ }
+}
diff --git a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java
index 3bd35fc8a7..c38b3dbdd3 100644
--- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java
+++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java
@@ -111,7 +111,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler
}
private static void appendDecoderResult(StringBuilder buf, HttpObject o) {
- DecoderResult result = o.getDecoderResult();
+ DecoderResult result = o.decoderResult();
if (result.isSuccess()) {
return;
}
@@ -148,7 +148,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler
boolean keepAlive = isKeepAlive(request);
// Build the response object.
FullHttpResponse response = new DefaultFullHttpResponse(
- HTTP_1_1, currentObj.getDecoderResult().isSuccess()? OK : BAD_REQUEST,
+ HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,
Unpooled.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java
index bdbcfe2837..b4a9eb99fa 100644
--- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java
+++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java
@@ -64,7 +64,7 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
// Handle a bad request.
- if (!req.getDecoderResult().isSuccess()) {
+ if (!req.decoderResult().isSuccess()) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
return;
}
diff --git a/testsuite/src/test/java/io/netty/testsuite/websockets/autobahn/AutobahnServerHandler.java b/testsuite/src/test/java/io/netty/testsuite/websockets/autobahn/AutobahnServerHandler.java
index 5a2205f286..ef0a410322 100644
--- a/testsuite/src/test/java/io/netty/testsuite/websockets/autobahn/AutobahnServerHandler.java
+++ b/testsuite/src/test/java/io/netty/testsuite/websockets/autobahn/AutobahnServerHandler.java
@@ -71,7 +71,7 @@ public class AutobahnServerHandler extends ChannelHandlerAdapter {
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req)
throws Exception {
// Handle a bad request.
- if (!req.getDecoderResult().isSuccess()) {
+ if (!req.decoderResult().isSuccess()) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
req.release();
return;
@@ -106,11 +106,9 @@ public class AutobahnServerHandler extends ChannelHandlerAdapter {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) {
ctx.write(new PongWebSocketFrame(frame.isFinalFragment(), frame.rsv(), frame.content()));
- } else if (frame instanceof TextWebSocketFrame) {
- ctx.write(frame);
- } else if (frame instanceof BinaryWebSocketFrame) {
- ctx.write(frame);
- } else if (frame instanceof ContinuationWebSocketFrame) {
+ } else if (frame instanceof TextWebSocketFrame ||
+ frame instanceof BinaryWebSocketFrame ||
+ frame instanceof ContinuationWebSocketFrame) {
ctx.write(frame);
} else if (frame instanceof PongWebSocketFrame) {
frame.release();