netty5/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectAggregator.java

213 lines
8.7 KiB
Java
Raw Normal View History

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
*
* 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
* 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
import io.netty.buffer.BufUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
2011-12-09 04:38:59 +01:00
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
2011-12-09 04:38:59 +01:00
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.MessageToMessageDecoder;
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
import static io.netty.handler.codec.http.HttpHeaders.*;
2009-03-10 09:42:19 +01:00
/**
* A {@link ChannelHandler} that aggregates an {@link HttpMessage}
* and its following {@link HttpContent}s into a single {@link HttpMessage} with
* 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
* 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}());
* 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-03-10 09:42:19 +01:00
*/
public class HttpObjectAggregator extends MessageToMessageDecoder<HttpObject> {
public static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
private static final ByteBuf CONTINUE = Unpooled.copiedBuffer(
"HTTP/1.1 100 Continue\r\n\r\n", CharsetUtil.US_ASCII);
2009-03-10 09:42:19 +01:00
private final int maxContentLength;
private FullHttpMessage currentMessage;
2009-03-10 09:42:19 +01: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.
*/
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;
}
/**
* 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.");
}
}
@Override
protected Object decode(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
FullHttpMessage currentMessage = this.currentMessage;
if (msg instanceof HttpMessage) {
assert currentMessage == null;
HttpMessage m = (HttpMessage) msg;
// 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)) {
ctx.write(CONTINUE.duplicate());
}
if (!m.getDecoderResult().isSuccess()) {
removeTransferEncodingChunked(m);
this.currentMessage = null;
BufUtil.retain(m);
return m;
}
if (msg instanceof HttpRequest) {
HttpRequest header = (HttpRequest) msg;
this.currentMessage = currentMessage = new DefaultFullHttpRequest(header.getProtocolVersion(),
header.getMethod(), header.getUri(), Unpooled.compositeBuffer(maxCumulationBufferComponents));
} else if (msg instanceof HttpResponse) {
HttpResponse header = (HttpResponse) msg;
this.currentMessage = currentMessage = new DefaultFullHttpResponse(
header.getProtocolVersion(), header.getStatus(),
Unpooled.compositeBuffer(maxCumulationBufferComponents));
} else {
throw new Error();
}
currentMessage.headers().set(m.headers());
// A streamed message - initialize the cumulative buffer, and wait for incoming chunks.
removeTransferEncodingChunked(currentMessage);
return null;
} else if (msg instanceof HttpContent) {
assert currentMessage != null;
2009-03-10 09:42:19 +01:00
// Merge the received chunk into the content of the current message.
HttpContent chunk = (HttpContent) msg;
CompositeByteBuf content = (CompositeByteBuf) currentMessage.data();
2009-03-10 09:42:19 +01:00
if (content.readableBytes() > maxContentLength - chunk.data().readableBytes()) {
// 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.");
}
// Append the content of the chunk
if (chunk.data().isReadable()) {
chunk.retain();
content.addComponent(chunk.data());
content.writerIndex(content.writerIndex() + chunk.data().readableBytes());
}
final boolean last;
if (!chunk.getDecoderResult().isSuccess()) {
currentMessage.setDecoderResult(
DecoderResult.partialFailure(chunk.getDecoderResult().cause()));
last = true;
} else {
last = chunk instanceof LastHttpContent;
}
if (last) {
2009-03-10 09:42:19 +01:00
this.currentMessage = null;
// Merge trailing headers into the message.
if (chunk instanceof LastHttpContent) {
LastHttpContent trailer = (LastHttpContent) chunk;
currentMessage.headers().add(trailer.trailingHeaders());
}
// Set the 'Content-Length' header.
currentMessage.headers().set(
2009-03-10 09:42:19 +01:00
HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(content.readableBytes()));
// All done
return currentMessage;
} else {
return null;
2009-03-10 09:42:19 +01:00
}
} else {
throw new Error();
2009-03-10 09:42:19 +01:00
}
}
@Override
public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
}
2009-03-10 09:42:19 +01:00
}