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

182 lines
6.9 KiB
Java
Raw Normal View History

2008-11-19 08:22:15 +01:00
/*
2012-06-04 22:31:44 +02:00
* Copyright 2012 The Netty Project
2009-06-19 19:48:17 +02: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:
2008-11-19 08:22:15 +01:00
*
2012-06-04 22:31:44 +02:00
* http://www.apache.org/licenses/LICENSE-2.0
2008-11-19 08:22:15 +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.
2008-11-19 08:22:15 +01:00
*/
2011-12-09 04:38:59 +01:00
package io.netty.handler.codec.http;
2008-11-19 08:22:15 +01:00
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.FileRegion;
import io.netty.handler.codec.MessageToMessageEncoder;
2011-12-09 04:38:59 +01:00
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
2008-11-19 08:22:15 +01:00
import java.util.List;
import java.util.Map;
import static io.netty.handler.codec.http.HttpConstants.*;
2008-11-19 08:22:15 +01:00
/**
* Encodes an {@link HttpMessage} or an {@link HttpContent} into
* a {@link ByteBuf}.
2009-06-19 17:35:19 +02:00
*
* <h3>Extensibility</h3>
*
* Please note that this encoder is designed to be extended to implement
* a protocol derived from HTTP, such as
* <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
* <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
* To implement the encoder of such a derived protocol, extend this class and
* implement all abstract methods properly.
2008-11-19 08:22:15 +01:00
*/
public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {
2013-05-24 09:07:17 +02:00
private static final byte[] CRLF = { CR, LF };
Revamp the core API to reduce memory footprint and consumption The API changes made so far turned out to increase the memory footprint and consumption while our intention was actually decreasing them. Memory consumption issue: When there are many connections which does not exchange data frequently, the old Netty 4 API spent a lot more memory than 3 because it always allocates per-handler buffer for each connection unless otherwise explicitly stated by a user. In a usual real world load, a client doesn't always send requests without pausing, so the idea of having a buffer whose life cycle if bound to the life cycle of a connection didn't work as expected. Memory footprint issue: The old Netty 4 API decreased overall memory footprint by a great deal in many cases. It was mainly because the old Netty 4 API did not allocate a new buffer and event object for each read. Instead, it created a new buffer for each handler in a pipeline. This works pretty well as long as the number of handlers in a pipeline is only a few. However, for a highly modular application with many handlers which handles connections which lasts for relatively short period, it actually makes the memory footprint issue much worse. Changes: All in all, this is about retaining all the good changes we made in 4 so far such as better thread model and going back to the way how we dealt with message events in 3. To fix the memory consumption/footprint issue mentioned above, we made a hard decision to break the backward compatibility again with the following changes: - Remove MessageBuf - Merge Buf into ByteBuf - Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler - Similar changes were made to the adapter classes - Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler - Similar changes were made to the adapter classes - Introduce MessageList which is similar to `MessageEvent` in Netty 3 - Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList) - Replace flush(ctx, promise) with write(ctx, MessageList, promise) - Remove ByteToByteEncoder/Decoder/Codec - Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf> - Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel - Add SimpleChannelInboundHandler which is sometimes more useful than ChannelInboundHandlerAdapter - Bring back Channel.isWritable() from Netty 3 - Add ChannelInboundHandler.channelWritabilityChanges() event - Add RecvByteBufAllocator configuration property - Similar to ReceiveBufferSizePredictor in Netty 3 - Some existing configuration properties such as DatagramChannelConfig.receivePacketSize is gone now. - Remove suspend/resumeIntermediaryDeallocation() in ByteBuf This change would have been impossible without @normanmaurer's help. He fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
private static final byte[] ZERO_CRLF = { '0', CR, LF };
private static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF };
private static final byte[] HEADER_SEPARATOR = { COLON, SP};
private static final int ST_INIT = 0;
private static final int ST_CONTENT_NON_CHUNK = 1;
private static final int ST_CONTENT_CHUNK = 2;
@SuppressWarnings("RedundantFieldInitialization")
private int state = ST_INIT;
2008-11-19 08:22:15 +01:00
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
if (msg instanceof HttpMessage) {
if (state != ST_INIT) {
throw new IllegalStateException("unexpected message type: " + msg.getClass().getSimpleName());
}
@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
H m = (H) msg;
ByteBuf buf = ctx.alloc().buffer();
// Encode the message.
encodeInitialLine(buf, m);
encodeHeaders(buf, m.headers());
buf.writeBytes(CRLF);
out.add(buf);
state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
}
if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
if (state == ST_INIT) {
throw new IllegalStateException("unexpected message type: " + msg.getClass().getSimpleName());
}
int contentLength = contentLength(msg);
if (state == ST_CONTENT_NON_CHUNK) {
if (contentLength > 0) {
out.add(encodeAndRetain(msg));
} else {
// Need to produce some output otherwise an
// IllegalstateException will be thrown
out.add(Unpooled.EMPTY_BUFFER);
}
if (msg instanceof LastHttpContent) {
state = ST_INIT;
}
} else if (state == ST_CONTENT_CHUNK) {
if (contentLength > 0) {
byte[] length = Integer.toHexString(contentLength).getBytes(CharsetUtil.US_ASCII);
ByteBuf buf = ctx.alloc().buffer(length.length + 2);
buf.writeBytes(length);
buf.writeBytes(CRLF);
out.add(buf);
out.add(encodeAndRetain(msg));
out.add(ctx.alloc().buffer(CRLF.length).writeBytes(CRLF));
}
if (msg instanceof LastHttpContent) {
HttpHeaders headers = ((LastHttpContent) msg).trailingHeaders();
if (headers.isEmpty()) {
out.add(ctx.alloc().buffer(ZERO_CRLF_CRLF.length).writeBytes(ZERO_CRLF_CRLF));
} else {
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(ZERO_CRLF);
encodeHeaders(buf, headers);
buf.writeBytes(CRLF);
out.add(buf);
}
2013-05-24 09:07:17 +02:00
state = ST_INIT;
} else {
if (contentLength == 0) {
// Need to produce some output otherwise an
// IllegalstateException will be thrown
out.add(Unpooled.EMPTY_BUFFER);
}
}
} else {
throw new Error();
}
2008-11-19 08:22:15 +01:00
}
}
@Override
public boolean acceptOutboundMessage(Object msg) throws Exception {
return msg instanceof HttpObject || msg instanceof ByteBuf || msg instanceof FileRegion;
}
private static Object encodeAndRetain(Object msg) {
if (msg instanceof ByteBuf) {
return ((ByteBuf) msg).retain();
}
if (msg instanceof HttpContent) {
return ((HttpContent) msg).content().retain();
}
if (msg instanceof FileRegion) {
return ((FileRegion) msg).retain();
}
throw new IllegalStateException("unexpected message type: " + msg.getClass().getSimpleName());
}
private static int contentLength(Object msg) {
if (msg instanceof HttpContent) {
return ((HttpContent) msg).content().readableBytes();
}
if (msg instanceof ByteBuf) {
return ((ByteBuf) msg).readableBytes();
}
if (msg instanceof FileRegion) {
return (int) ((FileRegion) msg).count();
}
throw new IllegalStateException("unexpected message type: " + msg.getClass().getSimpleName());
}
private static void encodeHeaders(ByteBuf buf, HttpHeaders headers) {
for (Map.Entry<String, String> h: headers) {
encodeHeader(buf, h.getKey(), h.getValue());
2008-11-19 08:22:15 +01:00
}
}
private static void encodeHeader(ByteBuf buf, String header, String value) {
2013-06-29 00:57:26 +02:00
encodeAscii(header, buf);
2013-05-24 09:07:17 +02:00
buf.writeBytes(HEADER_SEPARATOR);
2013-06-29 00:57:26 +02:00
encodeAscii(value, buf);
2013-05-24 09:07:17 +02:00
buf.writeBytes(CRLF);
}
protected static void encodeAscii(String s, ByteBuf buf) {
2013-06-29 00:57:26 +02:00
for (int i = 0; i < s.length(); i++) {
buf.writeByte(s.charAt(i));
}
}
protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
2008-11-19 08:22:15 +01:00
}