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
|
|
|
*
|
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:
|
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
|
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.
|
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
|
|
|
|
2012-06-10 04:08:43 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
2012-06-07 07:52:33 +02:00
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
2013-08-05 08:22:04 +02:00
|
|
|
import io.netty.channel.FileRegion;
|
2013-06-12 10:59:51 +02:00
|
|
|
import io.netty.handler.codec.MessageToMessageEncoder;
|
2011-12-09 04:38:59 +01:00
|
|
|
import io.netty.util.CharsetUtil;
|
2014-09-19 01:04:35 +02:00
|
|
|
import io.netty.util.internal.PlatformDependent;
|
2013-11-04 11:42:33 +01:00
|
|
|
import io.netty.util.internal.StringUtil;
|
2008-11-19 08:22:15 +01:00
|
|
|
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
import java.util.Iterator;
|
Remove MessageList from public API and change ChannelInbound/OutboundHandler accordingly
I must admit MesageList was pain in the ass. Instead of forcing a
handler always loop over the list of messages, this commit splits
messageReceived(ctx, list) into two event handlers:
- messageReceived(ctx, msg)
- mmessageReceivedLast(ctx)
When Netty reads one or more messages, messageReceived(ctx, msg) event
is triggered for each message. Once the current read operation is
finished, messageReceivedLast() is triggered to tell the handler that
the last messageReceived() was the last message in the current batch.
Similarly, for outbound, write(ctx, list) has been split into two:
- write(ctx, msg)
- flush(ctx, promise)
Instead of writing a list of message with a promise, a user is now
supposed to call write(msg) multiple times and then call flush() to
actually flush the buffered messages.
Please note that write() doesn't have a promise with it. You must call
flush() to get notified on completion. (or you can use writeAndFlush())
Other changes:
- Because MessageList is completely hidden, codec framework uses
List<Object> instead of MessageList as an output parameter.
2013-07-08 12:03:40 +02:00
|
|
|
import java.util.List;
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
import java.util.Map.Entry;
|
2012-05-20 07:19:11 +02:00
|
|
|
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
|
|
|
|
import static io.netty.buffer.Unpooled.directBuffer;
|
|
|
|
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
|
|
|
import static io.netty.handler.codec.http.HttpConstants.CR;
|
|
|
|
import static io.netty.handler.codec.http.HttpConstants.LF;
|
2013-07-10 13:00:42 +02:00
|
|
|
|
2008-11-19 08:22:15 +01:00
|
|
|
/**
|
2013-01-16 05:22:50 +01:00
|
|
|
* Encodes an {@link HttpMessage} or an {@link HttpContent} into
|
2012-06-10 04:08:43 +02:00
|
|
|
* 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
|
|
|
*/
|
2013-08-05 08:22:04 +02:00
|
|
|
public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {
|
2016-08-09 09:44:31 +02:00
|
|
|
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 };
|
2013-07-07 08:28:43 +02:00
|
|
|
private static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF };
|
2013-08-27 10:36:59 +02:00
|
|
|
private static final ByteBuf CRLF_BUF = unreleasableBuffer(directBuffer(CRLF.length).writeBytes(CRLF));
|
2013-08-11 21:52:14 +02:00
|
|
|
private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
|
|
|
|
.writeBytes(ZERO_CRLF_CRLF));
|
2013-07-07 08:28:43 +02:00
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
private static final int ST_INIT = 0;
|
|
|
|
private static final int ST_CONTENT_NON_CHUNK = 1;
|
|
|
|
private static final int ST_CONTENT_CHUNK = 2;
|
2008-12-17 08:38:32 +01:00
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@SuppressWarnings("RedundantFieldInitialization")
|
|
|
|
private int state = ST_INIT;
|
2010-01-07 10:05:38 +01:00
|
|
|
|
2008-11-19 08:22:15 +01:00
|
|
|
@Override
|
2013-08-05 08:22:04 +02:00
|
|
|
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
2013-11-28 12:27:35 +01:00
|
|
|
ByteBuf buf = null;
|
2013-01-16 05:22:50 +01:00
|
|
|
if (msg instanceof HttpMessage) {
|
|
|
|
if (state != ST_INIT) {
|
2013-11-04 11:42:33 +01:00
|
|
|
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
2013-01-14 16:52:30 +01:00
|
|
|
|
2013-02-06 04:55:42 +01:00
|
|
|
@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
|
|
|
|
H m = (H) msg;
|
2013-01-16 05:22:50 +01:00
|
|
|
|
2013-11-28 12:27:35 +01:00
|
|
|
buf = ctx.alloc().buffer();
|
2013-01-16 05:22:50 +01:00
|
|
|
// Encode the message.
|
2013-06-12 10:59:51 +02:00
|
|
|
encodeInitialLine(buf, m);
|
2014-12-08 08:32:48 +01:00
|
|
|
encodeHeaders(m.headers(), buf);
|
2013-06-12 10:59:51 +02:00
|
|
|
buf.writeBytes(CRLF);
|
2015-08-22 17:25:57 +02:00
|
|
|
state = HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
|
2013-01-14 16:52:30 +01:00
|
|
|
}
|
2014-10-22 07:39:31 +02:00
|
|
|
|
|
|
|
// Bypass the encoder in case of an empty buffer, so that the following idiom works:
|
|
|
|
//
|
|
|
|
// ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
|
|
|
//
|
|
|
|
// See https://github.com/netty/netty/issues/2983 for more information.
|
|
|
|
|
|
|
|
if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) {
|
|
|
|
out.add(EMPTY_BUFFER);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-08-05 08:22:04 +02:00
|
|
|
if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
|
2014-10-22 07:39:31 +02:00
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
if (state == ST_INIT) {
|
2013-11-04 11:42:33 +01:00
|
|
|
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2014-06-16 07:44:09 +02:00
|
|
|
final long contentLength = contentLength(msg);
|
2013-01-16 05:22:50 +01:00
|
|
|
if (state == ST_CONTENT_NON_CHUNK) {
|
|
|
|
if (contentLength > 0) {
|
2013-11-28 12:27:35 +01:00
|
|
|
if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) {
|
|
|
|
// merge into other buffer for performance reasons
|
|
|
|
buf.writeBytes(((HttpContent) msg).content());
|
|
|
|
out.add(buf);
|
|
|
|
} else {
|
|
|
|
if (buf != null) {
|
|
|
|
out.add(buf);
|
|
|
|
}
|
|
|
|
out.add(encodeAndRetain(msg));
|
|
|
|
}
|
2013-07-17 12:01:50 +02:00
|
|
|
} else {
|
2013-11-28 12:27:35 +01:00
|
|
|
if (buf != null) {
|
|
|
|
out.add(buf);
|
|
|
|
} else {
|
|
|
|
// Need to produce some output otherwise an
|
|
|
|
// IllegalStateException will be thrown
|
|
|
|
out.add(EMPTY_BUFFER);
|
|
|
|
}
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
2012-08-19 12:06:47 +02:00
|
|
|
|
2013-08-05 08:22:04 +02:00
|
|
|
if (msg instanceof LastHttpContent) {
|
2013-01-16 05:22:50 +01:00
|
|
|
state = ST_INIT;
|
|
|
|
}
|
|
|
|
} else if (state == ST_CONTENT_CHUNK) {
|
2013-11-28 12:27:35 +01:00
|
|
|
if (buf != null) {
|
|
|
|
out.add(buf);
|
|
|
|
}
|
2013-09-05 10:15:51 +02:00
|
|
|
encodeChunkedContent(ctx, msg, contentLength, out);
|
|
|
|
} else {
|
|
|
|
throw new Error();
|
|
|
|
}
|
2013-11-28 12:27:35 +01:00
|
|
|
} else {
|
|
|
|
if (buf != null) {
|
|
|
|
out.add(buf);
|
|
|
|
}
|
2013-09-05 10:15:51 +02:00
|
|
|
}
|
|
|
|
}
|
2013-01-16 05:22:50 +01:00
|
|
|
|
2014-12-08 08:32:48 +01:00
|
|
|
/**
|
|
|
|
* Encode the {@link HttpHeaders} into a {@link ByteBuf}.
|
|
|
|
*/
|
|
|
|
protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) throws Exception {
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
Iterator<Entry<CharSequence, CharSequence>> iter = headers.iteratorCharSequence();
|
|
|
|
while (iter.hasNext()) {
|
|
|
|
Entry<CharSequence, CharSequence> header = iter.next();
|
|
|
|
HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
|
|
|
|
}
|
2014-12-08 08:32:48 +01:00
|
|
|
}
|
|
|
|
|
2014-06-16 07:44:09 +02:00
|
|
|
private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List<Object> out) {
|
2013-09-05 10:15:51 +02:00
|
|
|
if (contentLength > 0) {
|
2014-06-16 07:44:09 +02:00
|
|
|
byte[] length = Long.toHexString(contentLength).getBytes(CharsetUtil.US_ASCII);
|
2013-09-05 10:15:51 +02:00
|
|
|
ByteBuf buf = ctx.alloc().buffer(length.length + 2);
|
|
|
|
buf.writeBytes(length);
|
|
|
|
buf.writeBytes(CRLF);
|
|
|
|
out.add(buf);
|
|
|
|
out.add(encodeAndRetain(msg));
|
|
|
|
out.add(CRLF_BUF.duplicate());
|
|
|
|
}
|
2013-05-24 09:07:17 +02:00
|
|
|
|
2013-09-05 10:15:51 +02:00
|
|
|
if (msg instanceof LastHttpContent) {
|
|
|
|
HttpHeaders headers = ((LastHttpContent) msg).trailingHeaders();
|
|
|
|
if (headers.isEmpty()) {
|
|
|
|
out.add(ZERO_CRLF_CRLF_BUF.duplicate());
|
2013-01-16 05:22:50 +01:00
|
|
|
} else {
|
2013-09-05 10:15:51 +02:00
|
|
|
ByteBuf buf = ctx.alloc().buffer();
|
|
|
|
buf.writeBytes(ZERO_CRLF);
|
2014-09-19 01:04:35 +02:00
|
|
|
try {
|
2014-12-08 08:32:48 +01:00
|
|
|
encodeHeaders(headers, buf);
|
2014-09-19 01:04:35 +02:00
|
|
|
} catch (Exception ex) {
|
|
|
|
buf.release();
|
|
|
|
PlatformDependent.throwException(ex);
|
|
|
|
}
|
2013-09-05 10:15:51 +02:00
|
|
|
buf.writeBytes(CRLF);
|
|
|
|
out.add(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
state = ST_INIT;
|
|
|
|
} else {
|
|
|
|
if (contentLength == 0) {
|
|
|
|
// Need to produce some output otherwise an
|
|
|
|
// IllegalstateException will be thrown
|
|
|
|
out.add(EMPTY_BUFFER);
|
2009-02-12 08:01:26 +01:00
|
|
|
}
|
2008-11-19 08:22:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-05 08:22:04 +02: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();
|
|
|
|
}
|
2013-11-04 11:42:33 +01:00
|
|
|
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
|
2013-08-05 08:22:04 +02:00
|
|
|
}
|
|
|
|
|
2014-06-16 07:44:09 +02:00
|
|
|
private static long contentLength(Object msg) {
|
2013-08-05 08:22:04 +02:00
|
|
|
if (msg instanceof HttpContent) {
|
|
|
|
return ((HttpContent) msg).content().readableBytes();
|
|
|
|
}
|
|
|
|
if (msg instanceof ByteBuf) {
|
|
|
|
return ((ByteBuf) msg).readableBytes();
|
|
|
|
}
|
|
|
|
if (msg instanceof FileRegion) {
|
2014-06-16 07:44:09 +02:00
|
|
|
return ((FileRegion) msg).count();
|
2013-08-05 08:22:04 +02:00
|
|
|
}
|
2013-11-04 11:42:33 +01:00
|
|
|
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
|
2013-08-05 08:22:04 +02:00
|
|
|
}
|
|
|
|
|
2013-11-28 08:15:14 +01:00
|
|
|
@Deprecated
|
2013-07-10 03:37:36 +02:00
|
|
|
protected static void encodeAscii(String s, ByteBuf buf) {
|
2015-08-22 17:25:57 +02:00
|
|
|
HttpUtil.encodeAscii0(s, buf);
|
2013-06-29 00:57:26 +02:00
|
|
|
}
|
|
|
|
|
2013-01-14 16:52:30 +01:00
|
|
|
protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
|
2008-11-19 08:22:15 +01:00
|
|
|
}
|