2009-03-10 09:42:19 +01:00
|
|
|
/*
|
2012-06-04 22:31:44 +02:00
|
|
|
* Copyright 2012 The Netty Project
|
2009-03-10 09:42:19 +01:00
|
|
|
*
|
2011-12-09 06:18:34 +01:00
|
|
|
* 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:
|
2009-03-10 09:42:19 +01:00
|
|
|
*
|
2012-06-04 22:31:44 +02:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2009-03-10 09:42:19 +01:00
|
|
|
*
|
2009-08-28 09:15:49 +02:00
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
2011-12-09 06:18:34 +01:00
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
2009-08-28 09:15:49 +02:00
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
2009-03-10 09:42:19 +01:00
|
|
|
*/
|
2011-12-09 04:38:59 +01:00
|
|
|
package io.netty.handler.codec.http;
|
2009-03-10 09:42:19 +01:00
|
|
|
|
2012-06-10 04:08:43 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
2012-07-03 10:37:05 +02:00
|
|
|
import io.netty.buffer.CompositeByteBuf;
|
2012-06-11 10:02:00 +02:00
|
|
|
import io.netty.buffer.Unpooled;
|
2011-12-09 04:38:59 +01:00
|
|
|
import io.netty.channel.ChannelHandler;
|
2012-06-07 07:52:33 +02:00
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
2011-12-09 04:38:59 +01:00
|
|
|
import io.netty.channel.ChannelPipeline;
|
2012-09-28 08:26:38 +02:00
|
|
|
import io.netty.handler.codec.DecoderResult;
|
2012-05-20 07:19:11 +02:00
|
|
|
import io.netty.handler.codec.MessageToMessageDecoder;
|
2012-05-16 16:02:06 +02:00
|
|
|
import io.netty.handler.codec.TooLongFrameException;
|
2011-12-09 04:38:59 +01:00
|
|
|
import io.netty.util.CharsetUtil;
|
2009-03-10 09:42:19 +01:00
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
import static io.netty.handler.codec.http.HttpHeaders.*;
|
|
|
|
|
2009-03-10 09:42:19 +01:00
|
|
|
/**
|
2013-01-16 05:22:50 +01:00
|
|
|
* A {@link ChannelHandler} that aggregates an {@link HttpMessage}
|
|
|
|
* and its following {@link HttpContent}s into a single {@link HttpMessage} with
|
2013-01-14 16:52:30 +01:00
|
|
|
* no following {@link HttpContent}s. It is useful when you don't want to take
|
2009-06-19 17:39:34 +02:00
|
|
|
* care of HTTP messages whose transfer encoding is 'chunked'. Insert this
|
2013-01-14 16:52:30 +01:00
|
|
|
* handler after {@link HttpObjectDecoder} in the {@link ChannelPipeline}:
|
2009-06-19 17:39:34 +02:00
|
|
|
* <pre>
|
2010-02-02 03:00:04 +01:00
|
|
|
* {@link ChannelPipeline} p = ...;
|
2009-06-19 17:39:34 +02:00
|
|
|
* ...
|
2010-02-02 03:00:04 +01:00
|
|
|
* p.addLast("decoder", new {@link HttpRequestDecoder}());
|
2013-01-14 16:52:30 +01:00
|
|
|
* p.addLast("aggregator", <b>new {@link HttpObjectAggregator}(1048576)</b>);
|
2009-06-19 17:39:34 +02:00
|
|
|
* ...
|
2010-02-02 03:00:04 +01:00
|
|
|
* p.addLast("encoder", new {@link HttpResponseEncoder}());
|
2009-06-19 17:39:34 +02:00
|
|
|
* p.addLast("handler", new HttpRequestHandler());
|
|
|
|
* </pre>
|
2009-07-20 05:37:35 +02:00
|
|
|
* @apiviz.landmark
|
2013-01-18 06:12:35 +01:00
|
|
|
* @apiviz.has io.netty.handler.codec.http.HttpContent oneway - - filters out
|
2009-03-10 09:42:19 +01:00
|
|
|
*/
|
2013-01-14 16:52:30 +01:00
|
|
|
public class HttpObjectAggregator extends MessageToMessageDecoder<HttpObject> {
|
2012-07-03 10:37:05 +02:00
|
|
|
public static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
|
2012-06-11 10:02:00 +02:00
|
|
|
private static final ByteBuf CONTINUE = Unpooled.copiedBuffer(
|
2010-10-19 07:40:44 +02:00
|
|
|
"HTTP/1.1 100 Continue\r\n\r\n", CharsetUtil.US_ASCII);
|
|
|
|
|
2009-03-10 09:42:19 +01:00
|
|
|
private final int maxContentLength;
|
2013-01-16 05:22:50 +01:00
|
|
|
private FullHttpMessage currentMessage;
|
2009-03-10 09:42:19 +01:00
|
|
|
|
2012-07-03 10:37:05 +02:00
|
|
|
private int maxCumulationBufferComponents = DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS;
|
|
|
|
private ChannelHandlerContext ctx;
|
|
|
|
|
2009-06-19 17:35:19 +02:00
|
|
|
/**
|
|
|
|
* Creates a new instance.
|
|
|
|
*
|
|
|
|
* @param maxContentLength
|
|
|
|
* the maximum length of the aggregated content.
|
|
|
|
* If the length of the aggregated content exceeds this value,
|
|
|
|
* a {@link TooLongFrameException} will be raised.
|
|
|
|
*/
|
2013-01-14 16:52:30 +01:00
|
|
|
public HttpObjectAggregator(int maxContentLength) {
|
2009-03-10 09:42:19 +01:00
|
|
|
if (maxContentLength <= 0) {
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"maxContentLength must be a positive integer: " +
|
|
|
|
maxContentLength);
|
|
|
|
}
|
|
|
|
this.maxContentLength = maxContentLength;
|
|
|
|
}
|
|
|
|
|
2012-07-03 10:37:05 +02:00
|
|
|
/**
|
|
|
|
* 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 {@link #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}.
|
|
|
|
*/
|
|
|
|
public final int getMaxCumulationBufferComponents() {
|
|
|
|
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 {@link #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.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-20 07:19:11 +02:00
|
|
|
@Override
|
2013-01-08 08:18:46 +01:00
|
|
|
protected Object decode(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
|
2013-01-16 05:22:50 +01:00
|
|
|
FullHttpMessage currentMessage = this.currentMessage;
|
|
|
|
|
|
|
|
if (msg instanceof HttpMessage) {
|
|
|
|
assert currentMessage == null;
|
2010-04-16 06:22:00 +02:00
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
HttpMessage m = (HttpMessage) msg;
|
2010-10-19 07:40:44 +02:00
|
|
|
|
|
|
|
// Handle the 'Expect: 100-continue' header if necessary.
|
|
|
|
// TODO: Respond with 413 Request Entity Too Large
|
|
|
|
// 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.
|
|
|
|
if (is100ContinueExpected(m)) {
|
2012-05-20 07:19:11 +02:00
|
|
|
ctx.write(CONTINUE.duplicate());
|
2010-10-19 07:40:44 +02:00
|
|
|
}
|
|
|
|
|
2013-01-30 07:42:18 +01:00
|
|
|
if (!m.getDecoderResult().isSuccess()) {
|
2013-01-14 16:52:30 +01:00
|
|
|
removeTransferEncodingChunked(m);
|
2012-09-28 08:26:38 +02:00
|
|
|
this.currentMessage = null;
|
|
|
|
return m;
|
|
|
|
}
|
2013-01-16 05:22:50 +01:00
|
|
|
if (msg instanceof HttpRequest) {
|
|
|
|
HttpRequest header = (HttpRequest) msg;
|
2013-01-30 07:42:18 +01:00
|
|
|
this.currentMessage = currentMessage = new DefaultFullHttpRequest(header.getProtocolVersion(),
|
|
|
|
header.getMethod(), header.getUri(), Unpooled.compositeBuffer(maxCumulationBufferComponents));
|
2013-01-16 05:22:50 +01:00
|
|
|
} else if (msg instanceof HttpResponse) {
|
|
|
|
HttpResponse header = (HttpResponse) msg;
|
|
|
|
this.currentMessage = currentMessage = new DefaultFullHttpResponse(
|
2013-01-30 07:42:18 +01:00
|
|
|
header.getProtocolVersion(), header.getStatus(),
|
2013-01-16 05:22:50 +01:00
|
|
|
Unpooled.compositeBuffer(maxCumulationBufferComponents));
|
|
|
|
} else {
|
|
|
|
throw new Error();
|
2013-01-14 16:52:30 +01:00
|
|
|
}
|
2013-01-16 05:22:50 +01:00
|
|
|
|
2013-01-16 16:33:40 +01:00
|
|
|
currentMessage.headers().set(m.headers());
|
|
|
|
|
2013-01-14 16:52:30 +01:00
|
|
|
// A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
|
2013-01-16 05:22:50 +01:00
|
|
|
removeTransferEncodingChunked(currentMessage);
|
2013-01-14 16:52:30 +01:00
|
|
|
return null;
|
|
|
|
|
|
|
|
} else if (msg instanceof HttpContent) {
|
2013-01-16 05:22:50 +01:00
|
|
|
assert currentMessage != null;
|
2010-04-16 06:22:00 +02:00
|
|
|
|
2009-03-10 09:42:19 +01:00
|
|
|
// Merge the received chunk into the content of the current message.
|
2013-01-14 16:52:30 +01:00
|
|
|
HttpContent chunk = (HttpContent) msg;
|
2013-01-16 05:22:50 +01:00
|
|
|
CompositeByteBuf content = (CompositeByteBuf) currentMessage.data();
|
2009-03-10 09:42:19 +01:00
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
if (content.readableBytes() > maxContentLength - chunk.data().readableBytes()) {
|
2013-02-10 05:10:09 +01:00
|
|
|
chunk.release();
|
2010-10-19 07:40:44 +02:00
|
|
|
// TODO: Respond with 413 Request Entity Too Large
|
|
|
|
// 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.
|
2009-03-10 09:42:19 +01:00
|
|
|
throw new TooLongFrameException(
|
|
|
|
"HTTP content length exceeded " + maxContentLength +
|
|
|
|
" bytes.");
|
|
|
|
}
|
|
|
|
|
2012-07-03 10:37:05 +02:00
|
|
|
// Append the content of the chunk
|
2013-01-31 15:39:57 +01:00
|
|
|
if (chunk.data().isReadable()) {
|
2013-01-16 05:22:50 +01:00
|
|
|
content.addComponent(chunk.data());
|
|
|
|
content.writerIndex(content.writerIndex() + chunk.data().readableBytes());
|
|
|
|
} else {
|
2013-02-10 05:10:09 +01:00
|
|
|
chunk.release();
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
2012-07-03 10:37:05 +02:00
|
|
|
|
2012-09-28 08:26:38 +02:00
|
|
|
final boolean last;
|
2013-01-30 07:42:18 +01:00
|
|
|
if (!chunk.getDecoderResult().isSuccess()) {
|
|
|
|
currentMessage.setDecoderResult(
|
|
|
|
DecoderResult.partialFailure(chunk.getDecoderResult().cause()));
|
2012-09-28 08:26:38 +02:00
|
|
|
last = true;
|
|
|
|
} else {
|
2013-01-14 16:52:30 +01:00
|
|
|
last = msg instanceof LastHttpContent;
|
2012-09-28 08:26:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (last) {
|
2009-03-10 09:42:19 +01:00
|
|
|
this.currentMessage = null;
|
2010-08-26 07:06:40 +02:00
|
|
|
|
|
|
|
// Merge trailing headers into the message.
|
2013-01-14 16:52:30 +01:00
|
|
|
if (chunk instanceof LastHttpContent) {
|
|
|
|
LastHttpContent trailer = (LastHttpContent) chunk;
|
2013-01-16 16:33:40 +01:00
|
|
|
currentMessage.headers().add(trailer.trailingHeaders());
|
2010-08-26 07:06:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the 'Content-Length' header.
|
2013-01-16 05:22:50 +01:00
|
|
|
currentMessage.headers().set(
|
2009-03-10 09:42:19 +01:00
|
|
|
HttpHeaders.Names.CONTENT_LENGTH,
|
|
|
|
String.valueOf(content.readableBytes()));
|
2010-08-26 07:06:40 +02:00
|
|
|
|
2012-05-20 07:19:11 +02:00
|
|
|
// All done
|
|
|
|
return currentMessage;
|
|
|
|
} else {
|
|
|
|
return null;
|
2009-03-10 09:42:19 +01:00
|
|
|
}
|
2010-04-16 06:22:00 +02:00
|
|
|
} else {
|
2013-01-16 05:22:50 +01:00
|
|
|
throw new Error();
|
2009-03-10 09:42:19 +01:00
|
|
|
}
|
|
|
|
}
|
2012-07-03 10:37:05 +02:00
|
|
|
|
2013-01-16 16:50:43 +01:00
|
|
|
@Override
|
|
|
|
protected void freeInboundMessage(HttpObject msg) throws Exception {
|
|
|
|
// decode() frees HttpContents.
|
|
|
|
}
|
|
|
|
|
2012-07-19 13:23:55 +02:00
|
|
|
@Override
|
2012-07-03 10:37:05 +02:00
|
|
|
public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
|
|
|
|
this.ctx = ctx;
|
|
|
|
}
|
2009-03-10 09:42:19 +01:00
|
|
|
}
|