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

451 lines
15 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.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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.MessageAggregator;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
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 FullHttpRequest}
* or {@link FullHttpResponse} (depending on if it used to handle requests or responses)
* 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
* ...
* p.addLast("encoder", new {@link HttpResponseEncoder}());
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
* ...
* p.addLast("handler", new HttpRequestHandler());
* </pre>
* Be aware that you need to have the {@link HttpResponseEncoder} or {@link HttpRequestEncoder}
* before the {@link HttpObjectAggregator} in the {@link ChannelPipeline}.
2009-03-10 09:42:19 +01:00
*/
public class HttpObjectAggregator
extends MessageAggregator<HttpObject, HttpMessage, HttpContent, FullHttpMessage> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpObjectAggregator.class);
private static final FullHttpResponse CONTINUE = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE, Unpooled.EMPTY_BUFFER);
private static final FullHttpResponse TOO_LARGE = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, Unpooled.EMPTY_BUFFER);
static {
Fix backward compatibility from the previous backport Motivation: The commit 50e06442c3f2753c9b2a506f68ea70273b829e21 changed the type of the constants in HttpHeaders.Names and HttpHeaders.Values, making 4.1 backward-incompatible with 4.0. It also introduces newer utility classes such as HttpHeaderUtil, which deprecates most static methods in HttpHeaders. To ease the migration between 4.1 and 5.0, we should deprecate all static methods that are non-existent in 5.0, and provide proper counterpart. Modification: - Revert the changes in HttpHeaders.Names and Values - Deprecate all static methods in HttpHeaders in favor of: - HttpHeaderUtil - the member methods of HttpHeaders - AsciiString - Add integer and date access methods to HttpHeaders for easier future migration to 5.0 - Add HttpHeaderNames and HttpHeaderValues which provide standard HTTP constants in AsciiString - Deprecate HttpHeaders.Names and Values - Make HttpHeaderValues.WEBSOCKET lowercased because it's actually lowercased in all WebSocket versions but the oldest one - Add RtspHeaderNames and RtspHeaderValues which provide standard RTSP constants in AsciiString - Deprecate RtspHeaders.* - Do not use AsciiString.equalsIgnoreCase(CharSeq, CharSeq) if one of the parameters are AsciiString - Avoid using AsciiString.toString() repetitively - Change the parameter type of some methods from String to CharSequence Result: Backward compatibility is recovered. New classes and methods will make the migration to 5.0 easier, once (Http|Rtsp)Header(Names|Values) are ported to master.
2014-10-31 08:48:28 +01:00
TOO_LARGE.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
}
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,
* {@link #handleOversizedMessage(ChannelHandlerContext, HttpMessage)}
* will be called.
2009-06-19 17:35:19 +02:00
*/
public HttpObjectAggregator(int maxContentLength) {
super(maxContentLength);
2009-03-10 09:42:19 +01:00
}
@Override
protected boolean isStartMessage(HttpObject msg) throws Exception {
return msg instanceof HttpMessage;
}
@Override
protected boolean isContentMessage(HttpObject msg) throws Exception {
return msg instanceof HttpContent;
}
@Override
protected boolean isLastContentMessage(HttpContent msg) throws Exception {
return msg instanceof LastHttpContent;
}
2009-03-10 09:42:19 +01:00
@Override
protected boolean isAggregated(HttpObject msg) throws Exception {
return msg instanceof FullHttpMessage;
}
@Override
protected boolean hasContentLength(HttpMessage start) throws Exception {
Fix backward compatibility from the previous backport Motivation: The commit 50e06442c3f2753c9b2a506f68ea70273b829e21 changed the type of the constants in HttpHeaders.Names and HttpHeaders.Values, making 4.1 backward-incompatible with 4.0. It also introduces newer utility classes such as HttpHeaderUtil, which deprecates most static methods in HttpHeaders. To ease the migration between 4.1 and 5.0, we should deprecate all static methods that are non-existent in 5.0, and provide proper counterpart. Modification: - Revert the changes in HttpHeaders.Names and Values - Deprecate all static methods in HttpHeaders in favor of: - HttpHeaderUtil - the member methods of HttpHeaders - AsciiString - Add integer and date access methods to HttpHeaders for easier future migration to 5.0 - Add HttpHeaderNames and HttpHeaderValues which provide standard HTTP constants in AsciiString - Deprecate HttpHeaders.Names and Values - Make HttpHeaderValues.WEBSOCKET lowercased because it's actually lowercased in all WebSocket versions but the oldest one - Add RtspHeaderNames and RtspHeaderValues which provide standard RTSP constants in AsciiString - Deprecate RtspHeaders.* - Do not use AsciiString.equalsIgnoreCase(CharSeq, CharSeq) if one of the parameters are AsciiString - Avoid using AsciiString.toString() repetitively - Change the parameter type of some methods from String to CharSequence Result: Backward compatibility is recovered. New classes and methods will make the migration to 5.0 easier, once (Http|Rtsp)Header(Names|Values) are ported to master.
2014-10-31 08:48:28 +01:00
return HttpHeaderUtil.isContentLengthSet(start);
}
@Override
protected long contentLength(HttpMessage start) throws Exception {
Fix backward compatibility from the previous backport Motivation: The commit 50e06442c3f2753c9b2a506f68ea70273b829e21 changed the type of the constants in HttpHeaders.Names and HttpHeaders.Values, making 4.1 backward-incompatible with 4.0. It also introduces newer utility classes such as HttpHeaderUtil, which deprecates most static methods in HttpHeaders. To ease the migration between 4.1 and 5.0, we should deprecate all static methods that are non-existent in 5.0, and provide proper counterpart. Modification: - Revert the changes in HttpHeaders.Names and Values - Deprecate all static methods in HttpHeaders in favor of: - HttpHeaderUtil - the member methods of HttpHeaders - AsciiString - Add integer and date access methods to HttpHeaders for easier future migration to 5.0 - Add HttpHeaderNames and HttpHeaderValues which provide standard HTTP constants in AsciiString - Deprecate HttpHeaders.Names and Values - Make HttpHeaderValues.WEBSOCKET lowercased because it's actually lowercased in all WebSocket versions but the oldest one - Add RtspHeaderNames and RtspHeaderValues which provide standard RTSP constants in AsciiString - Deprecate RtspHeaders.* - Do not use AsciiString.equalsIgnoreCase(CharSeq, CharSeq) if one of the parameters are AsciiString - Avoid using AsciiString.toString() repetitively - Change the parameter type of some methods from String to CharSequence Result: Backward compatibility is recovered. New classes and methods will make the migration to 5.0 easier, once (Http|Rtsp)Header(Names|Values) are ported to master.
2014-10-31 08:48:28 +01:00
return HttpHeaderUtil.getContentLength(start);
}
@Override
protected Object newContinueResponse(HttpMessage start) throws Exception {
Fix backward compatibility from the previous backport Motivation: The commit 50e06442c3f2753c9b2a506f68ea70273b829e21 changed the type of the constants in HttpHeaders.Names and HttpHeaders.Values, making 4.1 backward-incompatible with 4.0. It also introduces newer utility classes such as HttpHeaderUtil, which deprecates most static methods in HttpHeaders. To ease the migration between 4.1 and 5.0, we should deprecate all static methods that are non-existent in 5.0, and provide proper counterpart. Modification: - Revert the changes in HttpHeaders.Names and Values - Deprecate all static methods in HttpHeaders in favor of: - HttpHeaderUtil - the member methods of HttpHeaders - AsciiString - Add integer and date access methods to HttpHeaders for easier future migration to 5.0 - Add HttpHeaderNames and HttpHeaderValues which provide standard HTTP constants in AsciiString - Deprecate HttpHeaders.Names and Values - Make HttpHeaderValues.WEBSOCKET lowercased because it's actually lowercased in all WebSocket versions but the oldest one - Add RtspHeaderNames and RtspHeaderValues which provide standard RTSP constants in AsciiString - Deprecate RtspHeaders.* - Do not use AsciiString.equalsIgnoreCase(CharSeq, CharSeq) if one of the parameters are AsciiString - Avoid using AsciiString.toString() repetitively - Change the parameter type of some methods from String to CharSequence Result: Backward compatibility is recovered. New classes and methods will make the migration to 5.0 easier, once (Http|Rtsp)Header(Names|Values) are ported to master.
2014-10-31 08:48:28 +01:00
if (HttpHeaderUtil.is100ContinueExpected(start)) {
return CONTINUE;
} else {
return null;
}
}
@Override
protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
assert !(start instanceof FullHttpMessage);
Fix backward compatibility from the previous backport Motivation: The commit 50e06442c3f2753c9b2a506f68ea70273b829e21 changed the type of the constants in HttpHeaders.Names and HttpHeaders.Values, making 4.1 backward-incompatible with 4.0. It also introduces newer utility classes such as HttpHeaderUtil, which deprecates most static methods in HttpHeaders. To ease the migration between 4.1 and 5.0, we should deprecate all static methods that are non-existent in 5.0, and provide proper counterpart. Modification: - Revert the changes in HttpHeaders.Names and Values - Deprecate all static methods in HttpHeaders in favor of: - HttpHeaderUtil - the member methods of HttpHeaders - AsciiString - Add integer and date access methods to HttpHeaders for easier future migration to 5.0 - Add HttpHeaderNames and HttpHeaderValues which provide standard HTTP constants in AsciiString - Deprecate HttpHeaders.Names and Values - Make HttpHeaderValues.WEBSOCKET lowercased because it's actually lowercased in all WebSocket versions but the oldest one - Add RtspHeaderNames and RtspHeaderValues which provide standard RTSP constants in AsciiString - Deprecate RtspHeaders.* - Do not use AsciiString.equalsIgnoreCase(CharSeq, CharSeq) if one of the parameters are AsciiString - Avoid using AsciiString.toString() repetitively - Change the parameter type of some methods from String to CharSequence Result: Backward compatibility is recovered. New classes and methods will make the migration to 5.0 easier, once (Http|Rtsp)Header(Names|Values) are ported to master.
2014-10-31 08:48:28 +01:00
HttpHeaderUtil.setTransferEncodingChunked(start, false);
AggregatedFullHttpMessage ret;
if (start instanceof HttpRequest) {
ret = new AggregatedFullHttpRequest((HttpRequest) start, content, null);
} else if (start instanceof HttpResponse) {
ret = new AggregatedFullHttpResponse((HttpResponse) start, content, null);
} else {
throw new Error();
2009-03-10 09:42:19 +01:00
}
return ret;
}
@Override
protected void aggregate(FullHttpMessage aggregated, HttpContent content) throws Exception {
if (content instanceof LastHttpContent) {
// Merge trailing headers into the message.
((AggregatedFullHttpMessage) aggregated).setTrailingHeaders(((LastHttpContent) content).trailingHeaders());
}
}
@Override
protected void finishAggregation(FullHttpMessage aggregated) throws Exception {
// Set the 'Content-Length' header. If one isn't already set.
// This is important as HEAD responses will use a 'Content-Length' header which
// does not match the actual body, but the number of bytes that would be
// transmitted if a GET would have been used.
//
// See rfc2616 14.13 Content-Length
if (!HttpHeaderUtil.isContentLengthSet(aggregated)) {
aggregated.headers().set(
HttpHeaderNames.CONTENT_LENGTH,
String.valueOf(aggregated.content().readableBytes()));
}
}
@Override
protected void handleOversizedMessage(final ChannelHandlerContext ctx, HttpMessage oversized) throws Exception {
if (oversized instanceof HttpRequest) {
// send back a 413 and close the connection
ChannelFuture future = ctx.writeAndFlush(TOO_LARGE).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
logger.debug("Failed to send a 413 Request Entity Too Large.", future.cause());
ctx.close();
}
}
});
// If the client started to send data already, close because it's impossible to recover.
Don't close the connection whenever Expect: 100-continue is missing. Motivation: The 4.1.0-Beta3 implementation of HttpObjectAggregator.handleOversizedMessage closes the connection if the client sent oversized chunked data with no Expect: 100-continue header. This causes a broken pipe or "connection reset by peer" error in some clients (tested on Firefox 31 OS X 10.9.5, async-http-client 1.8.14). This part of the HTTP 1.1 spec (below) seems to say that in this scenario the connection should not be closed (unless the intention is to be very strict about how data should be sent). http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html "If an origin server receives a request that does not include an Expect request-header field with the "100-continue" expectation, the request includes a request body, and the server responds with a final status code before reading the entire request body from the transport connection, then the server SHOULD NOT close the transport connection until it has read the entire request, or until the client closes the connection. Otherwise, the client might not reliably receive the response message. However, this requirement is not be construed as preventing a server from defending itself against denial-of-service attacks, or from badly broken client implementations." Modifications: Change HttpObjectAggregator.handleOversizedMessage to close the connection only if keep-alive is off and Expect: 100-continue is missing. Update test to reflect the change. Result: Broken pipe and connection reset errors on the client are avoided when oversized data is sent.
2014-10-07 15:56:15 +02:00
// If keep-alive is off and 'Expect: 100-continue' is missing, no need to leave the connection open.
if (oversized instanceof FullHttpMessage ||
!HttpHeaderUtil.is100ContinueExpected(oversized) && !HttpHeaderUtil.isKeepAlive(oversized)) {
future.addListener(ChannelFutureListener.CLOSE);
}
// If an oversized request was handled properly and the connection is still alive
// (i.e. rejected 100-continue). the decoder should prepare to handle a new message.
HttpObjectDecoder decoder = ctx.pipeline().get(HttpObjectDecoder.class);
if (decoder != null) {
decoder.reset();
}
} else if (oversized instanceof HttpResponse) {
ctx.close();
throw new TooLongFrameException("Response entity too large: " + oversized);
} else {
throw new IllegalStateException();
}
}
private abstract static class AggregatedFullHttpMessage extends DefaultByteBufHolder implements FullHttpMessage {
protected final HttpMessage message;
private HttpHeaders trailingHeaders;
AggregatedFullHttpMessage(HttpMessage message, ByteBuf content, HttpHeaders trailingHeaders) {
super(content);
this.message = message;
this.trailingHeaders = trailingHeaders;
}
@Override
public HttpHeaders trailingHeaders() {
HttpHeaders trailingHeaders = this.trailingHeaders;
if (trailingHeaders == null) {
return HttpHeaders.EMPTY_HEADERS;
} else {
return trailingHeaders;
}
}
void setTrailingHeaders(HttpHeaders trailingHeaders) {
this.trailingHeaders = trailingHeaders;
}
@Override
public HttpVersion getProtocolVersion() {
return message.protocolVersion();
}
@Override
public HttpVersion protocolVersion() {
return message.protocolVersion();
}
@Override
public FullHttpMessage setProtocolVersion(HttpVersion version) {
message.setProtocolVersion(version);
return this;
}
@Override
public HttpHeaders headers() {
return message.headers();
}
@Override
public DecoderResult decoderResult() {
return message.decoderResult();
}
@Override
public DecoderResult getDecoderResult() {
return message.decoderResult();
}
@Override
public void setDecoderResult(DecoderResult result) {
message.setDecoderResult(result);
}
@Override
public FullHttpMessage retain(int increment) {
super.retain(increment);
return this;
}
@Override
public FullHttpMessage retain() {
super.retain();
return this;
}
@Override
public FullHttpMessage touch(Object hint) {
super.touch(hint);
return this;
}
@Override
public FullHttpMessage touch() {
super.touch();
return this;
}
@Override
public abstract FullHttpMessage copy();
@Override
public abstract FullHttpMessage duplicate();
}
private static final class AggregatedFullHttpRequest extends AggregatedFullHttpMessage implements FullHttpRequest {
AggregatedFullHttpRequest(HttpRequest request, ByteBuf content, HttpHeaders trailingHeaders) {
super(request, content, trailingHeaders);
}
@Override
public FullHttpRequest copy() {
DefaultFullHttpRequest copy = new DefaultFullHttpRequest(
getProtocolVersion(), getMethod(), getUri(), content().copy());
copy.headers().set(headers());
copy.trailingHeaders().set(trailingHeaders());
return copy;
}
@Override
public FullHttpRequest duplicate() {
DefaultFullHttpRequest duplicate = new DefaultFullHttpRequest(
getProtocolVersion(), getMethod(), getUri(), content().duplicate());
duplicate.headers().set(headers());
duplicate.trailingHeaders().set(trailingHeaders());
return duplicate;
}
@Override
public FullHttpRequest retain(int increment) {
super.retain(increment);
return this;
}
@Override
public FullHttpRequest retain() {
super.retain();
return this;
}
@Override
public FullHttpRequest touch() {
super.touch();
return this;
}
@Override
public FullHttpRequest touch(Object hint) {
super.touch(hint);
return this;
}
@Override
public FullHttpRequest setMethod(HttpMethod method) {
((HttpRequest) message).setMethod(method);
return this;
}
@Override
public FullHttpRequest setUri(String uri) {
((HttpRequest) message).setUri(uri);
return this;
}
@Override
public HttpMethod getMethod() {
return ((HttpRequest) message).method();
}
@Override
public String getUri() {
return ((HttpRequest) message).uri();
}
@Override
public HttpMethod method() {
return getMethod();
}
@Override
public String uri() {
return getUri();
}
@Override
public FullHttpRequest setProtocolVersion(HttpVersion version) {
super.setProtocolVersion(version);
return this;
}
@Override
public String toString() {
return HttpMessageUtil.appendFullRequest(new StringBuilder(256), this).toString();
}
}
private static final class AggregatedFullHttpResponse extends AggregatedFullHttpMessage
implements FullHttpResponse {
AggregatedFullHttpResponse(HttpResponse message, ByteBuf content, HttpHeaders trailingHeaders) {
super(message, content, trailingHeaders);
}
@Override
public FullHttpResponse copy() {
DefaultFullHttpResponse copy = new DefaultFullHttpResponse(
getProtocolVersion(), getStatus(), content().copy());
copy.headers().set(headers());
copy.trailingHeaders().set(trailingHeaders());
return copy;
}
@Override
public FullHttpResponse duplicate() {
DefaultFullHttpResponse duplicate = new DefaultFullHttpResponse(getProtocolVersion(), getStatus(),
content().duplicate());
duplicate.headers().set(headers());
duplicate.trailingHeaders().set(trailingHeaders());
return duplicate;
}
@Override
public FullHttpResponse setStatus(HttpResponseStatus status) {
((HttpResponse) message).setStatus(status);
return this;
}
@Override
public HttpResponseStatus getStatus() {
return ((HttpResponse) message).status();
}
@Override
public HttpResponseStatus status() {
return getStatus();
}
@Override
public FullHttpResponse setProtocolVersion(HttpVersion version) {
super.setProtocolVersion(version);
return this;
}
@Override
public FullHttpResponse retain(int increment) {
super.retain(increment);
return this;
}
@Override
public FullHttpResponse retain() {
super.retain();
return this;
}
@Override
public FullHttpResponse touch(Object hint) {
super.touch(hint);
return this;
}
@Override
public FullHttpResponse touch() {
super.touch();
return this;
}
@Override
public String toString() {
return HttpMessageUtil.appendFullResponse(new StringBuilder(256), this).toString();
}
}
2009-03-10 09:42:19 +01:00
}