From 389845b4c94b6ee0dbca10c6716d9e1cc5c98d23 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 4 Apr 2012 07:40:50 +0200 Subject: [PATCH 001/134] Make sure Channel connected event is not fired on connect failure. See #249 --- .../java/io/netty/channel/sctp/SctpWorker.java | 16 +++++++--------- .../io/netty/channel/socket/nio/NioWorker.java | 9 ++++----- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java index 10f597fcad..2c11779ead 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java @@ -215,7 +215,6 @@ public class SctpWorker extends NioWorker { protected void connect(SelectionKey k) { final SctpClientChannel ch = (SctpClientChannel) k.attachment(); try { - // TODO: Remove cast if (ch.getJdkChannel().finishConnect()) { registerTask(ch, ch.connectFuture); } @@ -358,7 +357,13 @@ public class SctpWorker extends NioWorker { ((SctpChannelImpl) channel).setConnected(); future.setSuccess(); } - + + if (!server) { + if (!((SctpClientChannel) channel).boundManually) { + fireChannelBound(channel, localAddress); + } + fireChannelConnected(channel, remoteAddress); + } } catch (IOException e) { if (future != null) { future.setFailure(e); @@ -369,13 +374,6 @@ public class SctpWorker extends NioWorker { "Failed to register a socket to the selector.", e); } } - - if (!server) { - if (!((SctpClientChannel) channel).boundManually) { - fireChannelBound(channel, localAddress); - } - fireChannelConnected(channel, remoteAddress); - } } @Override diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java index 4809133d53..92c18307c3 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java @@ -140,6 +140,10 @@ public class NioWorker extends AbstractNioWorker { } future.setSuccess(); } + if (server || !((NioClientSocketChannel) channel).boundManually) { + fireChannelBound(channel, localAddress); + } + fireChannelConnected(channel, remoteAddress); } catch (IOException e) { if (future != null) { @@ -151,11 +155,6 @@ public class NioWorker extends AbstractNioWorker { "Failed to register a socket to the selector.", e); } } - - if (server || !((NioClientSocketChannel) channel).boundManually) { - fireChannelBound(channel, localAddress); - } - fireChannelConnected(channel, remoteAddress); } } From c8679fe52c95336adc90421dce47a2187d86170b Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 6 Apr 2012 11:09:51 +0200 Subject: [PATCH 002/134] Catch Throwable --- .../testsuite/transport/AbstractSocketServerBootstrapTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java index 242c02feb5..6c6a634140 100644 --- a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java @@ -56,7 +56,7 @@ public abstract class AbstractSocketServerBootstrapTest { try { s = SctpChannel.open(); bufSizeModifiable = s.supportedOptions().contains(SctpStandardSocketOptions.SO_RCVBUF); - } catch (Exception e) { + } catch (Throwable e) { bufSizeModifiable = false; System.err.println( "SCTP SO_RCVBUF does not work: " + e); From d2d859d22c5a7199654df2dd04f8656a265de047 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 6 Apr 2012 21:00:49 +0200 Subject: [PATCH 003/134] Make sure multicast tests pass on all os'es --- .../transport/socket/AbstractDatagramMulticastTest.java | 3 +-- .../transport/socket/nio/oio/NioOioDatagramMulticastTest.java | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java index a4c6f08a33..7f62b2a14d 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java @@ -73,7 +73,7 @@ public abstract class AbstractDatagramMulticastTest { int port = TestUtils.getFreePort(); - NetworkInterface iface = NetworkInterface.getByInetAddress(InetAddress.getLoopbackAddress()); + NetworkInterface iface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); sb.setOption("networkInterface", iface); sb.setOption("reuseAddress", true); @@ -88,7 +88,6 @@ public abstract class AbstractDatagramMulticastTest { DatagramChannel cc = (DatagramChannel) cb.bind(new InetSocketAddress(port)); - assertTrue(cc.joinGroup(groupAddress, iface).awaitUninterruptibly().isSuccess()); assertTrue(sc.write(ChannelBuffers.wrapInt(1), groupAddress).awaitUninterruptibly().isSuccess()); diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java index 30c70aa019..ab5861ac9f 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java @@ -3,6 +3,7 @@ package io.netty.testsuite.transport.socket.nio.oio; import java.util.concurrent.Executor; import io.netty.channel.socket.DatagramChannelFactory; +import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannelFactory; import io.netty.channel.socket.oio.OioDatagramChannelFactory; import io.netty.testsuite.transport.socket.AbstractDatagramMulticastTest; @@ -16,7 +17,7 @@ public class NioOioDatagramMulticastTest extends AbstractDatagramMulticastTest { @Override protected DatagramChannelFactory newClientSocketChannelFactory(Executor executor) { - return new NioDatagramChannelFactory(executor); + return new NioDatagramChannelFactory(executor, NioDatagramChannel.ProtocolFamily.INET); } From 778f4a3cbcb203a47fb5400c268481809e20770d Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 7 Apr 2012 21:56:58 +0200 Subject: [PATCH 004/134] Make sure Future get notified before event is fired. See #254 --- .../src/main/java/io/netty/channel/socket/oio/OioWorker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java index 5d4eb6aa3b..fa4578d3d4 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java @@ -110,13 +110,13 @@ class OioWorker extends AbstractOioWorker { a.getBytes(a.readerIndex(), out, length); } } - + + future.setSuccess(); if (iothread) { fireWriteComplete(channel, length); } else { fireWriteCompleteLater(channel, length); } - future.setSuccess(); } catch (Throwable t) { // Convert 'SocketException: Socket closed' to From dd14b8d9e84785e144c481929cbea7bc60714786 Mon Sep 17 00:00:00 2001 From: vibul Date: Mon, 9 Apr 2012 14:33:45 +1000 Subject: [PATCH 005/134] Issue #250. Implement web socket close frame status code and reason text. --- .../http/websocketx/CloseWebSocketFrame.java | 102 +++++++++++++++++- .../websocketx/WebSocket08FrameDecoder.java | 37 ++++++- .../autobahn/AutobahnServerHandler.java | 5 +- 3 files changed, 139 insertions(+), 5 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java index 1f8ed46028..8ed9acb20b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/CloseWebSocketFrame.java @@ -15,7 +15,9 @@ */ package io.netty.handler.codec.http.websocketx; +import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; +import io.netty.util.CharsetUtil; /** * Web Socket Frame for closing the connection @@ -30,7 +32,20 @@ public class CloseWebSocketFrame extends WebSocketFrame { } /** - * Creates a new close frame + * Creates a new empty close frame with closing status code and reason text + * + * @param statusCode + * Integer status code as per RFC 6455. For + * example, 1000 indicates normal closure. + * @param reasonText + * Reason text. Set to null if no text. + */ + public CloseWebSocketFrame(int statusCode, String reasonText) { + this(true, 0, statusCode, reasonText); + } + + /** + * Creates a new close frame with no losing status code and no reason text * * @param finalFragment * flag indicating if this frame is the final fragment @@ -38,8 +53,93 @@ public class CloseWebSocketFrame extends WebSocketFrame { * reserved bits used for protocol extensions */ public CloseWebSocketFrame(boolean finalFragment, int rsv) { + this(finalFragment, rsv, null); + } + + /** + * Creates a new close frame with closing status code and reason text + * + * @param finalFragment + * flag indicating if this frame is the final fragment + * @param rsv + * reserved bits used for protocol extensions + * @param statusCode + * Integer status code as per RFC 6455. For + * example, 1000 indicates normal closure. + * @param reasonText + * Reason text. Set to null if no text. + */ + public CloseWebSocketFrame(boolean finalFragment, int rsv, int statusCode, String reasonText) { setFinalFragment(finalFragment); setRsv(rsv); + + byte[] reasonBytes = new byte[0]; + if (reasonText != null) { + reasonBytes = reasonText.getBytes(CharsetUtil.UTF_8); + } + + ChannelBuffer binaryData = ChannelBuffers.buffer(2 + reasonBytes.length); + binaryData.writeShort(statusCode); + if (reasonBytes.length > 0) { + binaryData.writeBytes(reasonBytes); + } + + binaryData.readerIndex(0); + setBinaryData(binaryData); + } + + /** + * Creates a new close frame + * + * @param finalFragment + * flag indicating if this frame is the final fragment + * @param rsv + * reserved bits used for protocol extensions + * @param binaryData + * the content of the frame. Must be 2 byte integer followed by optional UTF-8 encoded string. + */ + public CloseWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { + setFinalFragment(finalFragment); + setRsv(rsv); + if (binaryData == null) { + setBinaryData(ChannelBuffers.EMPTY_BUFFER); + } else { + setBinaryData(binaryData); + } + } + + /** + * Returns the closing status code as per RFC 6455. If + * a status code is set, -1 is returned. + */ + public int getStatusCode() { + ChannelBuffer binaryData = this.getBinaryData(); + if (binaryData == null || binaryData.capacity() == 0) { + return -1; + } + + binaryData.readerIndex(0); + int statusCode = binaryData.readShort(); + binaryData.readerIndex(0); + + return statusCode; + } + + /** + * Returns the reason text as per RFC 6455 If a reason + * text is not supplied, an empty string is returned. + */ + public String getReasonText() { + ChannelBuffer binaryData = this.getBinaryData(); + if (binaryData == null || binaryData.capacity() <= 2) { + return ""; + } + + binaryData.readerIndex(2); + String reasonText = binaryData.toString(CharsetUtil.UTF_8); + binaryData.readerIndex(0); + + return reasonText; } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java index 5bd7ea0038..b590c11d8a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java @@ -284,8 +284,9 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder= 0 && statusCode <= 999) || (statusCode >= 1004 && statusCode <= 1006) + || (statusCode >= 1012 && statusCode <= 2999)) { + protocolViolation(channel, "Invalid close frame status code: " + statusCode); + } + + // May have UTF-8 message + if (buffer.readableBytes() > 0) { + byte[] b = new byte[buffer.readableBytes()]; + buffer.readBytes(b); + try { + new UTF8Output(b); + } catch (UTF8Exception ex) { + protocolViolation(channel, "Invalid close frame reason text. Invalid UTF-8 bytes"); + } + } + + // Restore reader index + buffer.readerIndex(idx); + } } diff --git a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java index 0729d0b5ad..d04cb842c4 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java @@ -82,11 +82,10 @@ public class AutobahnServerHandler extends SimpleChannelUpstreamHandler { private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { if (logger.isDebugEnabled()) { - logger.debug(String - .format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); + logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass() + .getSimpleName())); } - if (frame instanceof CloseWebSocketFrame) { this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame); } else if (frame instanceof PingWebSocketFrame) { From 968b9103b3d9dcdb96c177aea21720c8883ee161 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 9 Apr 2012 16:35:56 +0200 Subject: [PATCH 006/134] Add test case to show that issue #235 is due some incorrect usage --- .../netty/channel/local/LocalAddressTest.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 transport/src/test/java/io/netty/channel/local/LocalAddressTest.java diff --git a/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java b/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java new file mode 100644 index 0000000000..6d51b65a7b --- /dev/null +++ b/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2011 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.local; + +import org.junit.Assert; +import org.junit.Test; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.Channels; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPipelineFactory; +import io.netty.channel.SimpleChannelUpstreamHandler; +import io.netty.channel.local.DefaultLocalClientChannelFactory; +import io.netty.channel.local.DefaultLocalServerChannelFactory; +import io.netty.channel.local.LocalAddress; + +public class LocalAddressTest { + private static String LOCAL_ADDR_ID = "test.id"; + + @Test + public void localConnectOK() + throws Exception { + + ClientBootstrap cb = new ClientBootstrap(new DefaultLocalClientChannelFactory()); + ServerBootstrap sb = new ServerBootstrap(new DefaultLocalServerChannelFactory()); + + cb.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() + throws Exception { + + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("test.handler", new TestHandler()); + return pipeline; + } + }); + + sb.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() + throws Exception { + + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("test.handler", new TestHandler()); + return pipeline; + } + }); + + LocalAddress addr = new LocalAddress(LOCAL_ADDR_ID); + + // Start server + Channel channel = sb.bind(addr); + + // Connect to the server + ChannelFuture connectFuture = cb.connect(addr); + connectFuture.awaitUninterruptibly(); + + // Send a message event up the pipeline. + Channels.fireMessageReceived(connectFuture.getChannel(), "Hello, World"); + + // Close the channel + connectFuture.getChannel().close(); + + // Wait until the connection is closed, or the connection attempt fails + connectFuture.getChannel().getCloseFuture().awaitUninterruptibly(); + + // close the server channel + channel.close().awaitUninterruptibly(); + + sb.releaseExternalResources(); + cb.releaseExternalResources(); + + Assert.assertTrue(String.format("Expected null, got channel '%s' for local address '%s'", LocalChannelRegistry.getChannel(addr), addr), LocalChannelRegistry.getChannel(addr) == null); + } + + @Test + public void localConnectAgain() + throws Exception { + + ClientBootstrap cb = new ClientBootstrap(new DefaultLocalClientChannelFactory()); + ServerBootstrap sb = new ServerBootstrap(new DefaultLocalServerChannelFactory()); + + cb.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() + throws Exception { + + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("test.handler", new TestHandler()); + return pipeline; + } + }); + + sb.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() + throws Exception { + + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("test.handler", new TestHandler()); + return pipeline; + } + }); + + LocalAddress addr = new LocalAddress(LOCAL_ADDR_ID); + + // Start server + Channel channel = sb.bind(addr); + + // Connect to the server + ChannelFuture connectFuture = cb.connect(addr); + connectFuture.awaitUninterruptibly(); + + // Send a message event up the pipeline. + Channels.fireMessageReceived(connectFuture.getChannel(), "Hello, World"); + + // Close the channel + connectFuture.getChannel().close(); + + // Wait until the connection is closed, or the connection attempt fails + connectFuture.getChannel().getCloseFuture().awaitUninterruptibly(); + + // close the server channel + channel.close().awaitUninterruptibly(); + + sb.releaseExternalResources(); + cb.releaseExternalResources(); + + Assert.assertTrue(String.format("Expected null, got channel '%s' for local address '%s'", LocalChannelRegistry.getChannel(addr), addr), LocalChannelRegistry.getChannel(addr) == null); + } + + public static class TestHandler + extends SimpleChannelUpstreamHandler { + + public TestHandler() { + } + + @Override + public void handleUpstream(ChannelHandlerContext ctx, + ChannelEvent e) + throws Exception { + + System.err.println(String.format("Received upstream event '%s'", e)); + } + } +} \ No newline at end of file From 32d327ede2e521b00cd65eba3153637494b80d8a Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 9 Apr 2012 20:00:20 +0200 Subject: [PATCH 007/134] Take care of releasing the local channel when releaseExternalResources() is called. See #235 --- .../channel/local/DefaultLocalServerChannelFactory.java | 9 +++++++-- .../java/io/netty/channel/local/LocalAddressTest.java | 9 ++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java b/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java index 94a637b70c..025f7d06ca 100644 --- a/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java @@ -17,6 +17,7 @@ package io.netty.channel.local; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelSink; +import io.netty.channel.group.DefaultChannelGroup; /** * The default {@link LocalServerChannelFactory} implementation. @@ -24,11 +25,15 @@ import io.netty.channel.ChannelSink; */ public class DefaultLocalServerChannelFactory implements LocalServerChannelFactory { + private final DefaultChannelGroup group = new DefaultChannelGroup(); + private final ChannelSink sink = new LocalServerChannelSink(); @Override public LocalServerChannel newChannel(ChannelPipeline pipeline) { - return DefaultLocalServerChannel.create(this, pipeline, sink); + LocalServerChannel channel = DefaultLocalServerChannel.create(this, pipeline, sink); + group.add(channel); + return channel; } /** @@ -37,6 +42,6 @@ public class DefaultLocalServerChannelFactory implements LocalServerChannelFacto */ @Override public void releaseExternalResources() { - // Unused + group.close(); } } diff --git a/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java b/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java index 6d51b65a7b..a51642e7bf 100644 --- a/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java +++ b/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java @@ -69,7 +69,7 @@ public class LocalAddressTest { LocalAddress addr = new LocalAddress(LOCAL_ADDR_ID); // Start server - Channel channel = sb.bind(addr); + sb.bind(addr); // Connect to the server ChannelFuture connectFuture = cb.connect(addr); @@ -84,9 +84,6 @@ public class LocalAddressTest { // Wait until the connection is closed, or the connection attempt fails connectFuture.getChannel().getCloseFuture().awaitUninterruptibly(); - // close the server channel - channel.close().awaitUninterruptibly(); - sb.releaseExternalResources(); cb.releaseExternalResources(); @@ -127,7 +124,7 @@ public class LocalAddressTest { LocalAddress addr = new LocalAddress(LOCAL_ADDR_ID); // Start server - Channel channel = sb.bind(addr); + sb.bind(addr); // Connect to the server ChannelFuture connectFuture = cb.connect(addr); @@ -142,8 +139,6 @@ public class LocalAddressTest { // Wait until the connection is closed, or the connection attempt fails connectFuture.getChannel().getCloseFuture().awaitUninterruptibly(); - // close the server channel - channel.close().awaitUninterruptibly(); sb.releaseExternalResources(); cb.releaseExternalResources(); From 70458316873ac9f1185553cc7cdcab6be80a70b5 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 9 Apr 2012 20:02:33 +0200 Subject: [PATCH 008/134] Correct javadoc. See #235 --- .../netty/channel/local/DefaultLocalServerChannelFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java b/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java index 025f7d06ca..0ee61332e2 100644 --- a/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java @@ -37,8 +37,8 @@ public class DefaultLocalServerChannelFactory implements LocalServerChannelFacto } /** - * Does nothing because this implementation does not require any external - * resources. + * Release all the previous created channels. This takes care of calling {@link LocalChannelRegistry#unregister(LocalAddress)} + * for each if them. */ @Override public void releaseExternalResources() { From 16bbd313300799acf516df4abe145cacf5cbae28 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 9 Apr 2012 20:07:45 +0200 Subject: [PATCH 009/134] Await for close of the channels. See #235 --- .../netty/channel/local/DefaultLocalServerChannelFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java b/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java index 0ee61332e2..7df60dcd27 100644 --- a/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/local/DefaultLocalServerChannelFactory.java @@ -42,6 +42,6 @@ public class DefaultLocalServerChannelFactory implements LocalServerChannelFacto */ @Override public void releaseExternalResources() { - group.close(); + group.close().awaitUninterruptibly(); } } From 1314db9c0ad4b36863eb9ffac0fac64875ccd197 Mon Sep 17 00:00:00 2001 From: norman Date: Tue, 10 Apr 2012 08:12:10 +0200 Subject: [PATCH 010/134] Fix a NPE in a testcase when running via ubuntu --- .../transport/socket/AbstractDatagramMulticastTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java index 7f62b2a14d..15c5095f6e 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java @@ -28,7 +28,6 @@ import io.netty.channel.socket.DatagramChannelFactory; import io.netty.testsuite.util.TestUtils; import io.netty.util.internal.ExecutorUtil; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.util.concurrent.CountDownLatch; @@ -73,7 +72,7 @@ public abstract class AbstractDatagramMulticastTest { int port = TestUtils.getFreePort(); - NetworkInterface iface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); + NetworkInterface iface = NetworkInterface.getNetworkInterfaces().nextElement(); sb.setOption("networkInterface", iface); sb.setOption("reuseAddress", true); From beedd26e1c81467e41730c743374abb0b33f7c67 Mon Sep 17 00:00:00 2001 From: norman Date: Tue, 10 Apr 2012 10:04:40 +0200 Subject: [PATCH 011/134] Add missing license header --- .../nio/oio/NioOioDatagramMulticastTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java index ab5861ac9f..337618a1e4 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/nio/oio/NioOioDatagramMulticastTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2011 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ package io.netty.testsuite.transport.socket.nio.oio; import java.util.concurrent.Executor; From f17e56b33bfb725474015e19cdddc5d1254ea989 Mon Sep 17 00:00:00 2001 From: norman Date: Tue, 10 Apr 2012 11:18:34 +0200 Subject: [PATCH 012/134] Accept all ready sockets for the SelectionKey. See #240 --- .../channel/socket/nio/AbstractNioWorker.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java index c65b6ff451..1db5154b27 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -464,17 +464,22 @@ abstract class AbstractNioWorker implements Worker { protected boolean accept(SelectionKey key) { NioServerSocketChannel channel = (NioServerSocketChannel) key.attachment(); try { - SocketChannel acceptedSocket = channel.socket.accept(); - if (acceptedSocket != null) { - + boolean handled = false; + + // accept all sockets that are waiting atm + for (;;) { + SocketChannel acceptedSocket = channel.socket.accept(); + if (acceptedSocket == null) { + break; + } // TODO: Remove the casting stuff ChannelPipeline pipeline = channel.getConfig().getPipelineFactory().getPipeline(); registerTask(NioAcceptedSocketChannel.create(channel.getFactory(), pipeline, channel, channel.getPipeline().getSink(), acceptedSocket, (NioWorker) this), null); - return true; + handled = true; } - return false; + return handled; } catch (SocketTimeoutException e) { // Thrown every second to get ClosedChannelException // raised. From 4436f60e2f92cdcd86b42a95a3eb5e0955cb7064 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 10 Apr 2012 21:00:44 +0200 Subject: [PATCH 013/134] Workaround to have multicast tests work on osx, linux and windows --- .../transport/socket/AbstractDatagramMulticastTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java index 15c5095f6e..673688e10d 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java @@ -28,6 +28,7 @@ import io.netty.channel.socket.DatagramChannelFactory; import io.netty.testsuite.util.TestUtils; import io.netty.util.internal.ExecutorUtil; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.util.concurrent.CountDownLatch; @@ -72,7 +73,13 @@ public abstract class AbstractDatagramMulticastTest { int port = TestUtils.getFreePort(); - NetworkInterface iface = NetworkInterface.getNetworkInterfaces().nextElement(); + NetworkInterface iface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); + + // check if the NetworkInterface is null, this is the case on my ubuntu dev machine but not on osx and windows. + // if so fail back the the first interface + if (iface == null) { + iface = NetworkInterface.getByIndex(0); + } sb.setOption("networkInterface", iface); sb.setOption("reuseAddress", true); From a37d7bb5f3f1463d08295a1d465ed9cf9475eee8 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 11 Apr 2012 08:09:03 +0200 Subject: [PATCH 014/134] Make sure multicast tests pass on all os'es --- .../transport/socket/AbstractDatagramMulticastTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java index 673688e10d..efdff083d0 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java @@ -78,7 +78,8 @@ public abstract class AbstractDatagramMulticastTest { // check if the NetworkInterface is null, this is the case on my ubuntu dev machine but not on osx and windows. // if so fail back the the first interface if (iface == null) { - iface = NetworkInterface.getByIndex(0); + // use nextElement() as NetWorkInterface.getByIndex(0) returns null + iface = NetworkInterface.getNetworkInterfaces().nextElement(); } sb.setOption("networkInterface", iface); sb.setOption("reuseAddress", true); From 470c1a898a6cdb5d20993e63d9e5b83c298290a9 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 11 Apr 2012 08:45:51 +0200 Subject: [PATCH 015/134] Fix a bug which lead to only use two threads for all tasks all the time, even if the WorkerPool contained more. See #240 --- .../io/netty/channel/socket/nio/AbstractNioWorker.java | 6 ++++-- .../netty/channel/socket/nio/NioServerSocketChannel.java | 8 +++++--- .../channel/socket/nio/NioServerSocketChannelFactory.java | 4 ++-- .../channel/socket/nio/NioServerSocketPipelineSink.java | 8 +------- .../main/java/io/netty/channel/socket/nio/NioWorker.java | 3 +-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java index 1db5154b27..c1df2bdcbd 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -475,8 +475,10 @@ abstract class AbstractNioWorker implements Worker { // TODO: Remove the casting stuff ChannelPipeline pipeline = channel.getConfig().getPipelineFactory().getPipeline(); - registerTask(NioAcceptedSocketChannel.create(channel.getFactory(), pipeline, channel, - channel.getPipeline().getSink(), acceptedSocket, (NioWorker) this), null); + NioWorker worker = channel.workers.nextWorker(); + + worker.registerWithWorker(NioAcceptedSocketChannel.create(channel.getFactory(), pipeline, channel, + channel.getPipeline().getSink(), acceptedSocket, worker), null); handled = true; } return handled; diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java index 608537e90a..db7bfc9a80 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java @@ -42,14 +42,15 @@ final class NioServerSocketChannel extends AbstractServerChannel final ServerSocketChannel socket; final Lock shutdownLock = new ReentrantLock(); final NioWorker worker; + final WorkerPool workers; private final ServerSocketChannelConfig config; static NioServerSocketChannel create(ChannelFactory factory, - ChannelPipeline pipeline, ChannelSink sink, NioWorker worker) { + ChannelPipeline pipeline, ChannelSink sink, NioWorker worker, WorkerPool workers) { NioServerSocketChannel instance = - new NioServerSocketChannel(factory, pipeline, sink, worker); + new NioServerSocketChannel(factory, pipeline, sink, worker, workers); fireChannelOpen(instance); return instance; } @@ -57,10 +58,11 @@ final class NioServerSocketChannel extends AbstractServerChannel private NioServerSocketChannel( ChannelFactory factory, ChannelPipeline pipeline, - ChannelSink sink, NioWorker worker) { + ChannelSink sink, NioWorker worker, WorkerPool workers) { super(factory, pipeline, sink); this.worker = worker; + this.workers = workers; try { socket = ServerSocketChannel.open(); } catch (IOException e) { diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java index 57ed6ecc34..33b0c4fa67 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java @@ -137,13 +137,13 @@ public class NioServerSocketChannelFactory implements ServerSocketChannelFactory } this.workerPool = workerPool; - sink = new NioServerSocketPipelineSink(workerPool); + sink = new NioServerSocketPipelineSink(); } @Override public ServerSocketChannel newChannel(ChannelPipeline pipeline) { - return NioServerSocketChannel.create(this, pipeline, sink, workerPool.nextWorker()); + return NioServerSocketChannel.create(this, pipeline, sink, workerPool.nextWorker(), workerPool); } @Override diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java index dbcf749f74..278f5d8873 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java @@ -35,12 +35,6 @@ class NioServerSocketPipelineSink extends AbstractNioChannelSink { static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketPipelineSink.class); - private final WorkerPool workerPool; - - NioServerSocketPipelineSink(WorkerPool workerPool) { - this.workerPool = workerPool; - } - @Override public void eventSunk( ChannelPipeline pipeline, ChannelEvent e) throws Exception { @@ -124,7 +118,7 @@ class NioServerSocketPipelineSink extends AbstractNioChannelSink { future.setSuccess(); fireChannelBound(channel, channel.getLocalAddress()); - workerPool.nextWorker().registerWithWorker(channel, future); + channel.getWorker().registerWithWorker(channel, future); } catch (Throwable t) { future.setFailure(t); diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java index 92c18307c3..3c1554f567 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java @@ -127,8 +127,7 @@ public class NioWorker extends AbstractNioWorker { boolean registered = channel.getJdkChannel().isRegistered(); if (!registered) { synchronized (channel.interestOpsLock) { - channel.getJdkChannel().register( - selector, channel.getRawInterestOps(), channel); + channel.getJdkChannel().register(selector, channel.getRawInterestOps(), channel); } } else { From 5b53b66fbf456de0bb2aff1092e6baa53221514f Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 11 Apr 2012 09:15:02 +0200 Subject: [PATCH 016/134] Allow to share a WorkerPool for boss and worker threads but also allow to have them separate. See #240 --- .../nio/NioServerSocketChannelFactory.java | 72 +++++++++++++++---- .../channel/socket/nio/SelectorUtil.java | 3 + 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java index 33b0c4fa67..f86f3a5065 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java @@ -86,64 +86,110 @@ public class NioServerSocketChannelFactory implements ServerSocketChannelFactory private final WorkerPool workerPool; private final ChannelSink sink; + private WorkerPool bossWorkerPool; /** * Create a new {@link NioServerSocketChannelFactory} using - * {@link Executors#newCachedThreadPool()} for the worker. + * {@link Executors#newCachedThreadPool()} for the workers. * * See {@link #NioServerSocketChannelFactory(Executor, Executor)} */ public NioServerSocketChannelFactory() { - this(Executors.newCachedThreadPool()); + this(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()); } /** * Creates a new instance. Calling this constructor is same with calling - * {@link #NioServerSocketChannelFactory(Executor, Executor, int)} with 2 * - * the number of available processors in the machine. The number of + * {@link #NioServerSocketChannelFactory(Executor, Executor, int, int)} with 1 + * as boss count and 2 * the number of available processors in the machine. The number of * available processors is obtained by {@link Runtime#availableProcessors()}. * - + * @param bossExecutor + * the {@link Executor} which will execute the I/O worker threads that handle the accepting of new connections * @param workerExecutor * the {@link Executor} which will execute the I/O worker threads */ - public NioServerSocketChannelFactory(Executor workerExecutor) { - this(workerExecutor, SelectorUtil.DEFAULT_IO_THREADS); + public NioServerSocketChannelFactory(Executor bossExecutor, Executor workerExecutor) { + this(bossExecutor, workerExecutor, SelectorUtil.DEFAULT_IO_ACCEPTING_THREADS, SelectorUtil.DEFAULT_IO_THREADS); } /** * Creates a new instance. - * + * + * @param bossExecutor + * the {@link Executor} which will execute the I/O worker threads that handle the accepting of new connections * @param workerExecutor * the {@link Executor} which will execute the I/O worker threads + * @param bossCount + * the maximum number of I/O worker threads that handling the accepting of connections * @param workerCount * the maximum number of I/O worker threads */ - public NioServerSocketChannelFactory(Executor workerExecutor, + public NioServerSocketChannelFactory(Executor bossExecutor, Executor workerExecutor, int bossCount, int workerCount) { - this(new NioWorkerPool(workerExecutor, workerCount, true)); + this(new NioWorkerPool(bossExecutor, bossCount, true), new NioWorkerPool(workerExecutor, workerCount, true)); } /** * Creates a new instance. * + * @param bossWorkerPool + * the {@link WorkerPool} which will be used to obtain the {@link Worker} that execute the I/O worker threads that handle the accepting of new connections * @param workerPool * the {@link WorkerPool} which will be used to obtain the {@link Worker} that execute the I/O worker threads */ - public NioServerSocketChannelFactory(WorkerPool workerPool) { + public NioServerSocketChannelFactory(WorkerPool bossWorkerPool, WorkerPool workerPool) { + if (bossWorkerPool == null) { + throw new NullPointerException("bossWorkerPool"); + } if (workerPool == null) { throw new NullPointerException("workerPool"); } - + this.bossWorkerPool = bossWorkerPool; this.workerPool = workerPool; sink = new NioServerSocketPipelineSink(); } + /** + * Creates a new instance which use the given {@link WorkerPool} for everything. + * + * @param genericExecutor + * the {@link Executor} which will execute the I/O worker threads ( this also includes handle the accepting of new connections) + * @param workerCount + * the maximum number of I/O worker threads + * + */ + public NioServerSocketChannelFactory(Executor genericExecutor, int workerCount) { + this(new NioWorkerPool(genericExecutor, workerCount, true)); + } + + /** + * Creates a new instance which use the given {@link WorkerPool} for everything. + * + * @param genericExecutor + * the {@link Executor} which will execute the I/O worker threads ( this also includes handle the accepting of new connections) + * + */ + public NioServerSocketChannelFactory(Executor genericExecutor) { + this(genericExecutor, SelectorUtil.DEFAULT_IO_ACCEPTING_THREADS + SelectorUtil.DEFAULT_IO_THREADS); + } + + + /** + * Creates a new instance which use the given {@link WorkerPool} for everything. + * + * @param genericWorkerPool + * the {@link WorkerPool} which will be used to obtain the {@link Worker} that execute the I/O worker threads (that included accepting of new connections) + */ + public NioServerSocketChannelFactory(WorkerPool genericWorkerPool) { + this(genericWorkerPool, genericWorkerPool); + } + @Override public ServerSocketChannel newChannel(ChannelPipeline pipeline) { - return NioServerSocketChannel.create(this, pipeline, sink, workerPool.nextWorker(), workerPool); + return NioServerSocketChannel.create(this, pipeline, sink, bossWorkerPool.nextWorker(), workerPool); } @Override diff --git a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java index 8f03731e11..9e41302ea7 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java @@ -28,6 +28,9 @@ public final class SelectorUtil { public static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2; + public static final int DEFAULT_IO_ACCEPTING_THREADS = 1; + + // Workaround for JDK NIO bug. // // See: From 962a67dc03c453f9c37a4f1cf7a2d8a08af516b4 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 11 Apr 2012 09:16:27 +0200 Subject: [PATCH 017/134] Add final keyword --- .../netty/channel/socket/nio/NioServerSocketChannelFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java index f86f3a5065..aa94a6a5f2 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java @@ -86,7 +86,7 @@ public class NioServerSocketChannelFactory implements ServerSocketChannelFactory private final WorkerPool workerPool; private final ChannelSink sink; - private WorkerPool bossWorkerPool; + private final WorkerPool bossWorkerPool; /** * Create a new {@link NioServerSocketChannelFactory} using From 3f8c13f138959ffc437e67c86ea33afe74d6c23f Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 11 Apr 2012 10:26:29 +0200 Subject: [PATCH 018/134] Fix regression in Zlib which was introduced while refactoring the code. See #255 --- .../netty/util/internal/jzlib/InfBlocks.java | 106 ++++++++++-------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java b/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java index e51d5ee454..7a3ac68a70 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java @@ -124,15 +124,16 @@ final class InfBlocks { int m; // bytes to end of window or read pointer // copy input/output information to locals (UPDATE macro restores) - - p = z.next_in_index; - n = z.avail_in; - b = bitb; - k = bitk; - - q = write; - m = q < read? read - q - 1 : end - q; - + { + p = z.next_in_index; + n = z.avail_in; + b = bitb; + k = bitk; + } + { + q = write; + m = q < read? read - q - 1 : end - q; + } // process input based on current state while (true) { @@ -160,17 +161,20 @@ final class InfBlocks { switch (t >>> 1) { case 0: // stored - + { b >>>= 3; k -= 3; - + } t = k & 7; // go to byte boundary - b >>>= t; - k -= t; + { + b >>>= t; + k -= t; + } mode = LENS; // get length of stored block break; case 1: // fixed + { int[] bl = new int[1]; int[] bd = new int[1]; int[][] tl = new int[1][]; @@ -178,24 +182,30 @@ final class InfBlocks { InfTree.inflate_trees_fixed(bl, bd, tl, td); codes.init(bl[0], bd[0], tl[0], 0, td[0], 0); + } - b >>>= 3; - k -= 3; + { + b >>>= 3; + k -= 3; + } mode = CODES; break; case 2: // dynamic + { b >>>= 3; k -= 3; + } mode = TABLE; break; case 3: // illegal + { b >>>= 3; k -= 3; - + } mode = BAD; z.msg = "invalid block type"; r = JZlib.Z_DATA_ERROR; @@ -342,9 +352,10 @@ final class InfBlocks { } } - - b >>>= 14; - k -= 14; + { + b >>>= 14; + k -= 14; + } index = 0; mode = BTREE; @@ -369,9 +380,10 @@ final class InfBlocks { blens[border[index ++]] = b & 7; - b >>>= 3; - k -= 3; - + { + b >>>= 3; + k -= 3; + } } while (index < 19) { @@ -493,36 +505,36 @@ final class InfBlocks { } tb[0] = -1; + { + int[] bl = new int[1]; + int[] bd = new int[1]; + int[] tl = new int[1]; + int[] td = new int[1]; + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions - int[] bl = new int[1]; - int[] bd = new int[1]; - int[] tl = new int[1]; - int[] td = new int[1]; - bl[0] = 9; // must be <= 9 for lookahead assumptions - bd[0] = 6; // must be <= 9 for lookahead assumptions + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + (t >> 5 & 0x1f), blens, bl, bd, tl, td, hufts, + z); - t = table; - t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), - 1 + (t >> 5 & 0x1f), blens, bl, bd, tl, td, hufts, - z); + if (t != JZlib.Z_OK) { + if (t == JZlib.Z_DATA_ERROR) { + blens = null; + mode = BAD; + } + r = t; - if (t != JZlib.Z_OK) { - if (t == JZlib.Z_DATA_ERROR) { - blens = null; - mode = BAD; + bitb = b; + bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + write = q; + return inflate_flush(z, r); } - r = t; - - bitb = b; - bitk = k; - z.avail_in = n; - z.total_in += p - z.next_in_index; - z.next_in_index = p; - write = q; - return inflate_flush(z, r); + codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); } - codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); - mode = CODES; case CODES: bitb = b; From 941e71de36e478b795c4ea0e8fd15dede05d1b7d Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 12 Apr 2012 09:29:59 +0200 Subject: [PATCH 019/134] cleanup comments --- .../java/io/netty/channel/socket/nio/AbstractNioWorker.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java index c1df2bdcbd..994c92ce9f 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -430,7 +430,7 @@ abstract class AbstractNioWorker implements Worker { int readyOps = k.readyOps(); if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) { if (!read(k)) { - // Connection already closed - no need to handle write. + // Connection already closed - no need to handle write / accept / connect. continue; } } @@ -472,7 +472,6 @@ abstract class AbstractNioWorker implements Worker { if (acceptedSocket == null) { break; } - // TODO: Remove the casting stuff ChannelPipeline pipeline = channel.getConfig().getPipelineFactory().getPipeline(); NioWorker worker = channel.workers.nextWorker(); From b9c60bd5187a15171b255e43f57b2798b98ebf56 Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 12 Apr 2012 10:22:10 +0200 Subject: [PATCH 020/134] Throw a PrematureChannelClosureException if the channel was closed before all responses were received for the sent requests. See #256 --- .../handler/codec/http/HttpClientCodec.java | 47 ++++++++++++++++-- .../PrematureChannelClosureException.java | 48 +++++++++++++++++++ .../io/netty/handler/codec/package-info.java | 21 ++++++++ 3 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 codec/src/main/java/io/netty/handler/codec/PrematureChannelClosureException.java create mode 100644 codec/src/main/java/io/netty/handler/codec/package-info.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java index 8049b79a90..cd16cca75f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java @@ -16,13 +16,16 @@ package io.netty.handler.codec.http; import java.util.Queue; +import java.util.concurrent.atomic.AtomicLong; import io.netty.buffer.ChannelBuffer; import io.netty.channel.Channel; import io.netty.channel.ChannelDownstreamHandler; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelStateEvent; import io.netty.channel.ChannelUpstreamHandler; +import io.netty.handler.codec.PrematureChannelClosureException; import io.netty.util.internal.QueueFactory; /** @@ -33,6 +36,10 @@ import io.netty.util.internal.QueueFactory; * {@link HttpResponseDecoder} to learn what additional state management needs * to be done for HEAD and CONNECT and why * {@link HttpResponseDecoder} can not handle it by itself. + * + * If the {@link Channel} gets closed and there are requests missing for a response + * a {@link PrematureChannelClosureException} is thrown. + * * @see HttpServerCodec * * @apiviz.has io.netty.handler.codec.http.HttpResponseDecoder @@ -49,7 +56,8 @@ public class HttpClientCodec implements ChannelUpstreamHandler, private final HttpRequestEncoder encoder = new Encoder(); private final HttpResponseDecoder decoder; - + private final AtomicLong requestResponseCounter = new AtomicLong(0); + /** * Creates a new instance with the default decoder options * ({@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and @@ -87,8 +95,17 @@ public class HttpClientCodec implements ChannelUpstreamHandler, @Override protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { - if (msg instanceof HttpRequest && !done) { - queue.offer(((HttpRequest) msg).getMethod()); + if (msg instanceof HttpRequest) { + if (!done) { + queue.offer(((HttpRequest) msg).getMethod()); + } + requestResponseCounter.incrementAndGet(); + } else if (msg instanceof HttpChunk) { + + // increment only if its the last chunk + if (((HttpChunk) msg).isLast()) { + requestResponseCounter.incrementAndGet(); + } } return super.encode(ctx, channel, msg); } @@ -106,7 +123,17 @@ public class HttpClientCodec implements ChannelUpstreamHandler, if (done) { return buffer.readBytes(actualReadableBytes()); } else { - return super.decode(ctx, channel, buffer, state); + Object msg = super.decode(ctx, channel, buffer, state); + + if (msg != null) { + if (msg instanceof HttpMessage) { + requestResponseCounter.decrementAndGet(); + } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { + requestResponseCounter.decrementAndGet(); + } + } + + return msg; } } @@ -161,5 +188,17 @@ public class HttpClientCodec implements ChannelUpstreamHandler, return super.isContentAlwaysEmpty(msg); } + + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + super.channelClosed(ctx, e); + + long missingResponses = requestResponseCounter.get(); + if (missingResponses > 0) { + throw new PrematureChannelClosureException("Channel closed but still missing " + missingResponses + " response(s)"); + } + } + + } } diff --git a/codec/src/main/java/io/netty/handler/codec/PrematureChannelClosureException.java b/codec/src/main/java/io/netty/handler/codec/PrematureChannelClosureException.java new file mode 100644 index 0000000000..64be04fcbe --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/PrematureChannelClosureException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec; + +/** + * Exception which should get thrown if a Channel got closed before it is expected + */ +public class PrematureChannelClosureException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 233460005724966593L; + + public PrematureChannelClosureException() { + super(); + } + + public PrematureChannelClosureException(String msg) { + super(msg); + } + + + public PrematureChannelClosureException(String msg, Throwable t) { + super(msg, t); + } + + + public PrematureChannelClosureException(Throwable t) { + super(t); + } + + + +} diff --git a/codec/src/main/java/io/netty/handler/codec/package-info.java b/codec/src/main/java/io/netty/handler/codec/package-info.java new file mode 100644 index 0000000000..99f835a366 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Base package for codecs + * + */ +package io.netty.handler.codec; From 5ed04c3ada397d90f8d750175ed1d63ddc5bb86d Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 12 Apr 2012 10:34:16 +0200 Subject: [PATCH 021/134] Correctly handle chunked requests/responses. See #256 --- .../netty/handler/codec/http/HttpClientCodec.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java index cd16cca75f..ba28c41ab7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java @@ -99,13 +99,15 @@ public class HttpClientCodec implements ChannelUpstreamHandler, if (!done) { queue.offer(((HttpRequest) msg).getMethod()); } - requestResponseCounter.incrementAndGet(); - } else if (msg instanceof HttpChunk) { - // increment only if its the last chunk - if (((HttpChunk) msg).isLast()) { + // check if the request is chunked if so do not increment + if (!((HttpRequest) msg).isChunked()) { requestResponseCounter.incrementAndGet(); } + } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { + // increment as its the last chunk + requestResponseCounter.incrementAndGet(); + } return super.encode(ctx, channel, msg); } @@ -126,7 +128,8 @@ public class HttpClientCodec implements ChannelUpstreamHandler, Object msg = super.decode(ctx, channel, buffer, state); if (msg != null) { - if (msg instanceof HttpMessage) { + // check if its a HttpMessage and its not chunked + if (msg instanceof HttpMessage && !((HttpMessage) msg).isChunked()) { requestResponseCounter.decrementAndGet(); } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { requestResponseCounter.decrementAndGet(); From d363f73fd831be950a2ba2e39b7f51df1cee0fea Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 12 Apr 2012 10:55:18 +0200 Subject: [PATCH 022/134] Only increment the counter if the encode did not fail. See #256 --- .../handler/codec/http/HttpClientCodec.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java index ba28c41ab7..10404e8c16 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java @@ -95,21 +95,22 @@ public class HttpClientCodec implements ChannelUpstreamHandler, @Override protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { - if (msg instanceof HttpRequest) { - if (!done) { - queue.offer(((HttpRequest) msg).getMethod()); - } - - // check if the request is chunked if so do not increment - if (!((HttpRequest) msg).isChunked()) { - requestResponseCounter.incrementAndGet(); - } + if (msg instanceof HttpRequest && !done) { + queue.offer(((HttpRequest) msg).getMethod()); + } + + Object obj = super.encode(ctx, channel, msg); + + // check if the request is chunked if so do not increment + if (msg instanceof HttpRequest && !((HttpRequest) msg).isChunked()) { + requestResponseCounter.incrementAndGet(); } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { // increment as its the last chunk requestResponseCounter.incrementAndGet(); - } - return super.encode(ctx, channel, msg); + + return obj; + } } From db97e4eb35699538023fda58114149f11cf669a4 Mon Sep 17 00:00:00 2001 From: Cruz Bishop Date: Sun, 15 Apr 2012 17:29:15 +1000 Subject: [PATCH 023/134] Use a logger in SocketAddresses --- common/src/main/java/io/netty/util/SocketAddresses.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/io/netty/util/SocketAddresses.java b/common/src/main/java/io/netty/util/SocketAddresses.java index edc0ef6220..1069773ee5 100644 --- a/common/src/main/java/io/netty/util/SocketAddresses.java +++ b/common/src/main/java/io/netty/util/SocketAddresses.java @@ -15,12 +15,17 @@ */ package io.netty.util; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.net.InetAddress; import java.net.UnknownHostException; public final class SocketAddresses { public static final InetAddress LOCALHOST; + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SocketAddresses.class); static { // We cache this because some machine takes almost forever to return @@ -36,8 +41,7 @@ public final class SocketAddresses { try { localhost = InetAddress.getByAddress(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }); } catch (UnknownHostException e2) { - System.err.println("Failed to get the localhost."); - e2.printStackTrace(); + logger.error("Failed to resolve localhost", e2); } } } From efabc3c28549209ab060bed785ca6541b2e35474 Mon Sep 17 00:00:00 2001 From: Cruz Bishop Date: Sun, 15 Apr 2012 17:34:51 +1000 Subject: [PATCH 024/134] Use a logger in ZStream --- .../main/java/io/netty/util/internal/jzlib/ZStream.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/jzlib/ZStream.java b/common/src/main/java/io/netty/util/internal/jzlib/ZStream.java index 0d4f2a343a..6c87d71254 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/ZStream.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/ZStream.java @@ -47,9 +47,14 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.netty.util.internal.jzlib; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.internal.jzlib.JZlib.WrapperType; public final class ZStream { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(ZStream.class); public byte[] next_in; // next input byte public int next_in_index; @@ -181,10 +186,10 @@ public final class ZStream { next_out.length <= next_out_index || dstate.pending_buf.length < dstate.pending_out + len || next_out.length < next_out_index + len) { - System.out.println(dstate.pending_buf.length + ", " + + logger.info(dstate.pending_buf.length + ", " + dstate.pending_out + ", " + next_out.length + ", " + next_out_index + ", " + len); - System.out.println("avail_out=" + avail_out); + logger.info("avail_out=" + avail_out); } System.arraycopy(dstate.pending_buf, dstate.pending_out, next_out, From a20ab9184e7f13fcd8d73b00fc2d97ce983f09a4 Mon Sep 17 00:00:00 2001 From: Cruz Bishop Date: Sun, 15 Apr 2012 17:49:04 +1000 Subject: [PATCH 025/134] Use loggers in some more classes --- .../http/websocketx/autobahn/AutobahnServer.java | 7 ++++++- .../main/java/io/netty/handler/ipfilter/IpSubnet.java | 10 ++++++++-- .../java/io/netty/handler/ipfilter/IpV4Subnet.java | 8 +++++++- .../socket/AbstractSocketServerBootstrapTest.java | 11 +++++++---- .../transport/socket/AbstractSocketSslEchoTest.java | 6 ++---- .../transport/AbstractSocketServerBootstrapTest.java | 8 ++++++-- .../transport/AbstractSocketSslEchoTest.java | 6 ++---- .../netty/channel/socket/nio/AbstractNioWorker.java | 2 +- 8 files changed, 39 insertions(+), 19 deletions(-) diff --git a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java index 02615c3d51..ee911c8e34 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java @@ -20,12 +20,17 @@ import java.util.concurrent.Executors; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * A Web Socket echo server for running the autobahn test * suite */ public class AutobahnServer { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(AutobahnServer.class); private final int port; @@ -45,7 +50,7 @@ public class AutobahnServer { // Bind and start to accept incoming connections. bootstrap.bind(new InetSocketAddress(port)); - System.out.println("Web Socket Server started at port " + port); + logger.info("Web Socket Server started at port " + port); } public static void main(String[] args) { diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnet.java b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnet.java index 251fefb3dc..c89890ba94 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnet.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnet.java @@ -15,6 +15,8 @@ */ package io.netty.handler.ipfilter; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.net.InetAddress; import java.net.UnknownHostException; @@ -52,6 +54,10 @@ import java.net.UnknownHostException; * where inetAddress2 is 1fff:0:0a88:85a3:0:0:ac1f:8001
*/ public class IpSubnet implements IpSet, Comparable { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(IpSubnet.class); + /** Internal representation */ private CIDR cidr; @@ -152,10 +158,10 @@ public class IpSubnet implements IpSet, Comparable { } catch (UnknownHostException e) { return; } - System.out.println("IpSubnet: " + ipSubnet.toString() + " from " + ipSubnet.cidr.getBaseAddress() + " to " + logger.debug("IpSubnet: " + ipSubnet.toString() + " from " + ipSubnet.cidr.getBaseAddress() + " to " + ipSubnet.cidr.getEndAddress() + " mask " + ipSubnet.cidr.getMask()); if (args.length > 1) { - System.out.println("Is IN: " + args[1] + " " + ipSubnet.contains(args[1])); + logger.debug("Is IN: " + args[1] + " " + ipSubnet.contains(args[1])); } } } diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpV4Subnet.java b/handler/src/main/java/io/netty/handler/ipfilter/IpV4Subnet.java index 71d6837e5d..3e513c4c11 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpV4Subnet.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpV4Subnet.java @@ -15,6 +15,8 @@ */ package io.netty.handler.ipfilter; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.StringTokenizer; @@ -40,6 +42,10 @@ import java.util.Vector; * where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123
*/ public class IpV4Subnet implements IpSet, Comparable { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(IpV4Subnet.class); + private static final int SUBNET_MASK = 0x80000000; private static final int BYTE_ADDRESS_MASK = 0xFF; @@ -263,7 +269,7 @@ public class IpV4Subnet implements IpSet, Comparable { return; } if (args.length > 1) { - System.out.println("Is IN: " + args[1] + " " + ipV4Subnet.contains(args[1])); + logger.debug("Is IN: " + args[1] + " " + ipV4Subnet.contains(args[1])); } } } diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketServerBootstrapTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketServerBootstrapTest.java index 63438d2be7..70164219b1 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketServerBootstrapTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketServerBootstrapTest.java @@ -37,6 +37,8 @@ import io.netty.channel.ChildChannelStateEvent; import io.netty.channel.ServerChannelFactory; import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.channel.socket.SocketChannelConfig; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.testsuite.util.DummyHandler; import io.netty.util.SocketAddresses; import io.netty.util.internal.ExecutorUtil; @@ -51,6 +53,9 @@ import org.junit.Test; * An abstract test class to test server socket bootstraps */ public abstract class AbstractSocketServerBootstrapTest { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(AbstractSocketServerBootstrapTest.class); private static final boolean BUFSIZE_MODIFIABLE; @@ -66,13 +71,11 @@ public abstract class AbstractSocketServerBootstrapTest { } } catch (Exception e) { bufSizeModifiable = false; - System.err.println( - "Socket.getReceiveBufferSize() does not work: " + e); + logger.error("Socket.getReceiveBufferSize() does not work: " + e); } } catch (Exception e) { bufSizeModifiable = false; - System.err.println( - "Socket.setReceiveBufferSize() does not work: " + e); + logger.error("Socket.setReceiveBufferSize() does not work: " + e); } finally { BUFSIZE_MODIFIABLE = bufSizeModifiable; try { diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java index a9906d3917..fb20eec8c8 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java @@ -322,8 +322,7 @@ public abstract class AbstractSocketSslEchoTest { // You should do something in the real world. // You will reach here only if you enabled client certificate auth, // as described in SecureChatSslContextFactory. - System.err.println( - "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); + logger.error("UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); } @Override @@ -331,8 +330,7 @@ public abstract class AbstractSocketSslEchoTest { X509Certificate[] chain, String authType) throws CertificateException { // Always trust - it is an example. // You should do something in the real world. - System.err.println( - "UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); + logger.error("UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); } }; diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java index 6c6a634140..76b5c2140d 100644 --- a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java @@ -22,6 +22,8 @@ import io.netty.bootstrap.ClientBootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.sctp.SctpChannelConfig; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.testsuite.util.DummyHandler; import io.netty.testsuite.util.SctpTestUtil; import io.netty.util.internal.ExecutorUtil; @@ -46,6 +48,9 @@ import static org.junit.Assert.assertEquals; * An abstract test class to test server socket bootstraps */ public abstract class AbstractSocketServerBootstrapTest { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(AbstractSocketServerBootstrapTest.class); private static final boolean BUFSIZE_MODIFIABLE; @@ -58,8 +63,7 @@ public abstract class AbstractSocketServerBootstrapTest { bufSizeModifiable = s.supportedOptions().contains(SctpStandardSocketOptions.SO_RCVBUF); } catch (Throwable e) { bufSizeModifiable = false; - System.err.println( - "SCTP SO_RCVBUF does not work: " + e); + logger.error("SCTP SO_RCVBUF does not work: " + e); } finally { BUFSIZE_MODIFIABLE = bufSizeModifiable; try { diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java index 12996df3e0..803b08237c 100644 --- a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java @@ -312,8 +312,7 @@ public abstract class AbstractSocketSslEchoTest { // You should do something in the real world. // You will reach here only if you enabled client certificate auth, // as described in SecureChatSslContextFactory. - System.err.println( - "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); + logger.error("UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); } @Override @@ -321,8 +320,7 @@ public abstract class AbstractSocketSslEchoTest { X509Certificate[] chain, String authType) throws CertificateException { // Always trust - it is an example. // You should do something in the real world. - System.err.println( - "UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); + logger.error("UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); } }; diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java index 994c92ce9f..03423f4d0f 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -887,7 +887,7 @@ abstract class AbstractNioWorker implements Worker { if (iothread) { fireExceptionCaught(channel, t); } else { - System.out.println(thread + "==" + channel.getWorker().thread); + logger.debug(thread + "==" + channel.getWorker().thread); fireExceptionCaughtLater(channel, t); } } From a682b018b29b79aebcc935f9f6213725bd5904a5 Mon Sep 17 00:00:00 2001 From: Cruz Bishop Date: Sun, 15 Apr 2012 19:18:35 +1000 Subject: [PATCH 026/134] More logging --- .../netty/example/discard/DiscardClient.java | 7 +- .../io/netty/example/echo/EchoClient.java | 7 +- .../example/factorial/FactorialClient.java | 10 ++- .../file/HttpStaticFileServerHandler.java | 7 +- .../example/http/snoop/HttpSnoopClient.java | 9 ++- .../http/snoop/HttpSnoopClientHandler.java | 28 +++---- .../example/http/upload/HttpUploadClient.java | 19 +++-- .../http/upload/HttpUploadClientHandler.java | 26 ++++--- .../http/upload/HttpUploadServerHandler.java | 8 +- .../websocketx/client/WebSocketClient.java | 13 +++- .../client/WebSocketClientHandler.java | 15 ++-- .../websocketx/server/WebSocketServer.java | 9 ++- .../sslserver/WebSocketSslServer.java | 13 +++- .../local/LocalExampleMultithreaded.java | 12 ++- .../local/LocalServerPipelineFactory.java | 7 +- .../example/localtime/LocalTimeClient.java | 11 ++- .../example/objectecho/ObjectEchoClient.java | 7 +- .../io/netty/example/proxy/HexDumpProxy.java | 9 ++- .../example/qotm/QuoteOfTheMomentClient.java | 7 +- .../qotm/QuoteOfTheMomentClientHandler.java | 9 ++- .../io/netty/example/redis/RedisClient.java | 16 ++-- .../io/netty/example/sctp/SctpClient.java | 7 +- .../example/securechat/SecureChatClient.java | 7 +- .../securechat/SecureChatClientHandler.java | 15 ++-- .../SecureChatTrustManagerFactory.java | 9 ++- .../io/netty/example/telnet/TelnetClient.java | 7 +- .../example/telnet/TelnetClientHandler.java | 15 ++-- .../io/netty/example/uptime/UptimeClient.java | 7 +- .../socket/http/HttpTunnelSoakTester.java | 77 +++++++++---------- .../sctp/SctpMultiHomingEchoTest.java | 8 +- .../socket/nio/NioProviderMetadata.java | 7 +- .../netty/channel/local/LocalAddressTest.java | 8 +- 32 files changed, 272 insertions(+), 144 deletions(-) diff --git a/example/src/main/java/io/netty/example/discard/DiscardClient.java b/example/src/main/java/io/netty/example/discard/DiscardClient.java index 6ad36ebaeb..b60c365359 100644 --- a/example/src/main/java/io/netty/example/discard/DiscardClient.java +++ b/example/src/main/java/io/netty/example/discard/DiscardClient.java @@ -24,11 +24,16 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipelineFactory; import io.netty.channel.Channels; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Keeps sending random data to the specified address. */ public class DiscardClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(DiscardClient.class); private final String host; private final int port; @@ -67,7 +72,7 @@ public class DiscardClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length < 2 || args.length > 3) { - System.err.println( + logger.error( "Usage: " + DiscardClient.class.getSimpleName() + " []"); return; diff --git a/example/src/main/java/io/netty/example/echo/EchoClient.java b/example/src/main/java/io/netty/example/echo/EchoClient.java index 10a878547d..bf0e9b5979 100644 --- a/example/src/main/java/io/netty/example/echo/EchoClient.java +++ b/example/src/main/java/io/netty/example/echo/EchoClient.java @@ -24,6 +24,8 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipelineFactory; import io.netty.channel.Channels; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Sends one message when a connection is open and echoes back any received @@ -32,6 +34,9 @@ import io.netty.channel.socket.nio.NioClientSocketChannelFactory; * the server. */ public class EchoClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(EchoClient.class); private final String host; private final int port; @@ -70,7 +75,7 @@ public class EchoClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length < 2 || args.length > 3) { - System.err.println( + logger.error( "Usage: " + EchoClient.class.getSimpleName() + " []"); return; diff --git a/example/src/main/java/io/netty/example/factorial/FactorialClient.java b/example/src/main/java/io/netty/example/factorial/FactorialClient.java index 88da01caed..932b0f295b 100644 --- a/example/src/main/java/io/netty/example/factorial/FactorialClient.java +++ b/example/src/main/java/io/netty/example/factorial/FactorialClient.java @@ -22,12 +22,17 @@ import io.netty.bootstrap.ClientBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Sends a sequence of integers to a {@link FactorialServer} to calculate * the factorial of the specified integer. */ public class FactorialClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(FactorialClient.class); private final String host; private final int port; @@ -60,8 +65,7 @@ public class FactorialClient { (FactorialClientHandler) channel.getPipeline().getLast(); // Print out the answer. - System.err.format( - "Factorial of %,d is: %,d", count, handler.getFactorial()); + logger.info(String.format("Factorial of %,d is: %,d", count, handler.getFactorial())); // Shut down all thread pools to exit. bootstrap.releaseExternalResources(); @@ -70,7 +74,7 @@ public class FactorialClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length != 3) { - System.err.println( + logger.error( "Usage: " + FactorialClient.class.getSimpleName() + " "); return; diff --git a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java index 8b73a41b0d..21814e30e6 100644 --- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java +++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java @@ -54,6 +54,8 @@ import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.ssl.SslHandler; import io.netty.handler.stream.ChunkedFile; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.CharsetUtil; /** @@ -103,6 +105,9 @@ import io.netty.util.CharsetUtil; * */ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpStaticFileServerHandler.class); public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; public static final String HTTP_DATE_GMT_TIMEZONE = "GMT"; @@ -188,7 +193,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler { @Override public void operationProgressed( ChannelFuture future, long amount, long current, long total) { - System.out.printf("%s: %d / %d (+%d)%n", path, current, total, amount); + logger.info(String.format("%s: %d / %d (+%d)%n", path, current, total, amount)); } }); } diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java index 914d83dae3..211233212a 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java @@ -29,12 +29,17 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * A simple HTTP client that prints out the content of the HTTP response to * {@link System#out} to test {@link HttpSnoopServer}. */ public class HttpSnoopClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpSnoopClient.class); private final URI uri; @@ -55,7 +60,7 @@ public class HttpSnoopClient { } if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) { - System.err.println("Only HTTP(S) is supported."); + logger.error("Only HTTP(S) is supported."); return; } @@ -105,7 +110,7 @@ public class HttpSnoopClient { public static void main(String[] args) throws Exception { if (args.length != 1) { - System.err.println( + logger.error( "Usage: " + HttpSnoopClient.class.getSimpleName() + " "); return; diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientHandler.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientHandler.java index 0e5d83e703..f88cbc7835 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientHandler.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientHandler.java @@ -21,9 +21,14 @@ import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.handler.codec.http.HttpChunk; import io.netty.handler.codec.http.HttpResponse; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.CharsetUtil; public class HttpSnoopClientHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpSnoopClientHandler.class); private boolean readingChunks; @@ -32,38 +37,35 @@ public class HttpSnoopClientHandler extends SimpleChannelUpstreamHandler { if (!readingChunks) { HttpResponse response = (HttpResponse) e.getMessage(); - System.out.println("STATUS: " + response.getStatus()); - System.out.println("VERSION: " + response.getProtocolVersion()); - System.out.println(); - + logger.info("STATUS: " + response.getStatus()); + logger.info("VERSION: " + response.getProtocolVersion()); + if (!response.getHeaderNames().isEmpty()) { for (String name: response.getHeaderNames()) { for (String value: response.getHeaders(name)) { - System.out.println("HEADER: " + name + " = " + value); + logger.info("HEADER: " + name + " = " + value); } } - System.out.println(); } if (response.isChunked()) { readingChunks = true; - System.out.println("CHUNKED CONTENT {"); + logger.info("CHUNKED CONTENT {"); } else { ChannelBuffer content = response.getContent(); if (content.readable()) { - System.out.println("CONTENT {"); - System.out.println(content.toString(CharsetUtil.UTF_8)); - System.out.println("} END OF CONTENT"); + logger.info("CONTENT {"); + logger.info(content.toString(CharsetUtil.UTF_8)); + logger.info("} END OF CONTENT"); } } } else { HttpChunk chunk = (HttpChunk) e.getMessage(); if (chunk.isLast()) { readingChunks = false; - System.out.println("} END OF CHUNKED CONTENT"); + logger.info("} END OF CHUNKED CONTENT"); } else { - System.out.print(chunk.getContent().toString(CharsetUtil.UTF_8)); - System.out.flush(); + logger.info(chunk.getContent().toString(CharsetUtil.UTF_8)); } } } diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java index eb8f527d22..258548f313 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java @@ -41,11 +41,14 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.InterfaceHttpData; import io.netty.handler.codec.http.QueryStringEncoder; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; -/** - */ public class HttpUploadClient { + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpUploadClient.class); + private final String baseUri; private final String filePath; @@ -69,7 +72,7 @@ public class HttpUploadClient { try { uriSimple = new URI(postSimple); } catch (URISyntaxException e) { - System.err.println("Error: " + e.getMessage()); + logger.error("Invalid URI syntax" + e.getCause()); return; } String scheme = uriSimple.getScheme() == null? "http" : uriSimple.getScheme(); @@ -84,7 +87,7 @@ public class HttpUploadClient { } if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) { - System.err.println("Only HTTP(S) is supported."); + logger.error("Only HTTP(S) is supported."); return; } @@ -94,12 +97,12 @@ public class HttpUploadClient { try { uriFile = new URI(postFile); } catch (URISyntaxException e) { - System.err.println("Error: " + e.getMessage()); + logger.error("Error: " + e.getMessage()); return; } File file = new File(filePath); if (! file.canRead()) { - System.err.println("A correct path is needed"); + logger.error("A correct path is needed"); return; } @@ -176,7 +179,7 @@ public class HttpUploadClient { try { uriGet = new URI(encoder.toString()); } catch (URISyntaxException e) { - System.err.println("Error: " + e.getMessage()); + logger.error("Error: " + e.getMessage()); bootstrap.releaseExternalResources(); return null; } @@ -377,7 +380,7 @@ public class HttpUploadClient { public static void main(String[] args) { if (args.length != 2) { - System.err.println( + logger.error( "Usage: " + HttpUploadClient.class.getSimpleName() + " baseURI filePath"); return; diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java index 6ad6fe52f0..f25308f672 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java @@ -22,9 +22,14 @@ import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.handler.codec.http.HttpChunk; import io.netty.handler.codec.http.HttpResponse; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.CharsetUtil; public class HttpUploadClientHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpUploadClientHandler.class); private volatile boolean readingChunks; @@ -33,38 +38,35 @@ public class HttpUploadClientHandler extends SimpleChannelUpstreamHandler { if (!readingChunks) { HttpResponse response = (HttpResponse) e.getMessage(); - System.out.println("STATUS: " + response.getStatus()); - System.out.println("VERSION: " + response.getProtocolVersion()); - System.out.println(); + logger.info("STATUS: " + response.getStatus()); + logger.info("VERSION: " + response.getProtocolVersion()); if (!response.getHeaderNames().isEmpty()) { for (String name: response.getHeaderNames()) { for (String value: response.getHeaders(name)) { - System.out.println("HEADER: " + name + " = " + value); + logger.info("HEADER: " + name + " = " + value); } } - System.out.println(); } if (response.getStatus().getCode() == 200 && response.isChunked()) { readingChunks = true; - System.out.println("CHUNKED CONTENT {"); + logger.info("CHUNKED CONTENT {"); } else { ChannelBuffer content = response.getContent(); if (content.readable()) { - System.out.println("CONTENT {"); - System.out.println(content.toString(CharsetUtil.UTF_8)); - System.out.println("} END OF CONTENT"); + logger.info("CONTENT {"); + logger.info(content.toString(CharsetUtil.UTF_8)); + logger.info("} END OF CONTENT"); } } } else { HttpChunk chunk = (HttpChunk) e.getMessage(); if (chunk.isLast()) { readingChunks = false; - System.out.println("} END OF CHUNKED CONTENT"); + logger.info("} END OF CHUNKED CONTENT"); } else { - System.out.print(chunk.getContent().toString(CharsetUtil.UTF_8)); - System.out.flush(); + logger.info(chunk.getContent().toString(CharsetUtil.UTF_8)); } } } diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java index e6c4974e56..15579d10a0 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java @@ -58,9 +58,14 @@ import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.InterfaceHttpData; import io.netty.handler.codec.http.InterfaceHttpData.HttpDataType; import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.CharsetUtil; public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpUploadServerHandler.class); private volatile HttpRequest request; @@ -480,8 +485,7 @@ public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - e.getCause().printStackTrace(); - System.err.println(responseContent.toString()); + logger.error(responseContent.toString(), e.getCause()); e.getChannel().close(); } } diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java index 32dced428b..a2a53d553d 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java +++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java @@ -57,8 +57,13 @@ import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; public class WebSocketClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(WebSocketClient.class); private final URI uri; @@ -101,7 +106,7 @@ public class WebSocketClient { }); // Connect - System.out.println("WebSocket Client connecting"); + logger.info("WebSocket Client connecting"); ChannelFuture future = bootstrap.connect( new InetSocketAddress(uri.getHost(), uri.getPort())); @@ -111,17 +116,17 @@ public class WebSocketClient { handshaker.handshake(ch).awaitUninterruptibly().rethrowIfFailed(); // Send 10 messages and wait for responses - System.out.println("WebSocket Client sending message"); + logger.info("WebSocket Client sending message"); for (int i = 0; i < 10; i++) { ch.write(new TextWebSocketFrame("Message #" + i)); } // Ping - System.out.println("WebSocket Client sending ping"); + logger.info("WebSocket Client sending ping"); ch.write(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[]{1, 2, 3, 4, 5, 6}))); // Close - System.out.println("WebSocket Client sending close"); + logger.info("WebSocket Client sending close"); ch.write(new CloseWebSocketFrame()); // WebSocketClientHandler will close the connection when the server diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java index 2c0be64576..006cb314aa 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java @@ -49,9 +49,14 @@ import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.CharsetUtil; public class WebSocketClientHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(WebSocketClientHandler.class); private final WebSocketClientHandshaker handshaker; @@ -61,7 +66,7 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler { @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { - System.out.println("WebSocket Client disconnected!"); + logger.debug("WebSocket Client disconnected!"); } @Override @@ -69,7 +74,7 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler { Channel ch = ctx.getChannel(); if (!handshaker.isHandshakeComplete()) { handshaker.finishHandshake(ch, (HttpResponse) e.getMessage()); - System.out.println("WebSocket Client connected!"); + logger.debug("WebSocket Client connected!"); return; } @@ -82,11 +87,11 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler { WebSocketFrame frame = (WebSocketFrame) e.getMessage(); if (frame instanceof TextWebSocketFrame) { TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; - System.out.println("WebSocket Client received message: " + textFrame.getText()); + logger.info("WebSocket Client received message: " + textFrame.getText()); } else if (frame instanceof PongWebSocketFrame) { - System.out.println("WebSocket Client received pong"); + logger.info("WebSocket Client received pong"); } else if (frame instanceof CloseWebSocketFrame) { - System.out.println("WebSocket Client received closing"); + logger.info("WebSocket Client received closing"); ch.close(); } } diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java index 63fe021a20..c48129bbda 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java @@ -20,6 +20,8 @@ import java.util.concurrent.Executors; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * A HTTP server which serves Web Socket requests at: @@ -41,6 +43,9 @@ import io.netty.channel.socket.nio.NioServerSocketChannelFactory; * */ public class WebSocketServer { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(WebSocketServer.class); private final int port; @@ -58,8 +63,8 @@ public class WebSocketServer { // Bind and start to accept incoming connections. bootstrap.bind(new InetSocketAddress(port)); - System.out.println("Web socket server started at port " + port + '.'); - System.out.println("Open your browser and navigate to http://localhost:" + port + '/'); + logger.info("Web socket server started at port " + port + '.'); + logger.info("Open your browser and navigate to http://localhost:" + port + '/'); } public static void main(String[] args) { diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java index 3d93387fa3..d9375dc6c9 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java @@ -20,6 +20,8 @@ import java.util.concurrent.Executors; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * A HTTP server which serves Web Socket requests at: @@ -40,6 +42,9 @@ import io.netty.channel.socket.nio.NioServerSocketChannelFactory; * */ public class WebSocketSslServer { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(WebSocketSslServer.class); private final int port; @@ -57,8 +62,8 @@ public class WebSocketSslServer { // Bind and start to accept incoming connections. bootstrap.bind(new InetSocketAddress(port)); - System.out.println("Web socket server started at port " + port + '.'); - System.out.println("Open your browser and navigate to https://localhost:" + port + '/'); + logger.info("Web socket server started at port " + port + '.'); + logger.info("Open your browser and navigate to https://localhost:" + port + '/'); } public static void main(String[] args) { @@ -71,13 +76,13 @@ public class WebSocketSslServer { String keyStoreFilePath = System.getProperty("keystore.file.path"); if (keyStoreFilePath == null || keyStoreFilePath.isEmpty()) { - System.out.println("ERROR: System property keystore.file.path not set. Exiting now!"); + logger.error("ERROR: System property keystore.file.path not set. Exiting now!"); System.exit(1); } String keyStoreFilePassword = System.getProperty("keystore.file.password"); if (keyStoreFilePassword == null || keyStoreFilePassword.isEmpty()) { - System.out.println("ERROR: System property keystore.file.password not set. Exiting now!"); + logger.error("ERROR: System property keystore.file.password not set. Exiting now!"); System.exit(1); } diff --git a/example/src/main/java/io/netty/example/local/LocalExampleMultithreaded.java b/example/src/main/java/io/netty/example/local/LocalExampleMultithreaded.java index 0a4e68f234..14f5c74ffd 100644 --- a/example/src/main/java/io/netty/example/local/LocalExampleMultithreaded.java +++ b/example/src/main/java/io/netty/example/local/LocalExampleMultithreaded.java @@ -31,8 +31,13 @@ import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import io.netty.handler.logging.LoggingHandler; import io.netty.logging.InternalLogLevel; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; public class LocalExampleMultithreaded { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(LocalExampleMultithreaded.class); private final String port; @@ -69,12 +74,11 @@ public class LocalExampleMultithreaded { // Read commands from array String[] commands = { "First", "Second", "Third", "quit" }; for (int j = 0; j < 5 ; j++) { - System.err.println("Start " + j); + logger.info("Start " + j); ChannelFuture channelFuture = cb.connect(socketAddress); channelFuture.awaitUninterruptibly(); if (! channelFuture.isSuccess()) { - System.err.println("CANNOT CONNECT"); - channelFuture.getCause().printStackTrace(); + logger.error("CANNOT CONNECT", channelFuture.getCause()); break; } ChannelFuture lastWriteFuture = null; @@ -90,7 +94,7 @@ public class LocalExampleMultithreaded { channelFuture.getChannel().close(); // Wait until the connection is closed or the connection attempt fails. channelFuture.getChannel().getCloseFuture().awaitUninterruptibly(); - System.err.println("End " + j); + logger.info("End " + j); } // Release all resources diff --git a/example/src/main/java/io/netty/example/local/LocalServerPipelineFactory.java b/example/src/main/java/io/netty/example/local/LocalServerPipelineFactory.java index 3c28c5d764..bc69fcb876 100644 --- a/example/src/main/java/io/netty/example/local/LocalServerPipelineFactory.java +++ b/example/src/main/java/io/netty/example/local/LocalServerPipelineFactory.java @@ -28,8 +28,13 @@ import io.netty.channel.MessageEvent; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.execution.ExecutionHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; public class LocalServerPipelineFactory implements ChannelPipelineFactory { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(LocalServerPipelineFactory.class); private final ExecutionHandler executionHandler; @@ -71,7 +76,7 @@ public class LocalServerPipelineFactory implements ChannelPipelineFactory { Channels.close(e.getChannel()); return; } - System.err.println("SERVER:" + msg); + logger.error("SERVER:" + msg); // Write back Channels.write(e.getChannel(), msg); } diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java index fc4d2bba8a..f922a2bcef 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java @@ -26,12 +26,17 @@ import io.netty.bootstrap.ClientBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Sends a list of continent/city pairs to a {@link LocalTimeServer} to * get the local times of the specified cities. */ public class LocalTimeClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(LocalTimeClient.class); private final String host; private final int port; @@ -99,10 +104,10 @@ public class LocalTimeClient { } private static void printUsage() { - System.err.println( + logger.error( "Usage: " + LocalTimeClient.class.getSimpleName() + " ..."); - System.err.println( + logger.error( "Example: " + LocalTimeClient.class.getSimpleName() + " localhost 8080 America/New_York Asia/Seoul"); } @@ -111,7 +116,7 @@ public class LocalTimeClient { List cities = new ArrayList(); for (int i = offset; i < args.length; i ++) { if (!args[i].matches("^[_A-Za-z]+/[_A-Za-z]+$")) { - System.err.println("Syntax error: '" + args[i] + "'"); + logger.error("Syntax error: '" + args[i] + "'"); printUsage(); return null; } diff --git a/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java b/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java index 38c84f7887..06fd815bec 100644 --- a/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java +++ b/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java @@ -24,6 +24,8 @@ import io.netty.example.echo.EchoClient; import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ObjectDecoder; import io.netty.handler.codec.serialization.ObjectEncoder; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.net.InetSocketAddress; import java.util.concurrent.Executors; @@ -32,6 +34,9 @@ import java.util.concurrent.Executors; * Modification of {@link EchoClient} which utilizes Java object serialization. */ public class ObjectEchoClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(ObjectEchoClient.class); private final String host; private final int port; @@ -68,7 +73,7 @@ public class ObjectEchoClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length < 2 || args.length > 3) { - System.err.println( + logger.error( "Usage: " + ObjectEchoClient.class.getSimpleName() + " []"); return; diff --git a/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java b/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java index 920ca9b0bd..ffa9f58098 100644 --- a/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java +++ b/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java @@ -23,8 +23,13 @@ import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.ClientSocketChannelFactory; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; public class HexDumpProxy { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HexDumpProxy.class); private final int localPort; private final String remoteHost; @@ -37,7 +42,7 @@ public class HexDumpProxy { } public void run() { - System.err.println( + logger.info( "Proxying *:" + localPort + " to " + remoteHost + ':' + remotePort + " ..."); @@ -60,7 +65,7 @@ public class HexDumpProxy { public static void main(String[] args) throws Exception { // Validate command line options. if (args.length != 3) { - System.err.println( + logger.error( "Usage: " + HexDumpProxy.class.getSimpleName() + " "); return; diff --git a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java index 986bb2b701..2fafcc7140 100644 --- a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java +++ b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java @@ -28,6 +28,8 @@ import io.netty.channel.socket.DatagramChannelFactory; import io.netty.channel.socket.nio.NioDatagramChannelFactory; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.CharsetUtil; /** @@ -37,6 +39,9 @@ import io.netty.util.CharsetUtil; * Inspired by the official Java tutorial. */ public class QuoteOfTheMomentClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(QuoteOfTheMomentClient.class); private final int port; @@ -86,7 +91,7 @@ public class QuoteOfTheMomentClient { // response is received. If the channel is not closed within 5 seconds, // print an error message and quit. if (!c.getCloseFuture().awaitUninterruptibly(5000)) { - System.err.println("QOTM request timed out."); + logger.error("QOTM request timed out."); c.close().awaitUninterruptibly(); } diff --git a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClientHandler.java b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClientHandler.java index 163272616b..857a7ec180 100644 --- a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClientHandler.java +++ b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClientHandler.java @@ -19,15 +19,20 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ExceptionEvent; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; public class QuoteOfTheMomentClientHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(QuoteOfTheMomentClientHandler.class); @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { String msg = (String) e.getMessage(); if (msg.startsWith("QOTM: ")) { - System.out.println("Quote of the Moment: " + msg.substring(6)); + logger.info("Quote of the Moment: " + msg.substring(6)); e.getChannel().close(); } } @@ -35,7 +40,7 @@ public class QuoteOfTheMomentClientHandler extends SimpleChannelUpstreamHandler @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - e.getCause().printStackTrace(); + logger.error("Exception caught", e.getCause()); e.getChannel().close(); } } diff --git a/example/src/main/java/io/netty/example/redis/RedisClient.java b/example/src/main/java/io/netty/example/redis/RedisClient.java index 46f4bb3f19..7d0e9f1320 100644 --- a/example/src/main/java/io/netty/example/redis/RedisClient.java +++ b/example/src/main/java/io/netty/example/redis/RedisClient.java @@ -27,6 +27,8 @@ import io.netty.handler.codec.redis.RedisDecoder; import io.netty.handler.codec.redis.RedisEncoder; import io.netty.handler.codec.redis.Reply; import io.netty.handler.queue.BlockingReadHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.io.IOException; import java.net.InetSocketAddress; @@ -36,6 +38,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public final class RedisClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(RedisClient.class); + private static final byte[] VALUE = "value".getBytes(); public static void main(String[] args) throws Exception { @@ -56,9 +62,9 @@ public final class RedisClient { Channel channel = redis.getChannel(); channel.write(new Command("set", "1", "value")); - System.out.print(blockingReadHandler.read()); + logger.info(blockingReadHandler.read().toString()); channel.write(new Command("get", "1")); - System.out.print(blockingReadHandler.read()); + logger.info(blockingReadHandler.read().toString()); int CALLS = 1000000; int PIPELINE = 50; @@ -85,7 +91,7 @@ public final class RedisClient { } } long end = System.currentTimeMillis(); - System.out.println(CALLS * 1000 / (end - start) + " calls per second"); + logger.info(CALLS * 1000 / (end - start) + " calls per second"); } private static void pipelinedIndividualRequests(BlockingReadHandler blockingReadHandler, Channel channel, long CALLS, int PIPELINE) throws IOException, InterruptedException { @@ -101,7 +107,7 @@ public final class RedisClient { } } long end = System.currentTimeMillis(); - System.out.println(CALLS * 1000 / (end - start) + " calls per second"); + logger.info(CALLS * 1000 / (end - start) + " calls per second"); } private static void requestResponse(BlockingReadHandler blockingReadHandler, Channel channel, int CALLS) throws IOException, InterruptedException { @@ -112,7 +118,7 @@ public final class RedisClient { blockingReadHandler.read(); } long end = System.currentTimeMillis(); - System.out.println(CALLS * 1000 / (end - start) + " calls per second"); + logger.info(CALLS * 1000 / (end - start) + " calls per second"); } private RedisClient() { diff --git a/example/src/main/java/io/netty/example/sctp/SctpClient.java b/example/src/main/java/io/netty/example/sctp/SctpClient.java index c093e15906..bee064210d 100644 --- a/example/src/main/java/io/netty/example/sctp/SctpClient.java +++ b/example/src/main/java/io/netty/example/sctp/SctpClient.java @@ -23,6 +23,8 @@ import io.netty.channel.Channels; import io.netty.channel.sctp.SctpClientSocketChannelFactory; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.net.InetSocketAddress; import java.util.concurrent.Executors; @@ -31,6 +33,9 @@ import java.util.concurrent.Executors; * Simple SCTP Echo Client */ public class SctpClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SctpClient.class); private final String host; private final int port; @@ -77,7 +82,7 @@ public class SctpClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length != 2) { - System.err.println( + logger.error( "Usage: " + SctpClient.class.getSimpleName() + " "); return; diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatClient.java b/example/src/main/java/io/netty/example/securechat/SecureChatClient.java index 856afbc862..0204359ed1 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatClient.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatClient.java @@ -26,11 +26,16 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; import io.netty.example.telnet.TelnetClient; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Simple SSL chat client modified from {@link TelnetClient}. */ public class SecureChatClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SecureChatClient.class); private final String host; private final int port; @@ -96,7 +101,7 @@ public class SecureChatClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length != 2) { - System.err.println( + logger.error( "Usage: " + SecureChatClient.class.getSimpleName() + " "); return; diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatClientHandler.java b/example/src/main/java/io/netty/example/securechat/SecureChatClientHandler.java index 87af04e02f..b11feebc6d 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatClientHandler.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatClientHandler.java @@ -15,9 +15,7 @@ */ package io.netty.example.securechat; -import java.util.logging.Level; -import java.util.logging.Logger; - +import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelStateEvent; @@ -25,14 +23,16 @@ import io.netty.channel.ExceptionEvent; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.handler.ssl.SslHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Handles a client-side channel. */ public class SecureChatClientHandler extends SimpleChannelUpstreamHandler { - private static final Logger logger = Logger.getLogger( - SecureChatClientHandler.class.getName()); + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SecureChatClientHandler.class); @Override public void handleUpstream( @@ -57,14 +57,13 @@ public class SecureChatClientHandler extends SimpleChannelUpstreamHandler { @Override public void messageReceived( ChannelHandlerContext ctx, MessageEvent e) { - System.err.println(e.getMessage()); + logger.error((String) e.getMessage()); } @Override public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e) { - logger.log( - Level.WARNING, + logger.warn( "Unexpected exception from downstream.", e.getCause()); e.getChannel().close(); diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java b/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java index b978b51cce..c15261da69 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java @@ -15,6 +15,8 @@ */ package io.netty.example.securechat; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -31,6 +33,9 @@ import javax.net.ssl.X509TrustManager; * even if it is invalid. */ public class SecureChatTrustManagerFactory extends TrustManagerFactorySpi { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SecureChatTrustManagerFactory.class); private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { @Override @@ -45,7 +50,7 @@ public class SecureChatTrustManagerFactory extends TrustManagerFactorySpi { // You should do something in the real world. // You will reach here only if you enabled client certificate auth, // as described in SecureChatSslContextFactory. - System.err.println( + logger.error( "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); } @@ -54,7 +59,7 @@ public class SecureChatTrustManagerFactory extends TrustManagerFactorySpi { X509Certificate[] chain, String authType) throws CertificateException { // Always trust - it is an example. // You should do something in the real world. - System.err.println( + logger.error( "UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); } }; diff --git a/example/src/main/java/io/netty/example/telnet/TelnetClient.java b/example/src/main/java/io/netty/example/telnet/TelnetClient.java index fa87123759..9fa4f475f1 100644 --- a/example/src/main/java/io/netty/example/telnet/TelnetClient.java +++ b/example/src/main/java/io/netty/example/telnet/TelnetClient.java @@ -25,11 +25,16 @@ import io.netty.bootstrap.ClientBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Simplistic telnet client. */ public class TelnetClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(TelnetClient.class); private final String host; private final int port; @@ -95,7 +100,7 @@ public class TelnetClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length != 2) { - System.err.println( + logger.error( "Usage: " + TelnetClient.class.getSimpleName() + " "); return; diff --git a/example/src/main/java/io/netty/example/telnet/TelnetClientHandler.java b/example/src/main/java/io/netty/example/telnet/TelnetClientHandler.java index 78aa5d9f9c..d85fb02fac 100644 --- a/example/src/main/java/io/netty/example/telnet/TelnetClientHandler.java +++ b/example/src/main/java/io/netty/example/telnet/TelnetClientHandler.java @@ -15,23 +15,23 @@ */ package io.netty.example.telnet; -import java.util.logging.Level; -import java.util.logging.Logger; - +import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelStateEvent; import io.netty.channel.ExceptionEvent; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Handles a client-side channel. */ public class TelnetClientHandler extends SimpleChannelUpstreamHandler { - private static final Logger logger = Logger.getLogger( - TelnetClientHandler.class.getName()); + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(TelnetClientHandler.class); @Override public void handleUpstream( @@ -46,14 +46,13 @@ public class TelnetClientHandler extends SimpleChannelUpstreamHandler { public void messageReceived( ChannelHandlerContext ctx, MessageEvent e) { // Print out the line received from the server. - System.err.println(e.getMessage()); + logger.info((String) e.getMessage()); } @Override public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e) { - logger.log( - Level.WARNING, + logger.warn( "Unexpected exception from downstream.", e.getCause()); e.getChannel().close(); diff --git a/example/src/main/java/io/netty/example/uptime/UptimeClient.java b/example/src/main/java/io/netty/example/uptime/UptimeClient.java index 8497c13917..c987b4febd 100644 --- a/example/src/main/java/io/netty/example/uptime/UptimeClient.java +++ b/example/src/main/java/io/netty/example/uptime/UptimeClient.java @@ -25,6 +25,8 @@ import io.netty.channel.ChannelPipelineFactory; import io.netty.channel.Channels; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; @@ -35,6 +37,9 @@ import io.netty.util.Timer; * mechanism in Netty. */ public class UptimeClient { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(UptimeClient.class); // Sleep 5 seconds before a reconnection attempt. static final int RECONNECT_DELAY = 5; @@ -84,7 +89,7 @@ public class UptimeClient { public static void main(String[] args) throws Exception { // Print usage if no argument is specified. if (args.length != 2) { - System.err.println( + logger.error( "Usage: " + UptimeClient.class.getSimpleName() + " "); return; diff --git a/transport-http/src/test/java/io/netty/channel/socket/http/HttpTunnelSoakTester.java b/transport-http/src/test/java/io/netty/channel/socket/http/HttpTunnelSoakTester.java index de061d8717..cf4ac18ba6 100644 --- a/transport-http/src/test/java/io/netty/channel/socket/http/HttpTunnelSoakTester.java +++ b/transport-http/src/test/java/io/netty/channel/socket/http/HttpTunnelSoakTester.java @@ -28,8 +28,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.logging.Logger; import io.netty.bootstrap.ClientBootstrap; import io.netty.bootstrap.ServerBootstrap; @@ -51,6 +49,8 @@ import io.netty.channel.socket.ServerSocketChannelFactory; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioClientSocketChannelFactory; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * Tests HTTP tunnel soaking @@ -59,8 +59,8 @@ public class HttpTunnelSoakTester { private static final int SERVER_PORT = 20100; - static final Logger LOG = Logger.getLogger(HttpTunnelSoakTester.class - .getName()); + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(HttpTunnelSoakTester.class); private static final long BYTES_TO_SEND = 1024 * 1024 * 1024; @@ -127,15 +127,14 @@ public class HttpTunnelSoakTester { clientBootstrap.setOption( HttpTunnelClientChannelConfig.PROXY_ADDRESS_OPTION, proxyAddress); - System.out.println("Using " + proxyAddress + + logger.info("Using " + proxyAddress + " as a proxy for this test run"); } else { - System.err.println("Failed to resolve proxy address " + + logger.error("Failed to resolve proxy address " + proxyAddress); } } else { - System.out - .println("No proxy specified, will connect to server directly"); + logger.info("No proxy specified, will connect to server directly"); } } @@ -197,16 +196,16 @@ public class HttpTunnelSoakTester { } public void run() throws InterruptedException { - LOG.info("binding server channel"); + logger.info("binding server channel"); Channel serverChannel = serverBootstrap.bind(new InetSocketAddress(SERVER_PORT)); channels.add(serverChannel); - LOG.log(Level.INFO, "server channel bound to {0}", - serverChannel.getLocalAddress()); + logger.info(String.format("server channel bound to {0}", + serverChannel.getLocalAddress())); SocketChannel clientChannel = createClientChannel(); if (clientChannel == null) { - LOG.severe("no client channel - bailing out"); + logger.error("no client channel - bailing out"); return; } @@ -216,37 +215,37 @@ public class HttpTunnelSoakTester { executor.execute(c2sDataSender); if (!c2sDataSender.waitForFinish(5, TimeUnit.MINUTES)) { - LOG.severe("Data send from client to server failed"); + logger.error("Data send from client to server failed"); } if (!s2cDataSender.waitForFinish(5, TimeUnit.MINUTES)) { - LOG.severe("Data send from server to client failed"); + logger.error("Data send from server to client failed"); } - LOG.log(Level.INFO, "Waiting for verification to complete"); + logger.info("Waiting for verification to complete"); if (!c2sVerifier.waitForCompletion(30L, TimeUnit.SECONDS)) { - LOG.warning("Timed out waiting for verification of client-to-server stream"); + logger.warn("Timed out waiting for verification of client-to-server stream"); } if (!s2cVerifier.waitForCompletion(30L, TimeUnit.SECONDS)) { - LOG.warning("Timed out waiting for verification of server-to-client stream"); + logger.warn("Timed out waiting for verification of server-to-client stream"); } - LOG.info("closing client channel"); + logger.info("closing client channel"); closeChannel(clientChannel); - LOG.info("server channel status: " + + logger.info("server channel status: " + (serverChannel.isOpen()? "open" : "closed")); - LOG.info("closing server channel"); + logger.info("closing server channel"); closeChannel(serverChannel); } private void closeChannel(Channel channel) { try { if (!channel.close().await(5L, TimeUnit.SECONDS)) { - LOG.warning("Failed to close connection within reasonable amount of time"); + logger.warn("Failed to close connection within reasonable amount of time"); } } catch (InterruptedException e) { - LOG.severe("Interrupted while closing connection"); + logger.error("Interrupted while closing connection"); } } @@ -258,16 +257,16 @@ public class HttpTunnelSoakTester { clientBootstrap.connect(serverAddress); try { if (!clientChannelFuture.await(1000, TimeUnit.MILLISECONDS)) { - LOG.severe("did not connect within acceptable time period"); + logger.error("did not connect within acceptable time period"); return null; } } catch (InterruptedException e) { - LOG.severe("Interrupted while waiting for client connect to be established"); + logger.error("Interrupted while waiting for client connect to be established"); return null; } if (!clientChannelFuture.isSuccess()) { - LOG.log(Level.SEVERE, "did not connect successfully", + logger.error("did not connect successfully", clientChannelFuture.getCause()); return null; } @@ -331,9 +330,9 @@ public class HttpTunnelSoakTester { while (bytesToVerify.readable()) { byte readByte = bytesToVerify.readByte(); if (readByte != expectedNext) { - LOG.log(Level.SEVERE, + logger.error(String.format( "{0}: received a byte out of sequence. Expected {1}, got {2}", - new Object[] { name, expectedNext, readByte }); + new Object[] { name, expectedNext, readByte })); System.exit(-1); return; } @@ -416,20 +415,20 @@ public class HttpTunnelSoakTester { @Override public void run() { if (!running.compareAndSet(false, true)) { - LOG.log(Level.WARNING, - "{0}: Attempt made to run duplicate sender!", name); + logger.warn(String.format( + "{0}: Attempt made to run duplicate sender!", name)); return; } if (finishLatch.getCount() == 0) { - LOG.log(Level.SEVERE, - "{0}: Attempt made to run after completion!", name); + logger.error(String.format( + "{0}: Attempt made to run after completion!", name)); } if (firstRun) { firstRun = false; runStartTime = System.currentTimeMillis(); - LOG.log(Level.INFO, "{0}: sending data", name); + logger.info(String.format("{0}: sending data", name)); } while (totalBytesSent < BYTES_TO_SEND) { @@ -447,22 +446,22 @@ public class HttpTunnelSoakTester { numWrites ++; if (numWrites % 100 == 0) { - LOG.log(Level.INFO, + logger.info(String.format( "{0}: {1} writes dispatched, totalling {2} bytes", - new Object[] { name, numWrites, totalBytesSent }); + new Object[] { name, numWrites, totalBytesSent })); } } - LOG.log(Level.INFO, "{0}: completed send cycle", name); + logger.info(String.format("{0}: completed send cycle", name)); long runEndTime = System.currentTimeMillis(); long totalTime = runEndTime - runStartTime; long totalKB = totalBytesSent / 1024; double rate = totalKB / (totalTime / 1000.0); - LOG.log(Level.INFO, "{0}: Sent {1} bytes", new Object[] { name, - totalBytesSent }); - LOG.log(Level.INFO, "{0}: Average throughput: {1} KB/s", - new Object[] { name, rate }); + logger.info(String.format("{0}: Sent {1} bytes", new Object[] { name, + totalBytesSent })); + logger.info(String.format("{0}: Average throughput: {1} KB/s", + new Object[] { name, rate })); finishLatch.countDown(); running.set(false); diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java index ed5cc560de..d92c5de30c 100644 --- a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java @@ -35,6 +35,8 @@ import io.netty.channel.sctp.SctpServerSocketChannelFactory; import io.netty.channel.sctp.codec.SctpFrameDecoder; import io.netty.channel.sctp.codec.SctpFrameEncoder; import io.netty.channel.sctp.handler.SimpleSctpChannelHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.testsuite.util.SctpTestUtil; import io.netty.util.internal.ExecutorUtil; @@ -53,6 +55,10 @@ import org.junit.BeforeClass; import org.junit.Test; public class SctpMultiHomingEchoTest { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SctpMultiHomingEchoTest.class); + private static final Random random = new Random(); static final byte[] data = new byte[4096];//could not test ultra jumbo frames @@ -228,7 +234,7 @@ public class SctpMultiHomingEchoTest { @Override public void sctpNotificationReceived(ChannelHandlerContext ctx, SctpNotificationEvent event) { - System.out.println("SCTP notification event received :" + event); + logger.info("SCTP notification event received :" + event); } } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java b/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java index f2993f52b2..af42d3e040 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java @@ -432,11 +432,10 @@ final class NioProviderMetadata { public static void main(String[] args) throws Exception { for (Entry e: System.getProperties().entrySet()) { - System.out.println(e.getKey() + ": " + e.getValue()); + logger.debug(e.getKey() + ": " + e.getValue()); } - System.out.println(); - System.out.println("Hard-coded Constraint Level: " + CONSTRAINT_LEVEL); - System.out.println( + logger.debug("Hard-coded Constraint Level: " + CONSTRAINT_LEVEL); + logger.debug( "Auto-detected Constraint Level: " + new ConstraintLevelAutodetector().autodetect()); } diff --git a/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java b/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java index a51642e7bf..e957c7eeda 100644 --- a/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java +++ b/transport/src/test/java/io/netty/channel/local/LocalAddressTest.java @@ -31,8 +31,14 @@ import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.channel.local.DefaultLocalClientChannelFactory; import io.netty.channel.local.DefaultLocalServerChannelFactory; import io.netty.channel.local.LocalAddress; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; public class LocalAddressTest { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(LocalAddressTest.class); + private static String LOCAL_ADDR_ID = "test.id"; @Test @@ -157,7 +163,7 @@ public class LocalAddressTest { ChannelEvent e) throws Exception { - System.err.println(String.format("Received upstream event '%s'", e)); + logger.info(String.format("Received upstream event '%s'", e)); } } } \ No newline at end of file From 11e974ace35cb79e2b1f730e57619f9220b3b890 Mon Sep 17 00:00:00 2001 From: Cruz Bishop Date: Sun, 15 Apr 2012 19:22:02 +1000 Subject: [PATCH 027/134] Fix up the last logging mishits --- .../java/io/netty/example/localtime/LocalTimeClient.java | 2 +- .../io/netty/example/uptime/UptimeClientHandler.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java index f922a2bcef..c061d05ca4 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java @@ -81,7 +81,7 @@ public class LocalTimeClient { Iterator i1 = cities.iterator(); Iterator i2 = response.iterator(); while (i1.hasNext()) { - System.out.format("%28s: %s%n", i1.next(), i2.next()); + logger.info(String.format("%28s: %s%n", i1.next(), i2.next())); } } diff --git a/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java b/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java index 096e575db6..503f69fb83 100644 --- a/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java +++ b/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java @@ -25,6 +25,8 @@ import io.netty.channel.ChannelStateEvent; import io.netty.channel.ExceptionEvent; import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; @@ -34,6 +36,9 @@ import io.netty.util.TimerTask; * connection attempt status. */ public class UptimeClientHandler extends SimpleChannelUpstreamHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(UptimeClientHandler.class); final ClientBootstrap bootstrap; private final Timer timer; @@ -91,9 +96,9 @@ public class UptimeClientHandler extends SimpleChannelUpstreamHandler { void println(String msg) { if (startTime < 0) { - System.err.format("[SERVER IS DOWN] %s%n", msg); + logger.error(String.format("[SERVER IS DOWN] %s%n", msg)); } else { - System.err.format("[UPTIME: %5ds] %s%n", (System.currentTimeMillis() - startTime) / 1000, msg); + logger.error(String.format("[UPTIME: %5ds] %s%n", (System.currentTimeMillis() - startTime) / 1000, msg)); } } } From bc141f695b614a7b9034b18e5bced3e218ed47b8 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 15 Apr 2012 15:47:05 +0200 Subject: [PATCH 028/134] Add test for HttpClientCodec that tests handling of missing responses. See #256 amd #259 --- .../codec/http/HttpClientCodecTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java new file mode 100644 index 0000000000..e53edc08d5 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2011 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.handler.codec.PrematureChannelClosureException; +import io.netty.handler.codec.embedder.CodecEmbedderException; +import io.netty.handler.codec.embedder.DecoderEmbedder; +import io.netty.handler.codec.embedder.EncoderEmbedder; +import io.netty.util.CharsetUtil; +import org.junit.Test; + +public class HttpClientCodecTest { + + private static final String RESPONSE = "HTTP/1.0 200 OK\r\n" + "Date: Fri, 31 Dec 1999 23:59:59 GMT\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 28\r\n" + "\r\n" + + "\r\n"; + + @Test + public void testFailsNotOnRequestResponse() { + HttpClientCodec codec = new HttpClientCodec(); + DecoderEmbedder decoder = new DecoderEmbedder(codec); + EncoderEmbedder encoder = new EncoderEmbedder(codec); + + encoder.offer(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost/")); + decoder.offer(ChannelBuffers.copiedBuffer(RESPONSE, CharsetUtil.ISO_8859_1)); + + encoder.finish(); + decoder.finish(); + + } + + @Test + public void testFailsOnMissingResponse() { + HttpClientCodec codec = new HttpClientCodec(); + EncoderEmbedder encoder = new EncoderEmbedder(codec); + + encoder.offer(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost/")); + + try { + encoder.finish(); + fail(); + } catch (CodecEmbedderException e) { + assertTrue(e.getCause() instanceof PrematureChannelClosureException); + } + + } +} From e02225a80f4621172f72978bdac6a148e471fed5 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 15 Apr 2012 20:58:40 +0200 Subject: [PATCH 029/134] Add also tests for HttpClientCodec that tests handling chunked responses. See #256 amd #259 --- .../codec/http/HttpClientCodecTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java index e53edc08d5..2f4ac8ba98 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java @@ -31,6 +31,10 @@ public class HttpClientCodecTest { private static final String RESPONSE = "HTTP/1.0 200 OK\r\n" + "Date: Fri, 31 Dec 1999 23:59:59 GMT\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 28\r\n" + "\r\n" + "\r\n"; + private static final String INCOMPLETE_CHUNKED_RESPONSE = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + +"5\r\n" + "first\r\n" + "6\r\n" + "second\r\n" + "0\r\n"; + private static final String CHUNKED_RESPONSE = INCOMPLETE_CHUNKED_RESPONSE + "\r\n"; + @Test public void testFailsNotOnRequestResponse() { @@ -46,6 +50,20 @@ public class HttpClientCodecTest { } + @Test + public void testFailsNotOnRequestResponseChunked() { + HttpClientCodec codec = new HttpClientCodec(); + DecoderEmbedder decoder = new DecoderEmbedder(codec); + EncoderEmbedder encoder = new EncoderEmbedder(codec); + + encoder.offer(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost/")); + decoder.offer(ChannelBuffers.copiedBuffer(CHUNKED_RESPONSE, CharsetUtil.ISO_8859_1)); + + encoder.finish(); + decoder.finish(); + + } + @Test public void testFailsOnMissingResponse() { HttpClientCodec codec = new HttpClientCodec(); @@ -61,4 +79,24 @@ public class HttpClientCodecTest { } } + + @Test + public void testFailsOnIncompleteChunkedResponse() { + HttpClientCodec codec = new HttpClientCodec(); + DecoderEmbedder decoder = new DecoderEmbedder(codec); + + EncoderEmbedder encoder = new EncoderEmbedder(codec); + + encoder.offer(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost/")); + decoder.offer(ChannelBuffers.copiedBuffer(INCOMPLETE_CHUNKED_RESPONSE, CharsetUtil.ISO_8859_1)); + + try { + encoder.finish(); + decoder.finish(); + fail(); + } catch (CodecEmbedderException e) { + assertTrue(e.getCause() instanceof PrematureChannelClosureException); + } + + } } From 05615c4779e4c8bd98ac9b15097ab5fef4916c78 Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 16 Apr 2012 09:55:03 +0200 Subject: [PATCH 030/134] Throw IllegalStateException if DynamicChannelBuffer exceed the maximum ChannelBuffer size of 2gb. See #258 --- .../main/java/io/netty/buffer/DynamicChannelBuffer.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/buffer/src/main/java/io/netty/buffer/DynamicChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/DynamicChannelBuffer.java index 2ea089c46b..83a202ad4f 100644 --- a/buffer/src/main/java/io/netty/buffer/DynamicChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/DynamicChannelBuffer.java @@ -73,6 +73,15 @@ public class DynamicChannelBuffer extends AbstractChannelBuffer { int minNewCapacity = writerIndex() + minWritableBytes; while (newCapacity < minNewCapacity) { newCapacity <<= 1; + + + // Check if we exceeded the maximum size of 2gb if this is the case then + // newCapacity == 0 + // + // https://github.com/netty/netty/issues/258 + if (newCapacity == 0) { + throw new IllegalStateException("Maximum size of 2gb exceeded"); + } } ChannelBuffer newBuffer = factory().getBuffer(order(), newCapacity); From c44f365ee7784dd3af2ecec73ed18b8f6239f674 Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 16 Apr 2012 10:43:21 +0200 Subject: [PATCH 031/134] Add some javadocs notes that explain the behavior of CookieEncoder.encode(). See #94 --- .../main/java/io/netty/handler/codec/http/CookieEncoder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java index a8de9a8e46..1675344b50 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java @@ -82,6 +82,8 @@ public class CookieEncoder { * Encodes the {@link Cookie}s which were added by {@link #addCookie(Cookie)} * so far into an HTTP header value. If no {@link Cookie}s were added, * an empty string is returned. + * + * Be aware that calling this method will clear the contends of the {@link CookieEncoder} */ public String encode() { String answer; From 978168e8c6c8d51e1c1d611a9900d2414e2ec3bc Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 16 Apr 2012 11:45:59 +0300 Subject: [PATCH 032/134] Fix typo --- .../main/java/io/netty/handler/codec/http/CookieEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java index 1675344b50..9a46fd3c58 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoder.java @@ -83,7 +83,7 @@ public class CookieEncoder { * so far into an HTTP header value. If no {@link Cookie}s were added, * an empty string is returned. * - * Be aware that calling this method will clear the contends of the {@link CookieEncoder} + * Be aware that calling this method will clear the content of the {@link CookieEncoder} */ public String encode() { String answer; From 987e152f083740738f05fab1d80165c9d6381f0b Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 16 Apr 2012 13:12:12 +0200 Subject: [PATCH 033/134] Add port to Origin HTTP Header if the port is non default (80/443). See #262 --- .../http/websocketx/WebSocketClientHandshaker00.java | 10 +++++++++- .../http/websocketx/WebSocketClientHandshaker08.java | 10 +++++++++- .../http/websocketx/WebSocketClientHandshaker13.java | 11 ++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index d4d26e45a6..63c76258ec 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -138,7 +138,15 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { request.addHeader(Names.UPGRADE, Values.WEBSOCKET); request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.HOST, wsURL.getHost()); - request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); + + int wsPort = wsURL.getPort(); + String originValue = "http://" + wsURL.getHost(); + if (wsPort != 80 && wsPort != 443) { + // if the port is not standard (80/443) its needed to add the port to the header. + // See http://tools.ietf.org/html/rfc6454#section-6.2 + originValue = originValue + ":" + wsPort; + } + request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); if (getExpectedSubprotocol() != null && !getExpectedSubprotocol().equals("")) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index f9d122903a..dc6ad60efb 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -122,7 +122,15 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.SEC_WEBSOCKET_KEY, key); request.addHeader(Names.HOST, wsURL.getHost()); - request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); + + int wsPort = wsURL.getPort(); + String originValue = "http://" + wsURL.getHost(); + if (wsPort != 80 && wsPort != 443) { + // if the port is not standard (80/443) its needed to add the port to the header. + // See http://tools.ietf.org/html/rfc6454#section-6.2 + originValue = originValue + ":" + wsPort; + } + if (protocol != null && !protocol.equals("")) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 19cfd1abbb..cce6f55265 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -122,7 +122,16 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.SEC_WEBSOCKET_KEY, key); request.addHeader(Names.HOST, wsURL.getHost()); - request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); + + int wsPort = wsURL.getPort(); + String originValue = "http://" + wsURL.getHost(); + if (wsPort != 80 && wsPort != 443) { + // if the port is not standard (80/443) its needed to add the port to the header. + // See http://tools.ietf.org/html/rfc6454#section-6.2 + originValue = originValue + ":" + wsPort; + } + request.addHeader(Names.ORIGIN, originValue); + if (protocol != null && !protocol.equals("")) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); } From 00e59da7ba9792fdcda082b52c948e80821ecef1 Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 16 Apr 2012 13:26:35 +0200 Subject: [PATCH 034/134] Add port to Origin HTTP Header if the port is non default (80/443). See #262 --- .../codec/http/websocketx/WebSocketClientHandshaker00.java | 2 ++ .../codec/http/websocketx/WebSocketClientHandshaker08.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index 63c76258ec..fc5ae38dac 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -146,6 +146,8 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { // See http://tools.ietf.org/html/rfc6454#section-6.2 originValue = originValue + ":" + wsPort; } + request.addHeader(Names.ORIGIN, originValue); + request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index dc6ad60efb..7de98038d8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -130,6 +130,8 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { // See http://tools.ietf.org/html/rfc6454#section-6.2 originValue = originValue + ":" + wsPort; } + request.addHeader(Names.ORIGIN, originValue); + if (protocol != null && !protocol.equals("")) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); From cd92478bfc9f3a8ab15514d56aaa6d4a43b5b9a1 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 16 Apr 2012 15:30:43 +0200 Subject: [PATCH 035/134] Use Sec-WebSocket-Origin in WebSocketClientHandshaker08 as replacement for Origin. See #264 --- .../codec/http/websocketx/WebSocketClientHandshaker08.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 7de98038d8..d22fc965fa 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -130,8 +130,10 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { // See http://tools.ietf.org/html/rfc6454#section-6.2 originValue = originValue + ":" + wsPort; } - request.addHeader(Names.ORIGIN, originValue); + // Use Sec-WebSocket-Origin + // See https://github.com/netty/netty/issues/264 + request.addHeader(Names.SEC_WEBSOCKET_ORIGIN, originValue); if (protocol != null && !protocol.equals("")) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); From aa6c16b13620c53a485247dae5da3ad0e7fe8ba6 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 17 Apr 2012 09:25:53 +0200 Subject: [PATCH 036/134] Make sure PrematureChannelClosureException is not thrown incorrectly sometimes. See #266 --- .../handler/codec/http/HttpClientCodec.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java index 10404e8c16..670f84bf5b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java @@ -127,20 +127,27 @@ public class HttpClientCodec implements ChannelUpstreamHandler, return buffer.readBytes(actualReadableBytes()); } else { Object msg = super.decode(ctx, channel, buffer, state); - - if (msg != null) { - // check if its a HttpMessage and its not chunked - if (msg instanceof HttpMessage && !((HttpMessage) msg).isChunked()) { - requestResponseCounter.decrementAndGet(); - } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { - requestResponseCounter.decrementAndGet(); - } - } - + decrement(msg); return msg; } } + private void decrement(Object msg) { + if (msg == null) { + return; + } + + // check if its a HttpMessage and its not chunked + if (msg instanceof HttpMessage && !((HttpMessage) msg).isChunked()) { + requestResponseCounter.decrementAndGet(); + } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { + requestResponseCounter.decrementAndGet(); + } else if (msg instanceof Object[]) { + // we just decrement it here as we only use this if the end of the chunk is reached + // It would be more safe to check all the objects in the array but would also be slower + requestResponseCounter.decrementAndGet(); + } + } @Override protected boolean isContentAlwaysEmpty(HttpMessage msg) { final int statusCode = ((HttpResponse) msg).getStatus().getCode(); From 2db3e59a6c5e40fb72cee46bc66b30ccd4dbbe79 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 17 Apr 2012 09:56:15 +0200 Subject: [PATCH 037/134] Add workaround for connection problems with IPv6 link-local addresses and jdk < 7. See #267 --- .../io/netty/util/internal/SocketUtil.java | 78 +++++++++++++++++++ .../nio/NioClientSocketPipelineSink.java | 3 + .../oio/OioClientSocketPipelineSink.java | 4 + 3 files changed, 85 insertions(+) create mode 100644 common/src/main/java/io/netty/util/internal/SocketUtil.java diff --git a/common/src/main/java/io/netty/util/internal/SocketUtil.java b/common/src/main/java/io/netty/util/internal/SocketUtil.java new file mode 100644 index 0000000000..53c0094781 --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/SocketUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util.internal; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +public final class SocketUtil { + + + private static final String ZONE_ID_SEPARATOR = "%"; + + + /** + * Takes care of stripping the zone id from the {@link InetAddress} if its an {@link Inet6Address} and the java + * version is < 7. After that a new {@link InetSocketAddress} is created based on the old one. This is needed because of a bug that exists in java + * versions < 7. + * + * See https://github.com/netty/netty/issues/267 + * + */ + public static InetSocketAddress stripZoneId(InetSocketAddress socketAddress) throws UnknownHostException { + return new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPort()); + } + + /** + * Takes care of stripping the zone id from the {@link InetAddress} if its an {@link Inet6Address} and the java + * version is < 7. This is needed because of a bug that exists in java versions < 7. + * + * See https://github.com/netty/netty/issues/267 + * + */ + public static InetAddress stripZoneId(InetAddress address) throws UnknownHostException { + // If we have a java version which is >= 7 we can just return the given + // InetSocketAddress as this bug only seems + // to exist in java 6 (and maybe also versions before) + if (DetectionUtil.javaVersion() >= 7) { + return address; + } + + if (address instanceof Inet6Address) { + Inet6Address inet6Address = (Inet6Address) address; + + // Check if its a LinkLocalAddress as this is the only one which is + // affected + if (inet6Address.isLinkLocalAddress()) { + String hostaddress = inet6Address.getHostAddress(); + + int separator = hostaddress.indexOf(ZONE_ID_SEPARATOR); + + // strip of the zoneId + String withoutZonedId = inet6Address.getHostAddress().substring(0, separator); + return InetAddress.getByName(withoutZonedId); + } + } + return address; + + } + + private SocketUtil() { + + } +} diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java index 495dd371fb..6b4008a520 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java @@ -27,7 +27,9 @@ import io.netty.channel.ChannelStateEvent; import io.netty.channel.MessageEvent; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; +import io.netty.util.internal.SocketUtil; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; @@ -99,6 +101,7 @@ class NioClientSocketPipelineSink extends AbstractNioChannelSink { final NioClientSocketChannel channel, final ChannelFuture cf, SocketAddress remoteAddress) { try { + remoteAddress = SocketUtil.stripZoneId((InetSocketAddress) remoteAddress); channel.getJdkChannel().connect(remoteAddress); channel.getCloseFuture().addListener(new ChannelFutureListener() { @Override diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java index e607d3282e..ea5148b76b 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java @@ -18,6 +18,7 @@ package io.netty.channel.socket.oio; import static io.netty.channel.Channels.*; import java.io.PushbackInputStream; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.Executor; @@ -29,6 +30,7 @@ import io.netty.channel.ChannelState; import io.netty.channel.ChannelStateEvent; import io.netty.channel.MessageEvent; import io.netty.util.internal.DeadLockProofWorker; +import io.netty.util.internal.SocketUtil; class OioClientSocketPipelineSink extends AbstractOioChannelSink { @@ -102,6 +104,8 @@ class OioClientSocketPipelineSink extends AbstractOioChannelSink { future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); try { + remoteAddress = SocketUtil.stripZoneId((InetSocketAddress) remoteAddress); + channel.socket.connect( remoteAddress, channel.getConfig().getConnectTimeoutMillis()); connected = true; From 02dc9ea8c16fbed57aa5e03799a5120f36e78723 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 17 Apr 2012 14:16:37 +0200 Subject: [PATCH 038/134] Add workaround for connection problems with IPv6 link-local addresses and jdk < 7. See #267 --- common/src/main/java/io/netty/util/internal/SocketUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/netty/util/internal/SocketUtil.java b/common/src/main/java/io/netty/util/internal/SocketUtil.java index 53c0094781..da0548b1b5 100644 --- a/common/src/main/java/io/netty/util/internal/SocketUtil.java +++ b/common/src/main/java/io/netty/util/internal/SocketUtil.java @@ -35,7 +35,7 @@ public final class SocketUtil { * */ public static InetSocketAddress stripZoneId(InetSocketAddress socketAddress) throws UnknownHostException { - return new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPort()); + return new InetSocketAddress(stripZoneId(socketAddress.getAddress()), socketAddress.getPort()); } /** From b3b5fb1de66fd1290015998d7d4d836bce3e8594 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 17 Apr 2012 14:17:56 +0200 Subject: [PATCH 039/134] Fallback to LegacyLinkedTransferQueue if using LinkedTransferQueue fails. See #268 --- .../io/netty/util/internal/QueueFactory.java | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/QueueFactory.java b/common/src/main/java/io/netty/util/internal/QueueFactory.java index 557e20a536..4c34a0356e 100644 --- a/common/src/main/java/io/netty/util/internal/QueueFactory.java +++ b/common/src/main/java/io/netty/util/internal/QueueFactory.java @@ -18,6 +18,9 @@ package io.netty.util.internal; import java.util.Collection; import java.util.concurrent.BlockingQueue; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + /** * This factory should be used to create the "optimal" {@link BlockingQueue} * instance for the running JVM. @@ -25,6 +28,7 @@ import java.util.concurrent.BlockingQueue; public final class QueueFactory { private static final boolean useUnsafe = DetectionUtil.hasUnsafe(); + private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QueueFactory.class); private QueueFactory() { // only use static methods! @@ -38,11 +42,20 @@ public final class QueueFactory { * @return queue the {@link BlockingQueue} implementation */ public static BlockingQueue createQueue(Class itemClass) { - if (useUnsafe) { - return new LinkedTransferQueue(); - } else { - return new LegacyLinkedTransferQueue(); + try { + if (useUnsafe) { + return new LinkedTransferQueue(); + } + } catch (Throwable t) { + // For whatever reason an exception was thrown while loading the LinkedTransferQueue + // + // This mostly happens because of a custom classloader or security policy that did not allow us to access the + // com.sun.Unmisc class. So just log it and fallback to the old LegacyLinkedTransferQueue that works in all cases + LOGGER.debug("Unable to instance LinkedTransferQueue, fallback to LegacyLinkedTransferQueue", t); } + + return new LegacyLinkedTransferQueue(); + } /** @@ -53,10 +66,19 @@ public final class QueueFactory { * @return queue the {@link BlockingQueue} implementation */ public static BlockingQueue createQueue(Collection collection, Class itemClass) { - if (useUnsafe) { - return new LinkedTransferQueue(collection); - } else { - return new LegacyLinkedTransferQueue(collection); + try { + if (useUnsafe) { + return new LinkedTransferQueue(collection); + } + } catch (Throwable t) { + // For whatever reason an exception was thrown while loading the LinkedTransferQueue + // + // This mostly happens because of a custom classloader or security policy that did not allow us to access the + // com.sun.Unmisc class. So just log it and fallback to the old LegacyLinkedTransferQueue that works in all cases + LOGGER.debug("Unable to instance LinkedTransferQueue, fallback to LegacyLinkedTransferQueue", t); } + + return new LegacyLinkedTransferQueue(collection); + } } From d808cd04757136c3ea2180e2c5e13dc509f5a2d0 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 17 Apr 2012 20:54:38 +0200 Subject: [PATCH 040/134] Fix compile error --- common/src/main/java/io/netty/util/internal/QueueFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/QueueFactory.java b/common/src/main/java/io/netty/util/internal/QueueFactory.java index 4c34a0356e..c05ceda6ed 100644 --- a/common/src/main/java/io/netty/util/internal/QueueFactory.java +++ b/common/src/main/java/io/netty/util/internal/QueueFactory.java @@ -18,8 +18,8 @@ package io.netty.util.internal; import java.util.Collection; import java.util.concurrent.BlockingQueue; -import org.jboss.netty.logging.InternalLogger; -import org.jboss.netty.logging.InternalLoggerFactory; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; /** * This factory should be used to create the "optimal" {@link BlockingQueue} From 54559a95956ba26bd75790fad04f7057fe0f6674 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 17 Apr 2012 20:54:58 +0200 Subject: [PATCH 041/134] Make it configurable if the HttpClientCodec should throw an exception on close when the response and request count does not match. Default is false. See #266 --- .../handler/codec/http/HttpClientCodec.java | 39 +++++++++++++------ .../codec/http/HttpClientCodecTest.java | 8 ++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java index 670f84bf5b..93b87dec95 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java @@ -57,14 +57,16 @@ public class HttpClientCodec implements ChannelUpstreamHandler, private final HttpRequestEncoder encoder = new Encoder(); private final HttpResponseDecoder decoder; private final AtomicLong requestResponseCounter = new AtomicLong(0); + private final boolean failOnMissingResponse; /** * Creates a new instance with the default decoder options * ({@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and * {@code maxChunkSize (8192)}). + * */ public HttpClientCodec() { - this(4096, 8192, 8192); + this(4096, 8192, 8192, false); } /** @@ -72,7 +74,16 @@ public class HttpClientCodec implements ChannelUpstreamHandler, */ public HttpClientCodec( int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { + this(maxInitialLineLength, maxHeaderSize, maxChunkSize, false); + } + + /** + * Creates a new instance with the specified decoder options. + */ + public HttpClientCodec( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean failOnMissingResponse) { decoder = new Decoder(maxInitialLineLength, maxHeaderSize, maxChunkSize); + this.failOnMissingResponse = failOnMissingResponse; } @Override @@ -101,12 +112,14 @@ public class HttpClientCodec implements ChannelUpstreamHandler, Object obj = super.encode(ctx, channel, msg); - // check if the request is chunked if so do not increment - if (msg instanceof HttpRequest && !((HttpRequest) msg).isChunked()) { - requestResponseCounter.incrementAndGet(); - } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { - // increment as its the last chunk - requestResponseCounter.incrementAndGet(); + if (failOnMissingResponse) { + // check if the request is chunked if so do not increment + if (msg instanceof HttpRequest && !((HttpRequest) msg).isChunked()) { + requestResponseCounter.incrementAndGet(); + } else if (msg instanceof HttpChunk && ((HttpChunk) msg).isLast()) { + // increment as its the last chunk + requestResponseCounter.incrementAndGet(); + } } return obj; @@ -127,7 +140,9 @@ public class HttpClientCodec implements ChannelUpstreamHandler, return buffer.readBytes(actualReadableBytes()); } else { Object msg = super.decode(ctx, channel, buffer, state); - decrement(msg); + if (failOnMissingResponse) { + decrement(msg); + } return msg; } } @@ -204,9 +219,11 @@ public class HttpClientCodec implements ChannelUpstreamHandler, public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { super.channelClosed(ctx, e); - long missingResponses = requestResponseCounter.get(); - if (missingResponses > 0) { - throw new PrematureChannelClosureException("Channel closed but still missing " + missingResponses + " response(s)"); + if (failOnMissingResponse) { + long missingResponses = requestResponseCounter.get(); + if (missingResponses > 0) { + throw new PrematureChannelClosureException("Channel closed but still missing " + missingResponses + " response(s)"); + } } } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java index 2f4ac8ba98..47e075d4c5 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpClientCodecTest.java @@ -38,7 +38,7 @@ public class HttpClientCodecTest { @Test public void testFailsNotOnRequestResponse() { - HttpClientCodec codec = new HttpClientCodec(); + HttpClientCodec codec = new HttpClientCodec(4096, 8192, 8192, true); DecoderEmbedder decoder = new DecoderEmbedder(codec); EncoderEmbedder encoder = new EncoderEmbedder(codec); @@ -52,7 +52,7 @@ public class HttpClientCodecTest { @Test public void testFailsNotOnRequestResponseChunked() { - HttpClientCodec codec = new HttpClientCodec(); + HttpClientCodec codec = new HttpClientCodec(4096, 8192, 8192, true); DecoderEmbedder decoder = new DecoderEmbedder(codec); EncoderEmbedder encoder = new EncoderEmbedder(codec); @@ -66,7 +66,7 @@ public class HttpClientCodecTest { @Test public void testFailsOnMissingResponse() { - HttpClientCodec codec = new HttpClientCodec(); + HttpClientCodec codec = new HttpClientCodec(4096, 8192, 8192, true); EncoderEmbedder encoder = new EncoderEmbedder(codec); encoder.offer(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost/")); @@ -82,7 +82,7 @@ public class HttpClientCodecTest { @Test public void testFailsOnIncompleteChunkedResponse() { - HttpClientCodec codec = new HttpClientCodec(); + HttpClientCodec codec = new HttpClientCodec(4096, 8192, 8192, true); DecoderEmbedder decoder = new DecoderEmbedder(codec); EncoderEmbedder encoder = new EncoderEmbedder(codec); From e719f23f7d3b2fca265ddb7fedef2e35e64d4594 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 19 Apr 2012 12:03:09 +0200 Subject: [PATCH 042/134] Only log if logging level is enabled --- .../main/java/io/netty/util/internal/QueueFactory.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/QueueFactory.java b/common/src/main/java/io/netty/util/internal/QueueFactory.java index c05ceda6ed..b3fab25795 100644 --- a/common/src/main/java/io/netty/util/internal/QueueFactory.java +++ b/common/src/main/java/io/netty/util/internal/QueueFactory.java @@ -51,7 +51,9 @@ public final class QueueFactory { // // This mostly happens because of a custom classloader or security policy that did not allow us to access the // com.sun.Unmisc class. So just log it and fallback to the old LegacyLinkedTransferQueue that works in all cases - LOGGER.debug("Unable to instance LinkedTransferQueue, fallback to LegacyLinkedTransferQueue", t); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unable to instance LinkedTransferQueue, fallback to LegacyLinkedTransferQueue", t); + } } return new LegacyLinkedTransferQueue(); @@ -75,7 +77,9 @@ public final class QueueFactory { // // This mostly happens because of a custom classloader or security policy that did not allow us to access the // com.sun.Unmisc class. So just log it and fallback to the old LegacyLinkedTransferQueue that works in all cases - LOGGER.debug("Unable to instance LinkedTransferQueue, fallback to LegacyLinkedTransferQueue", t); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unable to instance LinkedTransferQueue, fallback to LegacyLinkedTransferQueue", t); + } } return new LegacyLinkedTransferQueue(collection); From 2ecef07c4a5b1a2d302e7ad5adae9b828370234d Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 19 Apr 2012 12:07:17 +0200 Subject: [PATCH 043/134] Allow to disable the use of sun.misc.Unsafe via a System property. See #272 --- .../java/io/netty/util/internal/DetectionUtil.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/common/src/main/java/io/netty/util/internal/DetectionUtil.java b/common/src/main/java/io/netty/util/internal/DetectionUtil.java index 2a749f0488..578d5b0401 100644 --- a/common/src/main/java/io/netty/util/internal/DetectionUtil.java +++ b/common/src/main/java/io/netty/util/internal/DetectionUtil.java @@ -26,6 +26,11 @@ import java.util.zip.Deflater; * Utility that detects various properties specific to the current runtime * environment, such as Java version and the availability of the * {@code sun.misc.Unsafe} object. + * + *
+ * You can disable the use of {@code sun.misc.Unsafe} if you specify + * the System property io.netty.tryUnsafe with + * value of false. Default is true. */ public final class DetectionUtil { @@ -41,6 +46,15 @@ public final class DetectionUtil { } private static boolean hasUnsafe(ClassLoader loader) { + String value = SystemPropertyUtil.get("io.netty.tryUnsafe"); + if (value == null) { + value = SystemPropertyUtil.get("org.jboss.netty.tryUnsafe", "true"); + } + boolean useUnsafe = Boolean.valueOf(value); + if (!useUnsafe) { + return false; + } + try { Class unsafeClazz = Class.forName("sun.misc.Unsafe", true, loader); return hasUnsafeField(unsafeClazz); From ec409751e109256bd872c0d80c4ab1cbafc1a6de Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 19 Apr 2012 16:42:55 +0200 Subject: [PATCH 044/134] Correctly handle the stripping of the zoneId / scopeId in all cases. See #267 --- .../io/netty/util/internal/SocketUtil.java | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/SocketUtil.java b/common/src/main/java/io/netty/util/internal/SocketUtil.java index da0548b1b5..98ffd8b23e 100644 --- a/common/src/main/java/io/netty/util/internal/SocketUtil.java +++ b/common/src/main/java/io/netty/util/internal/SocketUtil.java @@ -18,7 +18,6 @@ package io.netty.util.internal; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.UnknownHostException; public final class SocketUtil { @@ -27,49 +26,40 @@ public final class SocketUtil { /** - * Takes care of stripping the zone id from the {@link InetAddress} if its an {@link Inet6Address} and the java + * Takes care of stripping the zone id / scope Id from the {@link InetAddress} if its an {@link Inet6Address} and the java * version is < 7. After that a new {@link InetSocketAddress} is created based on the old one. This is needed because of a bug that exists in java * versions < 7. * * See https://github.com/netty/netty/issues/267 * */ - public static InetSocketAddress stripZoneId(InetSocketAddress socketAddress) throws UnknownHostException { - return new InetSocketAddress(stripZoneId(socketAddress.getAddress()), socketAddress.getPort()); - } - - /** - * Takes care of stripping the zone id from the {@link InetAddress} if its an {@link Inet6Address} and the java - * version is < 7. This is needed because of a bug that exists in java versions < 7. - * - * See https://github.com/netty/netty/issues/267 - * - */ - public static InetAddress stripZoneId(InetAddress address) throws UnknownHostException { - // If we have a java version which is >= 7 we can just return the given - // InetSocketAddress as this bug only seems - // to exist in java 6 (and maybe also versions before) - if (DetectionUtil.javaVersion() >= 7) { - return address; - } + public static InetSocketAddress stripZoneId(InetSocketAddress socketAddress) { + // If we have a java version which is >= 7 we can just return the given + // InetSocketAddress as this bug only seems + // to exist in java 6 (and maybe also versions before) + if (DetectionUtil.javaVersion() >= 7) { + return socketAddress; + } + InetAddress address = socketAddress.getAddress(); + if (address instanceof Inet6Address) { + Inet6Address inet6Address = (Inet6Address) address; - if (address instanceof Inet6Address) { - Inet6Address inet6Address = (Inet6Address) address; + // Check if its a LinkLocalAddress as this is the only one which is + // affected + if (inet6Address.isLinkLocalAddress()) { + String hostaddress = inet6Address.getHostAddress(); - // Check if its a LinkLocalAddress as this is the only one which is - // affected - if (inet6Address.isLinkLocalAddress()) { - String hostaddress = inet6Address.getHostAddress(); - - int separator = hostaddress.indexOf(ZONE_ID_SEPARATOR); - - // strip of the zoneId - String withoutZonedId = inet6Address.getHostAddress().substring(0, separator); - return InetAddress.getByName(withoutZonedId); + int separator = hostaddress.indexOf(ZONE_ID_SEPARATOR); + + // check if the address contains a zoneId /scopeId and if so strip it + if (separator != -1) { + // strip of the zoneId / scopeId + String withoutZonedId = inet6Address.getHostAddress().substring(0, separator); + return new InetSocketAddress(withoutZonedId, socketAddress.getPort()); } } - return address; - + } + return socketAddress; } private SocketUtil() { From ccf01d133a031f69346d1479e5eb29547d09afeb Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 20 Apr 2012 20:17:30 +0200 Subject: [PATCH 045/134] Make sure we always cleanup once ReplayingDecoder handles a message. See #259 --- .../io/netty/handler/codec/replay/ReplayingDecoder.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java b/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java index a2c6b557a5..24f7723354 100644 --- a/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java @@ -293,6 +293,8 @@ public abstract class ReplayingDecoder> private ReplayingDecoderBuffer replayable; private T state; private int checkpoint; + private boolean needsCleanup; + /** * Creates a new instance with no initial state (i.e: {@code null}). @@ -429,6 +431,8 @@ public abstract class ReplayingDecoder> return; } + needsCleanup = true; + if (cumulation == null) { // the cumulation buffer is not created yet so just pass the input // to callDecode(...) method @@ -570,8 +574,10 @@ public abstract class ReplayingDecoder> throws Exception { try { ChannelBuffer cumulation = this.cumulation; - if (cumulation == null) { + if (!needsCleanup) { return; + } else { + needsCleanup = false; } this.cumulation = null; From 5f6b419bb92ec2c98d4b149f2a8a3f2a056ef0c9 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 21 Apr 2012 16:00:23 +0200 Subject: [PATCH 046/134] Allow to specify the local address when connect. See #276 --- .../channel/local/LocalClientChannelSink.java | 5 +- .../netty/channel/local/LocalClientTest.java | 88 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 transport/src/test/java/io/netty/channel/local/LocalClientTest.java diff --git a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java index 9b6bd455de..f7c3e51116 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java @@ -127,7 +127,10 @@ final class LocalClientChannelSink extends AbstractChannelSink { DefaultLocalChannel acceptedChannel = DefaultLocalChannel.create(serverChannel, serverChannel.getFactory(), pipeline, this, channel); channel.pairedChannel = acceptedChannel; - bind(channel, succeededFuture(channel), new LocalAddress(LocalAddress.EPHEMERAL)); + // check if the channel was bound before. See #276 + if (!channel.isBound()) { + bind(channel, succeededFuture(channel), new LocalAddress(LocalAddress.EPHEMERAL)); + } channel.remoteAddress = serverChannel.getLocalAddress(); channel.setConnected(); fireChannelConnected(channel, serverChannel.getLocalAddress()); diff --git a/transport/src/test/java/io/netty/channel/local/LocalClientTest.java b/transport/src/test/java/io/netty/channel/local/LocalClientTest.java new file mode 100644 index 0000000000..e00c84b8ad --- /dev/null +++ b/transport/src/test/java/io/netty/channel/local/LocalClientTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2011 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.local; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPipelineFactory; +import io.netty.channel.ChannelStateEvent; +import io.netty.channel.Channels; +import io.netty.channel.ExceptionEvent; +import io.netty.channel.SimpleChannelUpstreamHandler; +import org.junit.Test; + +public class LocalClientTest { + + @Test + public void testLocalAddressBind() throws InterruptedException { + ServerBootstrap sb = new ServerBootstrap(new DefaultLocalServerChannelFactory()); + + LocalAddress addr = new LocalAddress("Server"); + LocalAddress addr2 = new LocalAddress("C1"); + sb.bind(addr); + + + ClientBootstrap cb = new ClientBootstrap(new DefaultLocalClientChannelFactory()); + final ExceptionHandler handler = new ExceptionHandler(); + cb.setPipelineFactory(new ChannelPipelineFactory() { + + @Override + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(handler); + } + }); + ChannelFuture cf = cb.connect(addr, addr2).awaitUninterruptibly(); + + assertTrue(cf.isSuccess()); + assertTrue(cf.getChannel().close().awaitUninterruptibly().isSuccess()); + + assertNull(handler.await()); + sb.releaseExternalResources(); + cb.releaseExternalResources(); + } + + final class ExceptionHandler extends SimpleChannelUpstreamHandler { + private final CountDownLatch latch = new CountDownLatch(1); + private Throwable t; + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + super.exceptionCaught(ctx, e); + t = e.getCause(); + } + + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + super.channelClosed(ctx, e); + latch.countDown(); + } + + public Throwable await() throws InterruptedException { + latch.await(); + return t; + + } + + } +} From 980d96cf581493124028bb60cc4dc213c3f2737e Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 21 Apr 2012 16:12:08 +0200 Subject: [PATCH 047/134] Fix possible NPE. See #274 --- .../io/netty/example/proxy/HexDumpProxyInboundHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/src/main/java/io/netty/example/proxy/HexDumpProxyInboundHandler.java b/example/src/main/java/io/netty/example/proxy/HexDumpProxyInboundHandler.java index 839a7d8d6a..2a4046e570 100644 --- a/example/src/main/java/io/netty/example/proxy/HexDumpProxyInboundHandler.java +++ b/example/src/main/java/io/netty/example/proxy/HexDumpProxyInboundHandler.java @@ -100,7 +100,9 @@ public class HexDumpProxyInboundHandler extends SimpleChannelUpstreamHandler { // the incoming traffic from the outboundChannel. synchronized (trafficLock) { if (e.getChannel().isWritable()) { - outboundChannel.setReadable(true); + if (outboundChannel != null) { + outboundChannel.setReadable(true); + } } } } From 77d2f9c4ef5ae5070123cee4bca9870ff5f4c5fa Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 22 Apr 2012 12:56:37 +0200 Subject: [PATCH 048/134] Upgrade and Connection header must be matched in a case-insensitive manner in WebSocket 08 and 13. See #278 --- .../http/websocketx/WebSocketClientHandshaker08.java | 10 +++++++--- .../http/websocketx/WebSocketClientHandshaker13.java | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index d22fc965fa..4e8f24598d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -181,17 +181,21 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { } String upgrade = response.getHeader(Names.UPGRADE); - if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) { + // Upgrade header should be matched case-insensitive. + // See https://github.com/netty/netty/issues/278 + if (upgrade == null || !upgrade.toLowerCase().equals(Values.WEBSOCKET.toLowerCase())) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE)); } + // Connection header should be matched case-insensitive. + // See https://github.com/netty/netty/issues/278 String connection = response.getHeader(Names.CONNECTION); - if (connection == null || !connection.equals(Values.UPGRADE)) { + if (connection == null || !connection.toLowerCase().equals(Values.UPGRADE.toLowerCase())) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION)); } - + String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT); if (accept == null || !accept.equals(expectedChallengeResponseString)) { throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index cce6f55265..6d2348b4ae 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -178,13 +178,17 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { } String upgrade = response.getHeader(Names.UPGRADE); - if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) { + // Upgrade header should be matched case-insensitive. + // See https://github.com/netty/netty/issues/278 + if (upgrade == null || !upgrade.toLowerCase().equals(Values.WEBSOCKET.toLowerCase())) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE)); } + // Connection header should be matched case-insensitive. + // See https://github.com/netty/netty/issues/278 String connection = response.getHeader(Names.CONNECTION); - if (connection == null || !connection.equals(Values.UPGRADE)) { + if (connection == null || !connection.toLowerCase().equals(Values.UPGRADE.toLowerCase())) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION)); } From 6e2e9fb3c5e2d79c37cc618549aa4b7fe51499b4 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 24 Apr 2012 20:32:06 +0200 Subject: [PATCH 049/134] Use gathering writes if java version is >= 7 . See #269 and #271 --- .../netty/buffer/CompositeChannelBuffer.java | 5 ++ .../channel/socket/nio/SendBufferPool.java | 88 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java index e960ffab18..659ec30177 100644 --- a/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java @@ -15,6 +15,8 @@ */ package io.netty.buffer; +import io.netty.util.internal.DetectionUtil; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -291,6 +293,9 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + if (DetectionUtil.javaVersion() >= 7) { + return (int) out.write(toByteBuffers(index, length)); + } // XXX Gathering write is not supported because of a known issue. // See http://bugs.sun.com/view_bug.do?bug_id=6210541 // This issue appeared in 2004 and is still unresolved!? diff --git a/transport/src/main/java/io/netty/channel/socket/nio/SendBufferPool.java b/transport/src/main/java/io/netty/channel/socket/nio/SendBufferPool.java index 5a40272792..060adf6912 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/SendBufferPool.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/SendBufferPool.java @@ -20,10 +20,13 @@ import java.lang.ref.SoftReference; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; +import java.nio.channels.GatheringByteChannel; import java.nio.channels.WritableByteChannel; import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.CompositeChannelBuffer; import io.netty.channel.FileRegion; +import io.netty.util.internal.DetectionUtil; public class SendBufferPool { @@ -67,6 +70,11 @@ public class SendBufferPool { if (src.isDirect()) { return new UnpooledSendBuffer(src.toByteBuffer()); } + + if (src instanceof CompositeChannelBuffer && DetectionUtil.javaVersion() >= 7) { + return new GatheringSendBuffer(src.toByteBuffers()); + } + if (src.readableBytes() > DEFAULT_PREALLOCATION_SIZE) { return new UnpooledSendBuffer(src.toByteBuffer()); } @@ -261,6 +269,86 @@ public class SendBufferPool { } } + class GatheringSendBuffer implements SendBuffer { + + private final ByteBuffer[] buffers; + private final int last; + private long written; + private final int total; + + GatheringSendBuffer(ByteBuffer[] buffers) { + this.buffers = buffers; + this.last = buffers.length - 1; + int total = 0; + for (ByteBuffer buf: buffers) { + total += buf.remaining(); + } + this.total = total; + } + + @Override + public boolean finished() { + return !buffers[last].hasRemaining(); + } + + @Override + public long writtenBytes() { + return written; + } + + @Override + public long totalBytes() { + return total; + } + + @Override + public long transferTo(WritableByteChannel ch) throws IOException { + if (ch instanceof GatheringByteChannel) { + long w = ((GatheringByteChannel) ch).write(buffers); + written += w; + return w; + } else { + int send = 0; + for (ByteBuffer buf: buffers) { + if (buf.hasRemaining()) { + int w = ch.write(buf); + if (w == 0) { + break; + } else { + send += w; + } + } + } + written += send; + return send; + } + } + + @Override + public long transferTo(DatagramChannel ch, SocketAddress raddr) throws IOException { + int send = 0; + for (ByteBuffer buf: buffers) { + if (buf.hasRemaining()) { + int w = ch.send(buf, raddr); + if (w == 0) { + break; + } else { + send += w; + } + } + } + written += send; + + return send; + } + + @Override + public void release() { + // nothing todo + } + + } + static final class FileSendBuffer implements SendBuffer { private final FileRegion file; From 476cf97b9789e587e46d87b4798927ee8df2a2bc Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 24 Apr 2012 20:41:10 +0200 Subject: [PATCH 050/134] Make the cumulation usage more memory efficient. See #280 --- .../codec/frame/FixedLengthFrameDecoder.java | 4 +- .../handler/codec/frame/FrameDecoder.java | 51 ++++++++++++++---- .../codec/replay/ReplayingDecoder.java | 52 +++++++++++++++---- 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/codec/src/main/java/io/netty/handler/codec/frame/FixedLengthFrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/frame/FixedLengthFrameDecoder.java index 57863f3208..6a2ec11557 100644 --- a/codec/src/main/java/io/netty/handler/codec/frame/FixedLengthFrameDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/frame/FixedLengthFrameDecoder.java @@ -17,7 +17,6 @@ package io.netty.handler.codec.frame; import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBufferFactory; -import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; @@ -78,8 +77,7 @@ public class FixedLengthFrameDecoder extends FrameDecoder { protected ChannelBuffer newCumulationBuffer(ChannelHandlerContext ctx, int minimumCapacity) { ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); if (allocateFullBuffer) { - return ChannelBuffers.dynamicBuffer( - factory.getDefaultOrder(), frameLength, ctx.getChannel().getConfig().getBufferFactory()); + return factory.getBuffer(frameLength); } return super.newCumulationBuffer(ctx, minimumCapacity); } diff --git a/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java index 67c23316e5..3e1be81a8d 100644 --- a/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java @@ -210,15 +210,47 @@ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { (this.cumulation = newCumulationBuffer(ctx, input.readableBytes())).writeBytes(input); } } else { - ChannelBuffer cumulation = this.cumulation; assert cumulation.readable(); - if (cumulation.writableBytes() < input.readableBytes()) { - cumulation.discardReadBytes(); + boolean fit = false; + + int readable = input.readableBytes(); + int writable = cumulation.writableBytes(); + int w = writable - readable; + if (w < 0) { + int readerIndex = cumulation.readerIndex(); + if (w + readerIndex >= 0) { + // the input will fit if we discard all read bytes, so do it + cumulation.discardReadBytes(); + fit = true; + } + } else { + + // ok the input fit into the cumulation buffer + fit = true; } - cumulation.writeBytes(input); - callDecode(ctx, e.getChannel(), cumulation, e.getRemoteAddress()); - if (!cumulation.readable()) { + + + ChannelBuffer buf; + if (fit) { + // the input fit in the cumulation buffer so copy it over + buf = this.cumulation; + buf.writeBytes(input); + } else { + // wrap the cumulation and input + buf = ChannelBuffers.wrappedBuffer(cumulation, input); + this.cumulation = buf; + } + + + callDecode(ctx, e.getChannel(), buf, e.getRemoteAddress()); + if (!buf.readable()) { + // nothing readable left so reset the state this.cumulation = null; + } else { + // create a new buffer and copy the readable buffer into it + this.cumulation = newCumulationBuffer(ctx, buf.readableBytes()); + this.cumulation.writeBytes(buf); + } } } @@ -350,9 +382,7 @@ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { /** * Create a new {@link ChannelBuffer} which is used for the cumulation. - * Be aware that this MUST be a dynamic buffer. Sub-classes may override - * this to provide a dynamic {@link ChannelBuffer} which has some - * pre-allocated size that better fit their need. + * Sub-classes may override this. * * @param ctx {@link ChannelHandlerContext} for this handler * @return buffer the {@link ChannelBuffer} which is used for cumulation @@ -360,7 +390,6 @@ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { protected ChannelBuffer newCumulationBuffer( ChannelHandlerContext ctx, int minimumCapacity) { ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); - return ChannelBuffers.dynamicBuffer( - factory.getDefaultOrder(), Math.max(minimumCapacity, 256), factory); + return factory.getBuffer(Math.max(minimumCapacity, 256)); } } diff --git a/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java b/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java index 24f7723354..7bbd8e5ade 100644 --- a/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java @@ -473,16 +473,49 @@ public abstract class ReplayingDecoder> replayable = ReplayingDecoderBuffer.EMPTY_BUFFER; } } else { - ChannelBuffer cumulation = this.cumulation; assert cumulation.readable(); - if (cumulation.writableBytes() < input.readableBytes()) { - cumulation.discardReadBytes(); + boolean fit = false; + + int readable = input.readableBytes(); + int writable = cumulation.writableBytes(); + int w = writable - readable; + if (w < 0) { + int readerIndex = cumulation.readerIndex(); + if (w + readerIndex >= 0) { + // the input will fit if we discard all read bytes, so do it + cumulation.discardReadBytes(); + fit = true; + } + } else { + + // ok the input fit into the cumulation buffer + fit = true; } - cumulation.writeBytes(input); - callDecode(ctx, e.getChannel(), cumulation, replayable, e.getRemoteAddress()); - if (!cumulation.readable()) { + + ChannelBuffer buf; + if (fit) { + // the input fit in the cumulation buffer so copy it over + buf = this.cumulation; + buf.writeBytes(input); + } else { + // wrap the cumulation and input + buf = ChannelBuffers.wrappedBuffer(cumulation, input); + this.cumulation = buf; + replayable = new ReplayingDecoderBuffer(cumulation); + } + + + callDecode(ctx, e.getChannel(), buf, replayable, e.getRemoteAddress()); + if (!buf.readable()) { + // nothing readable left so reset the state this.cumulation = null; replayable = ReplayingDecoderBuffer.EMPTY_BUFFER; + } else { + // create a new buffer and copy the readable buffer into it + this.cumulation = newCumulationBuffer(ctx, buf.readableBytes()); + this.cumulation.writeBytes(buf); + replayable = new ReplayingDecoderBuffer(this.cumulation); + } } } @@ -605,9 +638,7 @@ public abstract class ReplayingDecoder> /** * Create a new {@link ChannelBuffer} which is used for the cumulation. - * Be aware that this MUST be a dynamic buffer. Sub-classes may override - * this to provide a dynamic {@link ChannelBuffer} which has some - * pre-allocated size that better fit their need. + * Sub-classes may override this. * * @param ctx {@link ChannelHandlerContext} for this handler * @return buffer the {@link ChannelBuffer} which is used for cumulation @@ -615,7 +646,6 @@ public abstract class ReplayingDecoder> protected ChannelBuffer newCumulationBuffer( ChannelHandlerContext ctx, int minimumCapacity) { ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); - return ChannelBuffers.dynamicBuffer( - factory.getDefaultOrder(), Math.max(minimumCapacity, 256), factory); + return factory.getBuffer(Math.max(minimumCapacity, 256)); } } From a8b9e27c92682dd59135622df15005d6ab0b8c09 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 25 Apr 2012 09:24:51 +0200 Subject: [PATCH 051/134] NioDatagramWorker.ChannelRegistionTask should handle ClosedChannelException gracefully. See #281 and #277 --- .../io/netty/channel/socket/nio/NioDatagramWorker.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java index 712b21fa99..ad84e771a6 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java @@ -29,6 +29,7 @@ import io.netty.channel.MessageEvent; import io.netty.channel.ReceiveBufferSizePredictor; import io.netty.channel.socket.nio.SendBufferPool.SendBuffer; +import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; @@ -154,13 +155,15 @@ public class NioDatagramWorker extends AbstractNioWorker { if (future != null) { future.setSuccess(); } - } catch (final ClosedChannelException e) { + } catch (final IOException e) { if (future != null) { future.setFailure(e); } close(channel, succeededFuture(channel)); - throw new ChannelException( - "Failed to register a socket to the selector.", e); + if (!(e instanceof ClosedChannelException)) { + throw new ChannelException( + "Failed to register a socket to the selector.", e); + } } } From 6009a413b910940c551c209cda057f50823f4e76 Mon Sep 17 00:00:00 2001 From: vibul Date: Thu, 26 Apr 2012 10:09:12 +1000 Subject: [PATCH 052/134] Issue #283 - Support max frame length for web socket to limit chance of DOS attack --- .../websocketx/WebSocket00FrameDecoder.java | 4 +-- .../websocketx/WebSocket08FrameDecoder.java | 17 ++++++++-- .../websocketx/WebSocket13FrameDecoder.java | 9 ++++-- .../websocketx/WebSocketClientHandshaker.java | 14 ++++++++- .../WebSocketClientHandshaker00.java | 15 ++++----- .../WebSocketClientHandshaker08.java | 18 ++++++----- .../WebSocketClientHandshaker13.java | 12 ++++--- .../WebSocketClientHandshakerFactory.java | 31 ++++++++++++++++--- .../websocketx/WebSocketServerHandshaker.java | 30 ++++++++++++------ .../WebSocketServerHandshaker00.java | 10 ++++-- .../WebSocketServerHandshaker08.java | 11 +++++-- .../WebSocketServerHandshaker13.java | 11 +++++-- .../WebSocketServerHandshakerFactory.java | 29 +++++++++++++++-- .../WebSocketServerHandshaker00Test.java | 2 +- .../WebSocketServerHandshaker08Test.java | 2 +- .../WebSocketServerHandshaker13Test.java | 2 +- .../websocketx/client/WebSocketClient.java | 2 +- 17 files changed, 161 insertions(+), 58 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java index 69ca9776cf..24561287c6 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java @@ -35,7 +35,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder { private static final int DEFAULT_MAX_FRAME_SIZE = 16384; - private final int maxFrameSize; + private final long maxFrameSize; private boolean receivedClosingHandshake; public WebSocket00FrameDecoder() { @@ -49,7 +49,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder { * @param maxFrameSize * the maximum frame size to decode */ - public WebSocket00FrameDecoder(int maxFrameSize) { + public WebSocket00FrameDecoder(long maxFrameSize) { this.maxFrameSize = maxFrameSize; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java index b590c11d8a..5ffdeccfa0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java @@ -82,6 +82,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder this.maxFramePayloadLength) { + protocolViolation(channel, "Max frame length of " + this.maxFramePayloadLength + " has been exceeded."); + return null; + } if (logger.isDebugEnabled()) { logger.debug("Decoding WebSocket Frame length=" + framePayloadLength); } @@ -236,10 +245,12 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder customHeaders; + private final long maxFramePayloadLength; + /** * Base constructor * @@ -51,13 +53,16 @@ public abstract class WebSocketClientHandshaker { * Sub protocol request sent to the server. * @param customHeaders * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload */ public WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol, - Map customHeaders) { + Map customHeaders, long maxFramePayloadLength) { this.webSocketUrl = webSocketUrl; this.version = version; expectedSubprotocol = subprotocol; this.customHeaders = customHeaders; + this.maxFramePayloadLength = maxFramePayloadLength; } /** @@ -74,6 +79,13 @@ public abstract class WebSocketClientHandshaker { return version; } + /** + * Returns the max length for any frame's payload + */ + public long getMaxFramePayloadLength() { + return maxFramePayloadLength; + } + /** * Flag to indicate if the opening handshake is complete */ diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index fc5ae38dac..bc51b5cd75 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -60,11 +60,12 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { * Sub protocol request sent to the server. * @param customHeaders * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload */ public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol, - Map customHeaders) { - super(webSocketURL, version, subprotocol, customHeaders); - + Map customHeaders, long maxFramePayloadLength) { + super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); } /** @@ -138,17 +139,16 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { request.addHeader(Names.UPGRADE, Values.WEBSOCKET); request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.HOST, wsURL.getHost()); - + int wsPort = wsURL.getPort(); String originValue = "http://" + wsURL.getHost(); if (wsPort != 80 && wsPort != 443) { - // if the port is not standard (80/443) its needed to add the port to the header. + // if the port is not standard (80/443) its needed to add the port to the header. // See http://tools.ietf.org/html/rfc6454#section-6.2 originValue = originValue + ":" + wsPort; } request.addHeader(Names.ORIGIN, originValue); - request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); if (getExpectedSubprotocol() != null && !getExpectedSubprotocol().equals("")) { @@ -220,7 +220,8 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); setActualSubprotocol(protocol); - channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder()); + channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", + new WebSocket00FrameDecoder(this.getMaxFramePayloadLength())); setHandshakeComplete(); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 4e8f24598d..e0728c1b4e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -54,7 +54,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { private final boolean allowExtensions; /** - * Constructor specifying the destination web socket location and version to initiate + * Constructor * * @param webSocketURL * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be @@ -67,10 +67,12 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { * Allow extensions to be used in the reserved bits of the web socket frame * @param customHeaders * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload */ public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol, - boolean allowExtensions, Map customHeaders) { - super(webSocketURL, version, subprotocol, customHeaders); + boolean allowExtensions, Map customHeaders, long maxFramePayloadLength) { + super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); this.allowExtensions = allowExtensions; } @@ -122,11 +124,11 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { request.addHeader(Names.CONNECTION, Values.UPGRADE); request.addHeader(Names.SEC_WEBSOCKET_KEY, key); request.addHeader(Names.HOST, wsURL.getHost()); - + int wsPort = wsURL.getPort(); String originValue = "http://" + wsURL.getHost(); if (wsPort != 80 && wsPort != 443) { - // if the port is not standard (80/443) its needed to add the port to the header. + // if the port is not standard (80/443) its needed to add the port to the header. // See http://tools.ietf.org/html/rfc6454#section-6.2 originValue = originValue + ":" + wsPort; } @@ -134,7 +136,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { // Use Sec-WebSocket-Origin // See https://github.com/netty/netty/issues/264 request.addHeader(Names.SEC_WEBSOCKET_ORIGIN, originValue); - + if (protocol != null && !protocol.equals("")) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); } @@ -195,7 +197,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION)); } - + String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT); if (accept == null || !accept.equals(expectedChallengeResponseString)) { throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, @@ -203,7 +205,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { } channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", - new WebSocket08FrameDecoder(false, allowExtensions)); + new WebSocket08FrameDecoder(false, allowExtensions, this.getMaxFramePayloadLength())); setHandshakeComplete(); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 6d2348b4ae..78eade9787 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -54,7 +54,7 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { private final boolean allowExtensions; /** - * Constructor specifying the destination web socket location and version to initiate + * Constructor * * @param webSocketURL * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be @@ -67,13 +67,15 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { * Allow extensions to be used in the reserved bits of the web socket frame * @param customHeaders * Map of custom headers to add to the client request + * @param maxFramePayloadLength + * Maximum length of a frame's payload */ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol, - boolean allowExtensions, Map customHeaders) { - super(webSocketURL, version, subprotocol, customHeaders); + boolean allowExtensions, Map customHeaders, long maxFramePayloadLength) { + super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); this.allowExtensions = allowExtensions; } - + /** * /** *

@@ -200,7 +202,7 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { } channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", - new WebSocket13FrameDecoder(false, allowExtensions)); + new WebSocket13FrameDecoder(false, allowExtensions, this.getMaxFramePayloadLength())); setHandshakeComplete(); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java index a199299411..e6d6b9c542 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java @@ -41,17 +41,40 @@ public class WebSocketClientHandshakerFactory { */ public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol, boolean allowExtensions, Map customHeaders) throws WebSocketHandshakeException { + return newHandshaker(webSocketURL, version, subprotocol, allowExtensions, customHeaders, Long.MAX_VALUE); + } + + /** + * Instances a new handshaker + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. Null if no sub-protocol support is required. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Custom HTTP headers to send during the handshake + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's requirement may + * reduce denial of service attacks using long data frames. + * @throws WebSocketHandshakeException + */ + public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, Map customHeaders, long maxFramePayloadLength) throws WebSocketHandshakeException { if (version == WebSocketVersion.V13) { - return new WebSocketClientHandshaker13(webSocketURL, version, subprotocol, allowExtensions, customHeaders); + return new WebSocketClientHandshaker13(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength); } if (version == WebSocketVersion.V08) { - return new WebSocketClientHandshaker08(webSocketURL, version, subprotocol, allowExtensions, customHeaders); + return new WebSocketClientHandshaker08(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength); } if (version == WebSocketVersion.V00) { - return new WebSocketClientHandshaker00(webSocketURL, version, subprotocol, customHeaders); + return new WebSocketClientHandshaker00(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength); } throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported."); - } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index e22561bc88..3060c8c9f3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -33,9 +33,11 @@ public abstract class WebSocketServerHandshaker { private final WebSocketVersion version; + private final long maxFramePayloadLength; + /** * Constructor specifying the destination web socket location - * + * * @param version * the protocol version * @param webSocketUrl @@ -43,9 +45,11 @@ public abstract class WebSocketServerHandshaker { * sent to this URL. * @param subprotocols * CSV of supported protocols. Null if sub protocols not supported. + * @param maxFramePayloadLength + * Maximum length of a frame's payload */ - protected WebSocketServerHandshaker( - WebSocketVersion version, String webSocketUrl, String subprotocols) { + protected WebSocketServerHandshaker(WebSocketVersion version, String webSocketUrl, String subprotocols, + long maxFramePayloadLength) { this.version = version; this.webSocketUrl = webSocketUrl; if (subprotocols != null) { @@ -57,6 +61,7 @@ public abstract class WebSocketServerHandshaker { } else { this.subprotocols = new String[0]; } + this.maxFramePayloadLength = maxFramePayloadLength; } /** @@ -71,7 +76,7 @@ public abstract class WebSocketServerHandshaker { */ public Set getSubprotocols() { Set ret = new LinkedHashSet(); - for (String p: this.subprotocols) { + for (String p : this.subprotocols) { ret.add(p); } return ret; @@ -84,9 +89,16 @@ public abstract class WebSocketServerHandshaker { return version; } + /** + * Returns the max length for any frame's payload + */ + public long getMaxFramePayloadLength() { + return maxFramePayloadLength; + } + /** * Performs the opening handshake - * + * * @param channel * Channel * @param req @@ -96,7 +108,7 @@ public abstract class WebSocketServerHandshaker { /** * Performs the closing handshake - * + * * @param channel * Channel * @param frame @@ -106,7 +118,7 @@ public abstract class WebSocketServerHandshaker { /** * Selects the first matching supported sub protocol - * + * * @param requestedSubprotocols * CSV of protocols to be supported. e.g. "chat, superchat" * @return First matching supported sub protocol. Null if not found. @@ -117,10 +129,10 @@ public abstract class WebSocketServerHandshaker { } String[] requesteSubprotocolArray = requestedSubprotocols.split(","); - for (String p: requesteSubprotocolArray) { + for (String p : requesteSubprotocolArray) { String requestedSubprotocol = p.trim(); - for (String supportedSubprotocol: subprotocols) { + for (String supportedSubprotocol : subprotocols) { if (requestedSubprotocol.equals(supportedSubprotocol)) { return requestedSubprotocol; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java index 4af7445fd5..ed8d8e8411 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java @@ -58,9 +58,12 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { * sent to this URL. * @param subprotocols * CSV of supported protocols + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's requirement may + * reduce denial of service attacks using long data frames. */ - public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) { - super(WebSocketVersion.V00, webSocketURL, subprotocols); + public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, long maxFramePayloadLength) { + super(WebSocketVersion.V00, webSocketURL, subprotocols, maxFramePayloadLength); } /** @@ -167,7 +170,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { if (p.get(HttpChunkAggregator.class) != null) { p.remove(HttpChunkAggregator.class); } - p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder()); + p.replace(HttpRequestDecoder.class, "wsdecoder", + new WebSocket00FrameDecoder(this.getMaxFramePayloadLength())); ChannelFuture future = channel.write(res); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index 5fabceaf86..18f1b72ebd 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -59,9 +59,13 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { * CSV of supported protocols * @param allowExtensions * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's requirement may + * reduce denial of service attacks using long data frames. */ - public WebSocketServerHandshaker08(String webSocketURL, String subprotocols, boolean allowExtensions) { - super(WebSocketVersion.V08, webSocketURL, subprotocols); + public WebSocketServerHandshaker08(String webSocketURL, String subprotocols, boolean allowExtensions, + long maxFramePayloadLength) { + super(WebSocketVersion.V08, webSocketURL, subprotocols, maxFramePayloadLength); this.allowExtensions = allowExtensions; } @@ -142,7 +146,8 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { p.remove(HttpChunkAggregator.class); } - p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket08FrameDecoder(true, allowExtensions)); + p.replace(HttpRequestDecoder.class, "wsdecoder", + new WebSocket08FrameDecoder(true, allowExtensions, this.getMaxFramePayloadLength())); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false)); return future; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index f8debb39c9..f3396f1c7f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -60,9 +60,13 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { * CSV of supported protocols * @param allowExtensions * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's requirement may + * reduce denial of service attacks using long data frames. */ - public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions) { - super(WebSocketVersion.V13, webSocketURL, subprotocols); + public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions, + long maxFramePayloadLength) { + super(WebSocketVersion.V13, webSocketURL, subprotocols, maxFramePayloadLength); this.allowExtensions = allowExtensions; } @@ -143,7 +147,8 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { p.remove(HttpChunkAggregator.class); } - p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, allowExtensions)); + p.replace(HttpRequestDecoder.class, "wsdecoder", + new WebSocket13FrameDecoder(true, allowExtensions, this.getMaxFramePayloadLength())); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false)); return future; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java index 4428bc5ea0..5f1cde5ba2 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java @@ -34,6 +34,8 @@ public class WebSocketServerHandshakerFactory { private final boolean allowExtensions; + private final long maxFramePayloadLength; + /** * Constructor specifying the destination web socket location * @@ -46,10 +48,31 @@ public class WebSocketServerHandshakerFactory { * Allow extensions to be used in the reserved bits of the web socket frame */ public WebSocketServerHandshakerFactory(String webSocketURL, String subprotocols, boolean allowExtensions) { + this(webSocketURL, subprotocols, allowExtensions, Long.MAX_VALUE); + } + + /** + * Constructor specifying the destination web socket location + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param subprotocols + * CSV of supported protocols. Null if sub protocols not supported. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param maxFramePayloadLength + * Maximum allowable frame payload length. Setting this value to your application's requirement may + * reduce denial of service attacks using long data frames. + */ + public WebSocketServerHandshakerFactory(String webSocketURL, String subprotocols, boolean allowExtensions, + long maxFramePayloadLength) { this.webSocketURL = webSocketURL; this.subprotocols = subprotocols; this.allowExtensions = allowExtensions; + this.maxFramePayloadLength = maxFramePayloadLength; } + /** * Instances a new handshaker @@ -63,16 +86,16 @@ public class WebSocketServerHandshakerFactory { if (version != null) { if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) { // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification). - return new WebSocketServerHandshaker13(webSocketURL, subprotocols, allowExtensions); + return new WebSocketServerHandshaker13(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength); } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) { // Version 8 of the wire protocol - version 10 of the draft hybi specification. - return new WebSocketServerHandshaker08(webSocketURL, subprotocols, allowExtensions); + return new WebSocketServerHandshaker08(webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength); } else { return null; } } else { // Assume version 00 where version header was not specified - return new WebSocketServerHandshaker00(webSocketURL, subprotocols); + return new WebSocketServerHandshaker00(webSocketURL, subprotocols, maxFramePayloadLength); } } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java index 8e98282542..ef18d87db6 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00Test.java @@ -75,7 +75,7 @@ public class WebSocketServerHandshaker00Test { ChannelBuffer buffer = ChannelBuffers.copiedBuffer("^n:ds[4U", Charset.defaultCharset()); req.setContent(buffer); - WebSocketServerHandshaker00 handsaker = new WebSocketServerHandshaker00("ws://example.com/chat", "chat"); + WebSocketServerHandshaker00 handsaker = new WebSocketServerHandshaker00("ws://example.com/chat", "chat", Long.MAX_VALUE); handsaker.handshake(channelMock, req); Assert.assertEquals("ws://example.com/chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_LOCATION)); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java index 6ca8a495d1..033d02672a 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08Test.java @@ -68,7 +68,7 @@ public class WebSocketServerHandshaker08Test { req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat"); req.setHeader(Names.SEC_WEBSOCKET_VERSION, "8"); - WebSocketServerHandshaker08 handsaker = new WebSocketServerHandshaker08("ws://example.com/chat", "chat", false); + WebSocketServerHandshaker08 handsaker = new WebSocketServerHandshaker08("ws://example.com/chat", "chat", false, Long.MAX_VALUE); handsaker.handshake(channelMock, req); Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT)); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java index 257e484fa5..57caf44313 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13Test.java @@ -67,7 +67,7 @@ public class WebSocketServerHandshaker13Test { req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, "http://example.com"); req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat"); req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13"); - WebSocketServerHandshaker13 handsaker = new WebSocketServerHandshaker13("ws://example.com/chat", "chat", false); + WebSocketServerHandshaker13 handsaker = new WebSocketServerHandshaker13("ws://example.com/chat", "chat", false, Long.MAX_VALUE); handsaker.handshake(channelMock, req); Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT)); diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java index a2a53d553d..f099662fc3 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java +++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java @@ -117,7 +117,7 @@ public class WebSocketClient { // Send 10 messages and wait for responses logger.info("WebSocket Client sending message"); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 1000; i++) { ch.write(new TextWebSocketFrame("Message #" + i)); } From 9d555b0b97618c8bdee03a7fa20b3c0f1e8321dc Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 26 Apr 2012 14:53:31 +0200 Subject: [PATCH 053/134] OioWorker failed to fire channelConnected event for OioAcceptedSocketChannel which is fixed now. This also fix a race which can could lead to missing events. See #287 --- .../channel/socket/oio/AbstractOioWorker.java | 33 ++++++++++++++----- .../netty/channel/socket/oio/OioWorker.java | 13 ++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java index e0b12fef11..ef97ecb2a4 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -36,6 +36,8 @@ abstract class AbstractOioWorker implements Worker protected final C channel; + private volatile boolean done; + /** * If this worker has been started thread will be a reference to the thread * used when starting. i.e. the current thread when the run method is executed. @@ -66,19 +68,20 @@ abstract class AbstractOioWorker implements Worker } } + boolean cont = false; try { - boolean cont = process(); - + cont = process(); + + } catch (Throwable t) { + if (!channel.isSocketClosed()) { + fireExceptionCaught(channel, t); + } + } finally { processEventQueue(); if (!cont) { break; } - } catch (Throwable t) { - if (!channel.isSocketClosed()) { - fireExceptionCaught(channel, t); - } - break; } } @@ -88,6 +91,14 @@ abstract class AbstractOioWorker implements Worker // Clean up. close(channel, succeededFuture(channel), true); + + // Mark the worker event loop as done so we know that we need to run tasks directly and not queue them + // See #287 + done = true; + + // just to make we don't have something left + processEventQueue(); + } static boolean isIoThread(AbstractOioChannel channel) { @@ -96,7 +107,11 @@ abstract class AbstractOioWorker implements Worker @Override public void executeInIoThread(Runnable task) { - if (Thread.currentThread() == thread) { + // check if the current thread is the worker thread + // + // Also check if the event loop of the worker is complete. If so we need to run the task now. + // See #287 + if (Thread.currentThread() == thread || done) { task.run(); } else { boolean added = eventQueue.offer(task); @@ -107,7 +122,7 @@ abstract class AbstractOioWorker implements Worker } } - private void processEventQueue() throws IOException { + private void processEventQueue() { for (;;) { final Runnable task = eventQueue.poll(); if (task == null) { diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java index fa4578d3d4..a98d5bb88f 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java @@ -39,6 +39,19 @@ class OioWorker extends AbstractOioWorker { super(channel); } + @Override + public void run() { + boolean fireConnected = channel instanceof OioAcceptedSocketChannel; + if (fireConnected && channel.isOpen()) { + // Fire the channelConnected event for OioAcceptedSocketChannel. + // See #287 + fireChannelConnected(channel, channel.getRemoteAddress()); + + } + super.run(); + } + + @Override boolean process() throws IOException { byte[] buf; From e61a9ce7c17011a9b2b052a31207191eba543266 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 26 Apr 2012 16:30:46 +0300 Subject: [PATCH 054/134] Make sure netty detects the right java version when running on android. See #282 --- .../main/java/io/netty/util/internal/DetectionUtil.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/main/java/io/netty/util/internal/DetectionUtil.java b/common/src/main/java/io/netty/util/internal/DetectionUtil.java index 578d5b0401..b16bca6298 100644 --- a/common/src/main/java/io/netty/util/internal/DetectionUtil.java +++ b/common/src/main/java/io/netty/util/internal/DetectionUtil.java @@ -75,6 +75,15 @@ public final class DetectionUtil { } private static int javaVersion0() { + try { + // Check if its android, if so handle it the same way as java6. + // + // See https://github.com/netty/netty/issues/282 + Class.forName("android.app.Application"); + return 6; + } catch (ClassNotFoundException e) { + //Ignore + } try { Deflater.class.getDeclaredField("SYNC_FLUSH"); return 7; From 40e9b96764d47465835ef3b23f28c5fe38cdd604 Mon Sep 17 00:00:00 2001 From: vibul Date: Fri, 27 Apr 2012 10:43:34 +1000 Subject: [PATCH 055/134] We need to keep the old constructor to not break the API. --- .../websocketx/WebSocket00FrameDecoder.java | 11 ++++++++++ .../websocketx/WebSocket08FrameDecoder.java | 13 ++++++++++++ .../websocketx/WebSocket13FrameDecoder.java | 13 ++++++++++++ .../websocketx/WebSocketClientHandshaker.java | 18 +++++++++++++++++ .../WebSocketClientHandshaker00.java | 18 +++++++++++++++++ .../WebSocketClientHandshaker08.java | 20 +++++++++++++++++++ .../WebSocketClientHandshaker13.java | 20 +++++++++++++++++++ .../websocketx/WebSocketServerHandshaker.java | 15 ++++++++++++++ .../WebSocketServerHandshaker00.java | 13 ++++++++++++ .../WebSocketServerHandshaker08.java | 15 ++++++++++++++ .../WebSocketServerHandshaker13.java | 15 ++++++++++++++ 11 files changed, 171 insertions(+) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java index 24561287c6..9a7304f346 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java @@ -42,6 +42,17 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder { this(DEFAULT_MAX_FRAME_SIZE); } + /** + * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client + * sends a frame size larger than {@code maxFrameSize}, the channel will be closed. + * + * @param maxFrameSize + * the maximum frame size to decode + */ + public WebSocket00FrameDecoder(int maxFrameSize) { + this.maxFrameSize = maxFrameSize; + } + /** * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client * sends a frame size larger than {@code maxFrameSize}, the channel will be closed. diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java index 5ffdeccfa0..19d81ec2eb 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java @@ -99,6 +99,19 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder customHeaders) { + this(webSocketUrl, version, subprotocol, customHeaders, Long.MAX_VALUE); + } + /** * Base constructor * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index bc51b5cd75..b52b840907 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -48,6 +48,24 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { private byte[] expectedChallengeResponseBytes; + /** + * Constructor with default values + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param customHeaders + * Map of custom headers to add to the client request + */ + public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol, + Map customHeaders) { + this(webSocketURL, version, subprotocol, customHeaders, Long.MAX_VALUE); + } + /** * Constructor specifying the destination web socket location and version to initiate * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index e0728c1b4e..5ba568e3cc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -53,6 +53,26 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { private final boolean allowExtensions; + /** + * Constructor with default values + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Map of custom headers to add to the client request + */ + public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, Map customHeaders) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, Long.MAX_VALUE); + } + /** * Constructor * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 78eade9787..5f31eed5b0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -53,6 +53,26 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { private final boolean allowExtensions; + /** + * Constructor with default values + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param version + * Version of web socket specification to use to connect to the server + * @param subprotocol + * Sub protocol request sent to the server. + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + * @param customHeaders + * Map of custom headers to add to the client request + */ + public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol, + boolean allowExtensions, Map customHeaders) { + this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, Long.MAX_VALUE); + } + /** * Constructor * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index 3060c8c9f3..e648915a6f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -35,6 +35,21 @@ public abstract class WebSocketServerHandshaker { private final long maxFramePayloadLength; + /** + * Constructor using default values + * + * @param version + * the protocol version + * @param webSocketUrl + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param subprotocols + * CSV of supported protocols. Null if sub protocols not supported. + */ + protected WebSocketServerHandshaker(WebSocketVersion version, String webSocketUrl, String subprotocols) { + this(version, webSocketUrl, subprotocols, Long.MAX_VALUE); + } + /** * Constructor specifying the destination web socket location * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java index ed8d8e8411..78a5f7669b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java @@ -50,6 +50,19 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker00.class); + /** + * Constructor with default values + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param subprotocols + * CSV of supported protocols + */ + public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) { + this(webSocketURL, subprotocols, Long.MAX_VALUE); + } + /** * Constructor specifying the destination web socket location * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index 18f1b72ebd..96eedfc580 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -49,6 +49,21 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { private final boolean allowExtensions; + /** + * Constructor using defaults + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param subprotocols + * CSV of supported protocols + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + */ + public WebSocketServerHandshaker08(String webSocketURL, String subprotocols, boolean allowExtensions) { + this(webSocketURL, subprotocols, allowExtensions, Long.MAX_VALUE); + } + /** * Constructor specifying the destination web socket location * diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index f3396f1c7f..dab9a8f8d9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -50,6 +50,21 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { private final boolean allowExtensions; + /** + * Constructor using defaults + * + * @param webSocketURL + * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be + * sent to this URL. + * @param subprotocols + * CSV of supported protocols + * @param allowExtensions + * Allow extensions to be used in the reserved bits of the web socket frame + */ + public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions) { + this(webSocketURL, subprotocols, allowExtensions, Long.MAX_VALUE); + } + /** * Constructor specifying the destination web socket location * From dcd2a10f95d0f3f30e1167330c0689da46629839 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 27 Apr 2012 07:39:26 +0200 Subject: [PATCH 056/134] Remove @deprecated constructor. See #283 --- .../http/websocketx/WebSocket00FrameDecoder.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java index 9a7304f346..24561287c6 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java @@ -42,17 +42,6 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder { this(DEFAULT_MAX_FRAME_SIZE); } - /** - * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client - * sends a frame size larger than {@code maxFrameSize}, the channel will be closed. - * - * @param maxFrameSize - * the maximum frame size to decode - */ - public WebSocket00FrameDecoder(int maxFrameSize) { - this.maxFrameSize = maxFrameSize; - } - /** * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client * sends a frame size larger than {@code maxFrameSize}, the channel will be closed. From 94b10d2d02d4a4fa0bd781caa5dcc40b1a4f72b7 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 27 Apr 2012 07:47:37 +0200 Subject: [PATCH 057/134] Remove volatile on two fields that don't need it --- .../io/netty/example/http/upload/HttpUploadServerHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java index 15579d10a0..06d09a87f3 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java @@ -67,9 +67,9 @@ public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler { private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpUploadServerHandler.class); - private volatile HttpRequest request; + private HttpRequest request; - private volatile boolean readingChunks; + private boolean readingChunks; private final StringBuilder responseContent = new StringBuilder(); From 65876fa7fbe2f63a73a590b5f2583daecf63984f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 12:29:05 +0300 Subject: [PATCH 058/134] Close channel when needed and optimize force() position after multiple writes not at each step --- .../netty/handler/codec/http/AbstractDiskHttpData.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/AbstractDiskHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/AbstractDiskHttpData.java index ebb4290b5d..04b3147447 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/AbstractDiskHttpData.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/AbstractDiskHttpData.java @@ -116,9 +116,9 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData { int written = 0; while (written < size) { written += localfileChannel.write(byteBuffer); - localfileChannel.force(false); } buffer.readerIndex(buffer.readerIndex() + written); + localfileChannel.force(false); localfileChannel.close(); completed = true; } @@ -143,7 +143,6 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData { } while (written < localsize) { written += fileChannel.write(byteBuffer); - fileChannel.force(false); } size += localsize; buffer.readerIndex(buffer.readerIndex() + written); @@ -156,6 +155,7 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData { FileOutputStream outputStream = new FileOutputStream(file); fileChannel = outputStream.getChannel(); } + fileChannel.force(false); fileChannel.close(); fileChannel = null; completed = true; @@ -195,9 +195,10 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData { while (read > 0) { byteBuffer.position(read).flip(); written += localfileChannel.write(byteBuffer); - localfileChannel.force(false); read = inputStream.read(bytes); } + localfileChannel.force(false); + localfileChannel.close(); size = written; if (definedSize > 0 && definedSize < size) { file.delete(); @@ -300,6 +301,8 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData { FileChannel in = inputStream.getChannel(); FileChannel out = outputStream.getChannel(); long destsize = in.transferTo(0, size, out); + in.close(); + out.close(); if (destsize == size) { file.delete(); file = dest; From 877383de3a518dd0dac56eba7337932dd55f59ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 12:30:08 +0300 Subject: [PATCH 059/134] Move force() after multiple writes, not at every steps --- .../io/netty/handler/codec/http/AbstractMemoryHttpData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/AbstractMemoryHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/AbstractMemoryHttpData.java index bdd24455f3..2e37976f32 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/AbstractMemoryHttpData.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/AbstractMemoryHttpData.java @@ -212,8 +212,8 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData { int written = 0; while (written < length) { written += fileChannel.write(byteBuffer); - fileChannel.force(false); } + fileChannel.force(false); fileChannel.close(); isRenamed = true; return written == length; From a410cb243b875890d96f64db5976a90e9e8295fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 12:32:24 +0300 Subject: [PATCH 060/134] Fix in addContent when switching from MemoryAttribute if it is done when last buffer added, in order to not close immediately the underlying file before adding the last buffer. --- .../main/java/io/netty/handler/codec/http/MixedAttribute.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/MixedAttribute.java b/codec-http/src/main/java/io/netty/handler/codec/http/MixedAttribute.java index 5aa301037c..2e3ab69491 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/MixedAttribute.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/MixedAttribute.java @@ -65,7 +65,7 @@ public class MixedAttribute implements Attribute { .getName()); if (((MemoryAttribute) attribute).getChannelBuffer() != null) { diskAttribute.addContent(((MemoryAttribute) attribute) - .getChannelBuffer(), last); + .getChannelBuffer(), false); } attribute = diskAttribute; } From d9085e9e37669624f1e716023cd91e6aeadd3c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 12:33:10 +0300 Subject: [PATCH 061/134] Fix in addContent when switching from MemoryAttribute if it is done when last buffer added, in order to not close immediately the underlying file before adding the last buffer. --- .../main/java/io/netty/handler/codec/http/MixedFileUpload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/MixedFileUpload.java b/codec-http/src/main/java/io/netty/handler/codec/http/MixedFileUpload.java index 6f0146cb37..b1a1350616 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/MixedFileUpload.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/MixedFileUpload.java @@ -58,7 +58,7 @@ public class MixedFileUpload implements FileUpload { definedSize); if (((MemoryFileUpload) fileUpload).getChannelBuffer() != null) { diskFileUpload.addContent(((MemoryFileUpload) fileUpload) - .getChannelBuffer(), last); + .getChannelBuffer(), false); } fileUpload = diskFileUpload; } From c83323f7484b094e8bee993154325d73dd19e11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 12:38:07 +0300 Subject: [PATCH 062/134] Add the SeekAheadOptimize class to enable faster seek of bytes values in HttpPostRequestDecoder --- .../handler/codec/http/HttpPostBodyUtil.java | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java index e74e1ecdc0..b1ceca7bf0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java @@ -116,18 +116,58 @@ final class HttpPostBodyUtil { private HttpPostBodyUtil() { } - //Some commons methods between HttpPostRequestDecoder and HttpMessageDecoder /** - * Skip control Characters - * @param buffer + * Exception when NO Backend Array is found + */ + static class SeekAheadNoBackArray extends Exception { + private static final long serialVersionUID = -630418804938699495L; + } + + /** + * This class intends to decrease the CPU in seeking ahead some bytes in + * HttpPostRequestDecoder */ - static void skipControlCharacters(ChannelBuffer buffer) { - for (;;) { - char c = (char) buffer.readUnsignedByte(); - if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { - buffer.readerIndex(buffer.readerIndex() - 1); - break; + static class SeekAheadOptimize { + byte[] bytes; + + int readerIndex; + + int pos; + + int limit; + + ChannelBuffer buffer; + + /** + * @param buffer + */ + SeekAheadOptimize(ChannelBuffer buffer) + throws SeekAheadNoBackArray { + if (! buffer.hasArray()) { + throw new SeekAheadNoBackArray(); } + this.buffer = buffer; + this.bytes = buffer.array(); + this.pos = this.readerIndex = buffer.readerIndex(); + this.limit = buffer.writerIndex(); + } + /** + * + * @param minus this value will be used as (currentPos - minus) to set + * the current readerIndex in the buffer. + */ + void setReadPosition(int minus) { + pos -= minus; + readerIndex = pos; + buffer.readerIndex(readerIndex); + } + + void clear() { + this.buffer = null; + this.bytes = null; + this.limit = 0; + this.pos = 0; + this.readerIndex = 0; } } From a52439647562f842ec2dd8a70427bd8af6e30e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 13:02:46 +0300 Subject: [PATCH 063/134] Optimize Buffer access while decoding by going through backend array when possible (divide by almost 2 the time spent in decoding) --- .../codec/http/HttpPostRequestDecoder.java | 454 +++++++++++++++++- 1 file changed, 448 insertions(+), 6 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java index b066479554..50adf9a206 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java @@ -26,6 +26,8 @@ import java.util.TreeMap; import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; +import org.jboss.netty.handler.codec.http2.HttpPostBodyUtil.SeekAheadNoBackArray; +import org.jboss.netty.handler.codec.http2.HttpPostBodyUtil.SeekAheadOptimize; import io.netty.handler.codec.http.HttpPostBodyUtil.TransferEncodingMechanism; /** @@ -427,7 +429,7 @@ public class HttpPostRequestDecoder { * @throws ErrorDataDecoderException if there is a problem with the charset decoding or * other errors */ - private void parseBodyAttributes() throws ErrorDataDecoderException { + private void parseBodyAttributesStandard() throws ErrorDataDecoderException { int firstpos = undecodedChunk.readerIndex(); int currentpos = firstpos; int equalpos = firstpos; @@ -538,6 +540,141 @@ public class HttpPostRequestDecoder { } } + /** + * This method fill the map and list with as much Attribute as possible from Body in + * not Multipart mode. + * + * @throws ErrorDataDecoderException if there is a problem with the charset decoding or + * other errors + */ + private void parseBodyAttributes() throws ErrorDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArray e1) { + parseBodyAttributesStandard(); + return; + } + int firstpos = undecodedChunk.readerIndex(); + int currentpos = firstpos; + int equalpos = firstpos; + int ampersandpos = firstpos; + if (currentStatus == MultiPartStatus.NOTSTARTED) { + currentStatus = MultiPartStatus.DISPOSITION; + } + boolean contRead = true; + try { + loop: + while (sao.pos < sao.limit) { + char read = (char) (sao.bytes[sao.pos ++] & 0xFF); + currentpos ++; + switch (currentStatus) { + case DISPOSITION:// search '=' + if (read == '=') { + currentStatus = MultiPartStatus.FIELD; + equalpos = currentpos - 1; + String key = decodeAttribute( + undecodedChunk.toString(firstpos, equalpos - firstpos, charset), + charset); + currentAttribute = factory.createAttribute(request, key); + firstpos = currentpos; + } else if (read == '&') { // special empty FIELD + currentStatus = MultiPartStatus.DISPOSITION; + ampersandpos = currentpos - 1; + String key = decodeAttribute(undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset); + currentAttribute = factory.createAttribute(request, key); + currentAttribute.setValue(""); // empty + addHttpData(currentAttribute); + currentAttribute = null; + firstpos = currentpos; + contRead = true; + } + break; + case FIELD:// search '&' or end of line + if (read == '&') { + currentStatus = MultiPartStatus.DISPOSITION; + ampersandpos = currentpos - 1; + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = true; + } else if (read == HttpCodecUtil.CR) { + if (sao.pos < sao.limit) { + read = (char) (sao.bytes[sao.pos ++] & 0xFF); + currentpos++; + if (read == HttpCodecUtil.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos - 2; + sao.setReadPosition(0); + setFinalBuffer( + undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = false; + break loop; + } else { + // Error + sao.setReadPosition(0); + contRead = false; + throw new ErrorDataDecoderException("Bad end of line"); + } + } else { + if (sao.limit > 0) { + currentpos --; + } + } + } else if (read == HttpCodecUtil.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos - 1; + sao.setReadPosition(0); + setFinalBuffer( + undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = false; + break loop; + } + break; + default: + // just stop + sao.setReadPosition(0); + contRead = false; + break loop; + } + } + if (isLastChunk && currentAttribute != null) { + // special case + ampersandpos = currentpos; + if (ampersandpos > firstpos) { + setFinalBuffer( + undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + } else if (! currentAttribute.isCompleted()) { + setFinalBuffer(ChannelBuffers.EMPTY_BUFFER); + } + firstpos = currentpos; + currentStatus = MultiPartStatus.EPILOGUE; + return; + } + if (contRead && currentAttribute != null) { + // reset index except if to continue in case of FIELD status + if (currentStatus == MultiPartStatus.FIELD) { + currentAttribute.addContent( + undecodedChunk.slice(firstpos, currentpos - firstpos), + false); + firstpos = currentpos; + } + undecodedChunk.readerIndex(firstpos); + } else { + // end of line so keep index + } + } catch (ErrorDataDecoderException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw e; + } catch (IOException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw new ErrorDataDecoderException(e); + } + } + private void setFinalBuffer(ChannelBuffer buffer) throws ErrorDataDecoderException, IOException { currentAttribute.addContent(buffer, true); String value = decodeAttribute( @@ -700,6 +837,38 @@ public class HttpPostRequestDecoder { } } + + /** + * Skip control Characters + */ + void skipControlCharacters() { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArray e) { + skipControlCharactersStandard(undecodedChunk); + return; + } + + while (sao.pos < sao.limit) { + char c = (char) sao.bytes[sao.pos ++]; + if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { + sao.setReadPosition(1); + return; + } + } + sao.setReadPosition(0); + } + static void skipControlCharactersStandard(ChannelBuffer buffer) { + for (;;) { + char c = (char) buffer.readUnsignedByte(); + if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { + buffer.readerIndex(buffer.readerIndex() - 1); + break; + } + } + } + /** * Find the next Multipart Delimiter * @param delimiter delimiter to find @@ -714,7 +883,7 @@ public class HttpPostRequestDecoder { throws ErrorDataDecoderException { // --AaB03x or --AaB03x-- int readerIndex = undecodedChunk.readerIndex(); - HttpPostBodyUtil.skipControlCharacters(undecodedChunk); + skipControlCharacters(undecodedChunk); skipOneLine(); String newline; try { @@ -755,7 +924,7 @@ public class HttpPostRequestDecoder { } // read many lines until empty line with newline found! Store all data while (!skipOneLine()) { - HttpPostBodyUtil.skipControlCharacters(undecodedChunk); + skipControlCharacters(undecodedChunk); String newline; try { newline = readLine(); @@ -1038,7 +1207,7 @@ public class HttpPostRequestDecoder { * @throws NotEnoughDataDecoderException Need more chunks and * reset the readerInder to the previous value */ - private String readLine() throws NotEnoughDataDecoderException { + private String readLineStandard() throws NotEnoughDataDecoderException { int readerIndex = undecodedChunk.readerIndex(); try { StringBuilder sb = new StringBuilder(64); @@ -1063,6 +1232,49 @@ public class HttpPostRequestDecoder { throw new NotEnoughDataDecoderException(); } + /** + * Read one line up to the CRLF or LF + * @return the String from one line + * @throws NotEnoughDataDecoderException Need more chunks and + * reset the readerInder to the previous value + */ + private String readLine() throws NotEnoughDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArray e1) { + return readLineStandard(); + } + int readerIndex = undecodedChunk.readerIndex(); + try { + StringBuilder sb = new StringBuilder(64); + while (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos ++]; + if (nextByte == HttpCodecUtil.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos ++]; + if (nextByte == HttpCodecUtil.LF) { + sao.setReadPosition(0); + return sb.toString(); + } + } else { + sb.append((char) nextByte); + } + } else if (nextByte == HttpCodecUtil.LF) { + sao.setReadPosition(0); + return sb.toString(); + } else { + sb.append((char) nextByte); + } + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + /** * Read a FileUpload data as Byte (Binary) and add the bytes directly to the * FileUpload. If the delimiter is found, the FileUpload is completed. @@ -1071,7 +1283,7 @@ public class HttpPostRequestDecoder { * do not reset the readerInder since some values will be already added to the FileOutput * @throws ErrorDataDecoderException write IO error occurs with the FileUpload */ - private void readFileUploadByteMultipart(String delimiter) + private void readFileUploadByteMultipartStandard(String delimiter) throws NotEnoughDataDecoderException, ErrorDataDecoderException { int readerIndex = undecodedChunk.readerIndex(); // found the decoder limit @@ -1157,12 +1369,128 @@ public class HttpPostRequestDecoder { } } + /** + * Read a FileUpload data as Byte (Binary) and add the bytes directly to the + * FileUpload. If the delimiter is found, the FileUpload is completed. + * @param delimiter + * @throws NotEnoughDataDecoderException Need more chunks but + * do not reset the readerInder since some values will be already added to the FileOutput + * @throws ErrorDataDecoderException write IO error occurs with the FileUpload + */ + private void readFileUploadByteMultipart(String delimiter) + throws NotEnoughDataDecoderException, ErrorDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArray e1) { + readFileUploadByteMultipartStandard(delimiter); + return; + } + int readerIndex = undecodedChunk.readerIndex(); + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + + while (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos ++]; + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index ++; + if (delimiter.length() == index) { + found = true; + sao.setReadPosition(0); + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos ++]; + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 2; + } + } else { + // save last valid position + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + // save last valid position + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } + } else { + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos ++]; + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 2; + } + } else { + // save last valid position + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + // save last valid position + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } + } + ChannelBuffer buffer = undecodedChunk.slice(readerIndex, lastPosition - readerIndex); + if (found) { + // found so lastPosition is correct and final + try { + currentFileUpload.addContent(buffer, true); + // just before the CRLF and delimiter + undecodedChunk.readerIndex(lastPosition); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } else { + // possibly the delimiter is partially found but still the last position is OK + try { + currentFileUpload.addContent(buffer, false); + // last valid char (not CR, not LF, not beginning of delimiter) + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + } + /** * Load the field value from a Multipart request * @throws NotEnoughDataDecoderException Need more chunks * @throws ErrorDataDecoderException */ - private void loadFieldMultipart(String delimiter) + private void loadFieldMultipartStandard(String delimiter) throws NotEnoughDataDecoderException, ErrorDataDecoderException { int readerIndex = undecodedChunk.readerIndex(); try { @@ -1252,6 +1580,120 @@ public class HttpPostRequestDecoder { } } + /** + * Load the field value from a Multipart request + * @throws NotEnoughDataDecoderException Need more chunks + * @throws ErrorDataDecoderException + */ + private void loadFieldMultipart(String delimiter) + throws NotEnoughDataDecoderException, ErrorDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArray e1) { + loadFieldMultipartStandard(delimiter); + return; + } + int readerIndex = undecodedChunk.readerIndex(); + try { + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + + while (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos ++]; + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index ++; + if (delimiter.length() == index) { + found = true; + sao.setReadPosition(0); + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos ++]; + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 2; + } + } else { + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } + } else { + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos ++]; + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 2; + } + } else { + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + sao.setReadPosition(0); + lastPosition = undecodedChunk.readerIndex(); + } + } + } + if (found) { + // found so lastPosition is correct + // but position is just after the delimiter (either close delimiter or simple one) + // so go back of delimiter size + try { + currentAttribute.addContent( + undecodedChunk.slice(readerIndex, lastPosition - readerIndex), true); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + } else { + try { + currentAttribute.addContent( + undecodedChunk.slice(readerIndex, lastPosition - readerIndex), false); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + } + /** * Clean the String from any unallowed character * @return the cleaned String From 675bccc9eabbf10f665ae9f078ba6e15f711af87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Mon, 30 Apr 2012 13:06:53 +0300 Subject: [PATCH 064/134] Update codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java --- .../netty/handler/codec/http/HttpPostRequestDecoder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java index 50adf9a206..ce57925395 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java @@ -26,8 +26,8 @@ import java.util.TreeMap; import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; -import org.jboss.netty.handler.codec.http2.HttpPostBodyUtil.SeekAheadNoBackArray; -import org.jboss.netty.handler.codec.http2.HttpPostBodyUtil.SeekAheadOptimize; +import io.netty.handler.codec.http.HttpPostBodyUtil.SeekAheadNoBackArray; +import io.netty.handler.codec.http.HttpPostBodyUtil.SeekAheadOptimize; import io.netty.handler.codec.http.HttpPostBodyUtil.TransferEncodingMechanism; /** @@ -883,7 +883,7 @@ public class HttpPostRequestDecoder { throws ErrorDataDecoderException { // --AaB03x or --AaB03x-- int readerIndex = undecodedChunk.readerIndex(); - skipControlCharacters(undecodedChunk); + skipControlCharacters(); skipOneLine(); String newline; try { @@ -924,7 +924,7 @@ public class HttpPostRequestDecoder { } // read many lines until empty line with newline found! Store all data while (!skipOneLine()) { - skipControlCharacters(undecodedChunk); + skipControlCharacters(); String newline; try { newline = readLine(); From 2a70df1c1c62f66336c4a497a2fb5c5829782e3f Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 30 Apr 2012 20:26:44 +0200 Subject: [PATCH 065/134] Optimize AbstractNioWorker.cleanUpWriteBuffer(..). See #293 --- .../channel/socket/nio/AbstractNioWorker.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java index 03423f4d0f..d57edde5ce 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -919,7 +919,11 @@ abstract class AbstractNioWorker implements Worker { } Queue writeBuffer = channel.writeBufferQueue; - if (!writeBuffer.isEmpty()) { + for (;;) { + evt = writeBuffer.poll(); + if (evt == null) { + break; + } // Create the exception only once to avoid the excessive overhead // caused by fillStackTrace. if (cause == null) { @@ -928,16 +932,10 @@ abstract class AbstractNioWorker implements Worker { } else { cause = new ClosedChannelException(); } - } - - for (;;) { - evt = writeBuffer.poll(); - if (evt == null) { - break; - } evt.getFuture().setFailure(cause); fireExceptionCaught = true; } + } } From 1bf17c7c87769c803e0af21d37bc6b93f6d96ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Tue, 1 May 2012 00:14:42 +0300 Subject: [PATCH 066/134] Add Exception to name of the Exception ;-) --- .../java/io/netty/handler/codec/http/HttpPostBodyUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java index b1ceca7bf0..02fb319e34 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostBodyUtil.java @@ -119,7 +119,7 @@ final class HttpPostBodyUtil { /** * Exception when NO Backend Array is found */ - static class SeekAheadNoBackArray extends Exception { + static class SeekAheadNoBackArrayException extends Exception { private static final long serialVersionUID = -630418804938699495L; } @@ -142,9 +142,9 @@ final class HttpPostBodyUtil { * @param buffer */ SeekAheadOptimize(ChannelBuffer buffer) - throws SeekAheadNoBackArray { + throws SeekAheadNoBackArrayException { if (! buffer.hasArray()) { - throw new SeekAheadNoBackArray(); + throw new SeekAheadNoBackArrayException(); } this.buffer = buffer; this.bytes = buffer.array(); From 502e469c45fc7dff596c3f0441db712a771f5a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Tue, 1 May 2012 00:18:42 +0300 Subject: [PATCH 067/134] Add Exception to the exception class name --- .../handler/codec/http/HttpPostRequestDecoder.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java index ce57925395..1347c1be89 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java @@ -26,7 +26,7 @@ import java.util.TreeMap; import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; -import io.netty.handler.codec.http.HttpPostBodyUtil.SeekAheadNoBackArray; +import io.netty.handler.codec.http.HttpPostBodyUtil.SeekAheadNoBackArrayException; import io.netty.handler.codec.http.HttpPostBodyUtil.SeekAheadOptimize; import io.netty.handler.codec.http.HttpPostBodyUtil.TransferEncodingMechanism; @@ -551,7 +551,7 @@ public class HttpPostRequestDecoder { SeekAheadOptimize sao = null; try { sao = new SeekAheadOptimize(undecodedChunk); - } catch (SeekAheadNoBackArray e1) { + } catch (SeekAheadNoBackArrayException e1) { parseBodyAttributesStandard(); return; } @@ -845,7 +845,7 @@ public class HttpPostRequestDecoder { SeekAheadOptimize sao = null; try { sao = new SeekAheadOptimize(undecodedChunk); - } catch (SeekAheadNoBackArray e) { + } catch (SeekAheadNoBackArrayException e) { skipControlCharactersStandard(undecodedChunk); return; } @@ -1242,7 +1242,7 @@ public class HttpPostRequestDecoder { SeekAheadOptimize sao = null; try { sao = new SeekAheadOptimize(undecodedChunk); - } catch (SeekAheadNoBackArray e1) { + } catch (SeekAheadNoBackArrayException e1) { return readLineStandard(); } int readerIndex = undecodedChunk.readerIndex(); @@ -1382,7 +1382,7 @@ public class HttpPostRequestDecoder { SeekAheadOptimize sao = null; try { sao = new SeekAheadOptimize(undecodedChunk); - } catch (SeekAheadNoBackArray e1) { + } catch (SeekAheadNoBackArrayException e1) { readFileUploadByteMultipartStandard(delimiter); return; } @@ -1590,7 +1590,7 @@ public class HttpPostRequestDecoder { SeekAheadOptimize sao = null; try { sao = new SeekAheadOptimize(undecodedChunk); - } catch (SeekAheadNoBackArray e1) { + } catch (SeekAheadNoBackArrayException e1) { loadFieldMultipartStandard(delimiter); return; } From b2e77beb4603d9df613f23ff02f74944edc94f1f Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 1 May 2012 12:00:36 +0200 Subject: [PATCH 068/134] We need to set the exception on each MessageEvent. See #293 --- .../java/io/netty/channel/socket/nio/AbstractNioWorker.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java index d57edde5ce..815c32f0d9 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -892,7 +892,7 @@ abstract class AbstractNioWorker implements Worker { } } } - + protected void cleanUpWriteBuffer(AbstractNioChannel channel) { Exception cause = null; boolean fireExceptionCaught = false; @@ -932,9 +932,10 @@ abstract class AbstractNioWorker implements Worker { } else { cause = new ClosedChannelException(); } - evt.getFuture().setFailure(cause); fireExceptionCaught = true; } + evt.getFuture().setFailure(cause); + } } From 202df0618c5ba7c58a0e4ef8e4f9ac76c8a32a46 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 2 May 2012 07:39:02 +0200 Subject: [PATCH 069/134] Remove workaround for ipv6 link-localaddresses as it not work on most os / jdk versions. See #267 and #295 --- .../netty/channel/socket/nio/NioClientSocketPipelineSink.java | 3 --- .../netty/channel/socket/oio/OioClientSocketPipelineSink.java | 4 ---- 2 files changed, 7 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java index 6b4008a520..495dd371fb 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java @@ -27,9 +27,7 @@ import io.netty.channel.ChannelStateEvent; import io.netty.channel.MessageEvent; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; -import io.netty.util.internal.SocketUtil; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; @@ -101,7 +99,6 @@ class NioClientSocketPipelineSink extends AbstractNioChannelSink { final NioClientSocketChannel channel, final ChannelFuture cf, SocketAddress remoteAddress) { try { - remoteAddress = SocketUtil.stripZoneId((InetSocketAddress) remoteAddress); channel.getJdkChannel().connect(remoteAddress); channel.getCloseFuture().addListener(new ChannelFutureListener() { @Override diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java index ea5148b76b..e607d3282e 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java @@ -18,7 +18,6 @@ package io.netty.channel.socket.oio; import static io.netty.channel.Channels.*; import java.io.PushbackInputStream; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.Executor; @@ -30,7 +29,6 @@ import io.netty.channel.ChannelState; import io.netty.channel.ChannelStateEvent; import io.netty.channel.MessageEvent; import io.netty.util.internal.DeadLockProofWorker; -import io.netty.util.internal.SocketUtil; class OioClientSocketPipelineSink extends AbstractOioChannelSink { @@ -104,8 +102,6 @@ class OioClientSocketPipelineSink extends AbstractOioChannelSink { future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); try { - remoteAddress = SocketUtil.stripZoneId((InetSocketAddress) remoteAddress); - channel.socket.connect( remoteAddress, channel.getConfig().getConnectTimeoutMillis()); connected = true; From d475a8cc64613c54c9e3318710a94f38334ac34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Wed, 2 May 2012 11:40:28 +0300 Subject: [PATCH 070/134] Very small fix (readUnsigned while in optimized version it was signed but should be unsigned) --- .../io/netty/handler/codec/http/HttpPostRequestDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java index 1347c1be89..88d96ad42e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java @@ -851,7 +851,7 @@ public class HttpPostRequestDecoder { } while (sao.pos < sao.limit) { - char c = (char) sao.bytes[sao.pos ++]; + char c = (char) ()sao.bytes[sao.pos ++] & 0xFF); if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { sao.setReadPosition(1); return; From 62f5623d2af2a5bbbfcfc51cbde19929c35de81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Wed, 2 May 2012 12:33:40 +0300 Subject: [PATCH 071/134] typo fix ! Sorry --- .../io/netty/handler/codec/http/HttpPostRequestDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java index 88d96ad42e..7f0c9689d7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java @@ -851,7 +851,7 @@ public class HttpPostRequestDecoder { } while (sao.pos < sao.limit) { - char c = (char) ()sao.bytes[sao.pos ++] & 0xFF); + char c = (char) (sao.bytes[sao.pos ++] & 0xFF); if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { sao.setReadPosition(1); return; From cc0d7d8be50bd59c369529305228c328b561993f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Wed, 2 May 2012 12:35:36 +0300 Subject: [PATCH 072/134] typo fix! sorry --- .../io/netty/handler/codec/http/HttpPostRequestDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java index 88d96ad42e..7f0c9689d7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpPostRequestDecoder.java @@ -851,7 +851,7 @@ public class HttpPostRequestDecoder { } while (sao.pos < sao.limit) { - char c = (char) ()sao.bytes[sao.pos ++] & 0xFF); + char c = (char) (sao.bytes[sao.pos ++] & 0xFF); if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { sao.setReadPosition(1); return; From 4e528c10fae3abaa932e751c831aa48a23645744 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 2 May 2012 13:15:28 +0300 Subject: [PATCH 073/134] Fix small race which can lead to resumeTransfer() to not kick in. See #300 --- .../main/java/io/netty/handler/stream/ChunkedWriteHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index 2e70697e0e..aa4b9484f4 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -77,7 +77,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns private final Queue queue = QueueFactory.createQueue(MessageEvent.class); - private ChannelHandlerContext ctx; + private volatile ChannelHandlerContext ctx; private MessageEvent currentEvent; /** From fb52b8a3b2e3940a0bcf6e9167b157d59a811691 Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 3 May 2012 09:24:36 +0200 Subject: [PATCH 074/134] Make sure ChunkedInput.close() is not called before the write is complete. See #303 --- .../netty/handler/stream/ChunkedWriteHandler.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index aa4b9484f4..ec3804484a 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -204,7 +204,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns final MessageEvent currentEvent = this.currentEvent; Object m = currentEvent.getMessage(); if (m instanceof ChunkedInput) { - ChunkedInput chunks = (ChunkedInput) m; + final ChunkedInput chunks = (ChunkedInput) m; Object chunk; boolean endOfInput; boolean suspend; @@ -241,8 +241,18 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns ChannelFuture writeFuture; if (endOfInput) { this.currentEvent = null; - closeInput(chunks); writeFuture = currentEvent.getFuture(); + + // Register a listener which will close the input once the write is complete. This is needed because the Chunk may have + // some resource bound that can not be closed before its not written + // + // See https://github.com/netty/netty/issues/303 + writeFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + closeInput(chunks); + } + }); } else { writeFuture = future(channel); writeFuture.addListener(new ChannelFutureListener() { From f023120a62ce756280c6a9d1e6525ca3070b583d Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 3 May 2012 17:15:03 +0200 Subject: [PATCH 075/134] Allow to register ChannelFutureListener's that get notified once the inbound of the SSLEngine is closed. See #137 --- .../java/io/netty/handler/ssl/SslHandler.java | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index d14f8205c3..124e27b8a0 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -43,6 +43,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelStateEvent; import io.netty.channel.Channels; +import io.netty.channel.DefaultChannelFuture; import io.netty.channel.DownstreamMessageEvent; import io.netty.channel.ExceptionEvent; import io.netty.channel.LifeCycleAwareChannelHandler; @@ -207,6 +208,8 @@ public class SslHandler extends FrameDecoder }; + private final SSLEngineInboundCloseFuture sslEngineCloseFuture = new SSLEngineInboundCloseFuture(); + /** * Creates a new instance. * @@ -426,6 +429,18 @@ public class SslHandler extends FrameDecoder return issueHandshake; } + /** + * Return the {@link ChannelFuture} that will get notified if the inbound of the {@link SSLEngine} will get closed. + * + * This method will return the same {@link ChannelFuture} all the time. + * + * For more informations see the apidocs of {@link SSLEngine} + * + */ + public ChannelFuture getSSLEngineInboundCloseFuture() { + return sslEngineCloseFuture; + } + @Override public void handleDownstream( final ChannelHandlerContext context, final ChannelEvent evt) throws Exception { @@ -924,7 +939,12 @@ public class SslHandler extends FrameDecoder synchronized (handshakeLock) { result = engine.unwrap(inNetBuf, outAppBuf); } - + + // notify about the CLOSED state of the SSLEngine. See #137 + if (result.getStatus() == Status.CLOSED) { + sslEngineCloseFuture.setClosed(); + } + final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); handleRenegotiation(handshakeStatus); switch (handshakeStatus) { @@ -1209,4 +1229,34 @@ public class SslHandler extends FrameDecoder } super.channelConnected(ctx, e); } + + private final class SSLEngineInboundCloseFuture extends DefaultChannelFuture { + public SSLEngineInboundCloseFuture() { + super(null, true); + } + + void setClosed() { + super.setSuccess(); + } + + @Override + public Channel getChannel() { + if (ctx == null) { + // Maybe we should better throw an IllegalStateException() ? + return null; + } else { + return ctx.getChannel(); + } + } + + @Override + public boolean setSuccess() { + return false; + } + + @Override + public boolean setFailure(Throwable cause) { + return false; + } + } } From 2174ce36280e2369e7efca3eae49ef623170084f Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 10:24:28 +0200 Subject: [PATCH 076/134] Fail all pending writes on channelClosed(..). See #305 --- .../java/io/netty/handler/ssl/SslHandler.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 124e27b8a0..824013efb4 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -1230,6 +1230,48 @@ public class SslHandler extends FrameDecoder super.channelConnected(ctx, e); } + /** + * Loop over all the pending writes and fail them. + * + * See #305 for more details. + */ + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + Throwable cause = null; + synchronized (pendingUnencryptedWrites) { + for (;;) { + PendingWrite pw = pendingUnencryptedWrites.poll(); + if (pw == null) { + break; + } + if (cause == null) { + cause = new ClosedChannelException(); + } + pw.future.setFailure(cause); + + } + + + for (;;) { + MessageEvent ev = pendingEncryptedWrites.poll(); + if (ev == null) { + break; + } + if (cause == null) { + cause = new ClosedChannelException(); + } + ev.getFuture().setFailure(cause); + + } + } + + if (cause != null) { + fireExceptionCaught(ctx, cause); + } + + super.channelClosed(ctx, e); + } + private final class SSLEngineInboundCloseFuture extends DefaultChannelFuture { public SSLEngineInboundCloseFuture() { super(null, true); From bc6948c3979503404adec190f46ae2bd55006379 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 10:26:25 +0200 Subject: [PATCH 077/134] Fail all queued writes if the ChunkedWriteHandler is removed from the ChannelPipeline. See #304 --- .../handler/stream/ChunkedWriteHandler.java | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index ec3804484a..c4f6c1417a 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -17,6 +17,7 @@ package io.netty.handler.stream; import static io.netty.channel.Channels.*; +import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.util.Queue; @@ -32,6 +33,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelStateEvent; import io.netty.channel.ChannelUpstreamHandler; import io.netty.channel.Channels; +import io.netty.channel.LifeCycleAwareChannelHandler; import io.netty.channel.MessageEvent; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; @@ -70,7 +72,7 @@ import io.netty.util.internal.QueueFactory; * @apiviz.landmark * @apiviz.has io.netty.handler.stream.ChunkedInput oneway - - reads from */ -public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler { +public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler, LifeCycleAwareChannelHandler { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChunkedWriteHandler.class); @@ -293,4 +295,63 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } } } + + + public void beforeAdd(ChannelHandlerContext ctx) throws Exception { + // nothing to do + + } + + public void afterAdd(ChannelHandlerContext ctx) throws Exception { + // nothing to do + + } + + public void beforeRemove(ChannelHandlerContext ctx) throws Exception { + // try to flush again a last time. + // + // See #304 + flush(ctx, false); + } + + // This method should not need any synchronization as the ChunkedWriteHandler will not receive any new events + public void afterRemove(ChannelHandlerContext ctx) throws Exception { + // Fail all MessageEvent's that are left. This is needed because otherwise we would never notify the + // ChannelFuture and the registered FutureListener. See #304 + // + Throwable cause = null; + boolean fireExceptionCaught = false; + + for (;;) { + MessageEvent currentEvent = this.currentEvent; + + if (this.currentEvent == null) { + currentEvent = queue.poll(); + } else { + this.currentEvent = null; + } + + if (currentEvent == null) { + break; + } + + Object m = currentEvent.getMessage(); + if (m instanceof ChunkedInput) { + closeInput((ChunkedInput) m); + } + + // Create exception + if (cause == null) { + cause = new IOException("Unable to flush event, discarding"); + } + currentEvent.getFuture().setFailure(cause); + fireExceptionCaught = true; + + currentEvent = null; + } + + if (fireExceptionCaught) { + Channels.fireExceptionCaughtLater(ctx.getChannel(), cause); + } + } } From d3c137923f246130d2f34a474c8117c7734245c6 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 10:27:58 +0200 Subject: [PATCH 078/134] Notify ChannelFuture's of queued writes if the SslHandler gets remove d from the ChannelPipeline. See #306 --- .../java/io/netty/handler/ssl/SslHandler.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 824013efb4..761ce1bd85 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -1211,9 +1211,41 @@ public class SslHandler extends FrameDecoder // Unused } + /** + * Fail all pending writes which we were not able to flush out + */ @Override public void afterRemove(ChannelHandlerContext ctx) throws Exception { - // Unused + + // there is no need for synchronization here as we do not receive downstream events anymore + Throwable cause = null; + for (;;) { + PendingWrite pw = pendingUnencryptedWrites.poll(); + if (pw == null) { + break; + } + if (cause == null) { + cause = new IOException("Unable to write data"); + } + pw.future.setFailure(cause); + + } + + for (;;) { + MessageEvent ev = pendingEncryptedWrites.poll(); + if (ev == null) { + break; + } + if (cause == null) { + cause = new IOException("Unable to write data"); + } + ev.getFuture().setFailure(cause); + + } + + if (cause != null) { + fireExceptionCaught(ctx, cause); + } } From 7016b83629ac8ad734b896e4ee34c44cd4dc3788 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 10:28:57 +0200 Subject: [PATCH 079/134] Add @Override annotations --- .../java/io/netty/handler/stream/ChunkedWriteHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index c4f6c1417a..04a6e8b65d 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -297,16 +297,19 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } + @Override public void beforeAdd(ChannelHandlerContext ctx) throws Exception { // nothing to do } + @Override public void afterAdd(ChannelHandlerContext ctx) throws Exception { // nothing to do } + @Override public void beforeRemove(ChannelHandlerContext ctx) throws Exception { // try to flush again a last time. // @@ -315,6 +318,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } // This method should not need any synchronization as the ChunkedWriteHandler will not receive any new events + @Override public void afterRemove(ChannelHandlerContext ctx) throws Exception { // Fail all MessageEvent's that are left. This is needed because otherwise we would never notify the // ChannelFuture and the registered FutureListener. See #304 From ec28cc8ba15d2890e11c93651b68618fc5fa7081 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 10:31:06 +0200 Subject: [PATCH 080/134] Refactor ChunkedWriteHandler to remove synchronization which can have bad side effects like deadlocks. See #297 and #301 --- .../handler/stream/ChunkedWriteHandler.java | 206 ++++++++++-------- 1 file changed, 111 insertions(+), 95 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index 04a6e8b65d..017b996c5f 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -20,6 +20,7 @@ import static io.netty.channel.Channels.*; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; @@ -80,6 +81,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns private final Queue queue = QueueFactory.createQueue(MessageEvent.class); private volatile ChannelHandlerContext ctx; + private final AtomicBoolean flush = new AtomicBoolean(false); private MessageEvent currentEvent; /** @@ -183,106 +185,120 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } } - private synchronized void flush(ChannelHandlerContext ctx, boolean fireNow) throws Exception { + private void flush(ChannelHandlerContext ctx, boolean fireNow) throws Exception { + boolean acquired = false; final Channel channel = ctx.getChannel(); - if (!channel.isConnected()) { - discard(ctx, fireNow); - } - while (channel.isWritable()) { - if (currentEvent == null) { - currentEvent = queue.poll(); - } - - if (currentEvent == null) { - break; - } - - if (currentEvent.getFuture().isDone()) { - // Skip the current request because the previous partial write - // attempt for the current request has been failed. - currentEvent = null; - } else { - final MessageEvent currentEvent = this.currentEvent; - Object m = currentEvent.getMessage(); - if (m instanceof ChunkedInput) { - final ChunkedInput chunks = (ChunkedInput) m; - Object chunk; - boolean endOfInput; - boolean suspend; - try { - chunk = chunks.nextChunk(); - endOfInput = chunks.isEndOfInput(); - if (chunk == null) { - chunk = ChannelBuffers.EMPTY_BUFFER; - // No need to suspend when reached at the end. - suspend = !endOfInput; - } else { - suspend = false; - } - } catch (Throwable t) { - this.currentEvent = null; - - currentEvent.getFuture().setFailure(t); - if (fireNow) { - fireExceptionCaught(ctx, t); - } else { - fireExceptionCaughtLater(ctx, t); - } - - closeInput(chunks); - break; - } - - if (suspend) { - // ChunkedInput.nextChunk() returned null and it has - // not reached at the end of input. Let's wait until - // more chunks arrive. Nothing to write or notify. - break; - } else { - ChannelFuture writeFuture; - if (endOfInput) { - this.currentEvent = null; - writeFuture = currentEvent.getFuture(); - - // Register a listener which will close the input once the write is complete. This is needed because the Chunk may have - // some resource bound that can not be closed before its not written - // - // See https://github.com/netty/netty/issues/303 - writeFuture.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - closeInput(chunks); - } - }); - } else { - writeFuture = future(channel); - writeFuture.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) - throws Exception { - if (!future.isSuccess()) { - currentEvent.getFuture().setFailure(future.getCause()); - closeInput((ChunkedInput) currentEvent.getMessage()); - } - } - }); - } - - Channels.write( - ctx, writeFuture, chunk, - currentEvent.getRemoteAddress()); - } - } else { - this.currentEvent = null; - ctx.sendDownstream(currentEvent); + // use CAS to see if the have flush already running, if so we don't need to take futher actions + if (acquired = flush.compareAndSet(false, true)) { + try { + + if (!channel.isConnected()) { + discard(ctx, fireNow); } - } - if (!channel.isConnected()) { - discard(ctx, fireNow); - break; + while (channel.isWritable()) { + if (currentEvent == null) { + currentEvent = queue.poll(); + } + + if (currentEvent == null) { + break; + } + + if (currentEvent.getFuture().isDone()) { + // Skip the current request because the previous partial write + // attempt for the current request has been failed. + currentEvent = null; + } else { + final MessageEvent currentEvent = this.currentEvent; + Object m = currentEvent.getMessage(); + if (m instanceof ChunkedInput) { + final ChunkedInput chunks = (ChunkedInput) m; + Object chunk; + boolean endOfInput; + boolean suspend; + try { + chunk = chunks.nextChunk(); + endOfInput = chunks.isEndOfInput(); + if (chunk == null) { + chunk = ChannelBuffers.EMPTY_BUFFER; + // No need to suspend when reached at the end. + suspend = !endOfInput; + } else { + suspend = false; + } + } catch (Throwable t) { + this.currentEvent = null; + + currentEvent.getFuture().setFailure(t); + if (fireNow) { + fireExceptionCaught(ctx, t); + } else { + fireExceptionCaughtLater(ctx, t); + } + + closeInput(chunks); + break; + } + + if (suspend) { + // ChunkedInput.nextChunk() returned null and it has + // not reached at the end of input. Let's wait until + // more chunks arrive. Nothing to write or notify. + break; + } else { + ChannelFuture writeFuture; + if (endOfInput) { + this.currentEvent = null; + writeFuture = currentEvent.getFuture(); + + // Register a listener which will close the input once the write is complete. This is needed because the Chunk may have + // some resource bound that can not be closed before its not written + // + // See https://github.com/netty/netty/issues/303 + writeFuture.addListener(new ChannelFutureListener() { + + public void operationComplete(ChannelFuture future) throws Exception { + closeInput(chunks); + } + }); + } else { + writeFuture = future(channel); + writeFuture.addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + currentEvent.getFuture().setFailure(future.getCause()); + closeInput((ChunkedInput) currentEvent.getMessage()); + } + } + }); + } + + Channels.write( + ctx, writeFuture, chunk, + currentEvent.getRemoteAddress()); + } + } else { + this.currentEvent = null; + ctx.sendDownstream(currentEvent); + } + } + + if (!channel.isConnected()) { + discard(ctx, fireNow); + break; + } + } + } finally { + // mark the flush as done + flush.set(false); } + + } + + if (acquired && !channel.isConnected() || (channel.isWritable() && !queue.isEmpty())) { + flush(ctx, fireNow); } } From 781e628dd8eae7ec97d030c72d645ea7ebcd227d Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 13:23:32 +0200 Subject: [PATCH 081/134] Let ChannelLocal implement Iterable. See #307 --- .../src/main/java/io/netty/channel/ChannelLocal.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/transport/src/main/java/io/netty/channel/ChannelLocal.java b/transport/src/main/java/io/netty/channel/ChannelLocal.java index 02f0bef3bc..91c5747d1e 100644 --- a/transport/src/main/java/io/netty/channel/ChannelLocal.java +++ b/transport/src/main/java/io/netty/channel/ChannelLocal.java @@ -15,6 +15,9 @@ */ package io.netty.channel; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import io.netty.util.internal.ConcurrentIdentityWeakKeyHashMap; @@ -33,7 +36,7 @@ import io.netty.util.internal.ConcurrentIdentityWeakKeyHashMap; * @apiviz.stereotype utility */ @Deprecated -public class ChannelLocal { +public class ChannelLocal implements Iterable> { private final ConcurrentMap map = new ConcurrentIdentityWeakKeyHashMap(); @@ -159,4 +162,11 @@ public class ChannelLocal { return removed; } } + + /** + * Returns a read-only {@link Iterator} that holds all {@link Entry}'s of this ChannelLocal + */ + public Iterator> iterator() { + return Collections.unmodifiableSet(map.entrySet()).iterator(); + } } From d509425b9013af0aefcd2392547d445406b600d9 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 13:49:22 +0200 Subject: [PATCH 082/134] Make sure we fire the event from the io-thread. See #306 --- handler/src/main/java/io/netty/handler/ssl/SslHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 761ce1bd85..557764296d 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -1244,7 +1244,7 @@ public class SslHandler extends FrameDecoder } if (cause != null) { - fireExceptionCaught(ctx, cause); + fireExceptionCaughtLater(ctx, cause); } } From 21a61ce632047e30daf3f5efcc6900b42cd5651b Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 13:56:34 +0200 Subject: [PATCH 083/134] Make sure the ChannelFuture's of the MessageEvent's are notified on channelClosed(..) event and on removal of the handler from the ChannelPipeline. See #308 --- .../handler/queue/BufferedWriteHandler.java | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/queue/BufferedWriteHandler.java b/handler/src/main/java/io/netty/handler/queue/BufferedWriteHandler.java index 97ceabc0f0..9c8ab56acd 100644 --- a/handler/src/main/java/io/netty/handler/queue/BufferedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/queue/BufferedWriteHandler.java @@ -15,6 +15,8 @@ */ package io.netty.handler.queue; +import java.io.IOException; +import java.nio.channels.ClosedChannelException; import java.util.ArrayList; import java.util.List; import java.util.Queue; @@ -29,6 +31,7 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelStateEvent; import io.netty.channel.Channels; +import io.netty.channel.LifeCycleAwareChannelHandler; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelHandler; import io.netty.channel.socket.nio.NioSocketChannelConfig; @@ -156,7 +159,7 @@ import io.netty.util.internal.QueueFactory; * {@link HashedWheelTimer} every second. * @apiviz.landmark */ -public class BufferedWriteHandler extends SimpleChannelHandler { +public class BufferedWriteHandler extends SimpleChannelHandler implements LifeCycleAwareChannelHandler { private final Queue queue; private final boolean consolidateOnFlush; @@ -351,4 +354,74 @@ public class BufferedWriteHandler extends SimpleChannelHandler { ctx.sendDownstream(e); } } + + /** + * Fail all buffered writes that are left. See #308 for more details + */ + @Override + public void afterRemove(ChannelHandlerContext ctx) throws Exception { + Throwable cause = null; + for (;;) { + MessageEvent ev = queue.poll(); + + if (ev == null) { + break; + } + + if (cause == null) { + cause = new IOException("Unable to flush message"); + } + ev.getFuture().setFailure(cause); + + } + if (cause != null) { + Channels.fireExceptionCaughtLater(ctx.getChannel(), cause); + } + } + + } From c24eafed480a78ccbf65ad3751529cfd7f89bd45 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 4 May 2012 14:36:30 +0200 Subject: [PATCH 084/134] MemoryAwareThreadPoolExecutor needs to notify ChannelFuture's of the queued ChannelEventRunnable on shutdownNow(). See #309 --- .../MemoryAwareThreadPoolExecutor.java | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/execution/MemoryAwareThreadPoolExecutor.java b/handler/src/main/java/io/netty/handler/execution/MemoryAwareThreadPoolExecutor.java index e78200ff99..a6ce8f674f 100644 --- a/handler/src/main/java/io/netty/handler/execution/MemoryAwareThreadPoolExecutor.java +++ b/handler/src/main/java/io/netty/handler/execution/MemoryAwareThreadPoolExecutor.java @@ -15,6 +15,10 @@ */ package io.netty.handler.execution; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -28,9 +32,11 @@ import java.util.concurrent.atomic.AtomicLong; import io.netty.buffer.ChannelBuffer; import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelState; import io.netty.channel.ChannelStateEvent; +import io.netty.channel.Channels; import io.netty.channel.MessageEvent; import io.netty.channel.WriteCompletionEvent; import io.netty.util.internal.ConcurrentIdentityHashMap; @@ -135,6 +141,8 @@ public class MemoryAwareThreadPoolExecutor extends ThreadPoolExecutor { new ConcurrentIdentityHashMap(); private final Limiter totalLimiter; + private volatile boolean notifyOnShutdown; + /** * Creates a new instance. * @@ -234,13 +242,68 @@ public class MemoryAwareThreadPoolExecutor extends ThreadPoolExecutor { // Misuse check misuseDetector.increase(); } - + @Override protected void terminated() { super.terminated(); misuseDetector.decrease(); } + /** + * This will call {@link #shutdownNow(boolean)} with the value of {@link #getNotifyChannelFuturesOnShutdown()}. + */ + @Override + public List shutdownNow() { + return shutdownNow(notifyOnShutdown); + } + + /** + * See {@link ThreadPoolExecutor#shutdownNow()} for how it handles the shutdown. If true is given to this method it also notifies all {@link ChannelFuture}'s + * of the not executed {@link ChannelEventRunnable}'s. + * + *

+ * Be aware that if you call this with false you will need to handle the notification of the {@link ChannelFuture}'s by your self. So only use this if you + * really have a use-case for it. + *

+ * + */ + public List shutdownNow(boolean notify) { + if (!notify) { + return super.shutdownNow(); + } + Throwable cause = null; + Set channels = null; + + List tasks = super.shutdownNow(); + + // loop over all tasks and cancel the ChannelFuture of the ChannelEventRunable's + for (Runnable task: tasks) { + if (task instanceof ChannelEventRunnable) { + if (cause == null) { + cause = new IOException("Unable to process queued event"); + } + ChannelEvent event = ((ChannelEventRunnable) task).getEvent(); + event.getFuture().setFailure(cause); + + if (channels == null) { + channels = new HashSet(); + } + + + // store the Channel of the event for later notification of the exceptionCaught event + channels.add(event.getChannel()); + } + } + + // loop over all channels and fire an exceptionCaught event + if (channels != null) { + for (Channel channel: channels) { + Channels.fireExceptionCaughtLater(channel, cause); + } + } + return tasks; + } + /** * Returns the {@link ObjectSizeEstimator} of this pool. */ @@ -295,6 +358,7 @@ public class MemoryAwareThreadPoolExecutor extends ThreadPoolExecutor { return totalLimiter.limit; } + /** * @deprecated maxTotalMemorySize is not modifiable anymore. */ @@ -311,6 +375,28 @@ public class MemoryAwareThreadPoolExecutor extends ThreadPoolExecutor { } } + /** + * If set to false no queued {@link ChannelEventRunnable}'s {@link ChannelFuture} will get notified once {@link #shutdownNow()} is called. + * If set to true every queued {@link ChannelEventRunnable} will get marked as failed via {@link ChannelFuture#setFailure(Throwable)}. + * + *

+ * Please only set this to false if you want to handle the notification by yourself and know what you are doing. Default is true. + *

+ */ + public void setNotifyChannelFuturesOnShutdown(boolean notifyOnShutdown) { + this.notifyOnShutdown = notifyOnShutdown; + } + + /** + * Returns if the {@link ChannelFuture}'s of the {@link ChannelEventRunnable}'s should be notified about the shutdown of this {@link MemoryAwareThreadPoolExecutor}. + * + */ + public boolean getNotifyChannelFuturesOnShutdown() { + return notifyOnShutdown; + } + + + @Override public void execute(Runnable command) { if (command instanceof ChannelDownstreamEventRunnable) { From 4b1721af17ec1a4e52d96e83d1fbfaa851b1de0b Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 6 May 2012 21:50:15 +0200 Subject: [PATCH 085/134] Fix regression in ChunkedWriteHandler. See #310 --- .../handler/stream/ChunkedWriteHandler.java | 29 +++-- .../stream/ChunkedWriteHandlerTest.java | 122 ++++++++++++++++++ 2 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index 017b996c5f..3329fd0578 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -114,12 +114,11 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns assert offered; final Channel channel = ctx.getChannel(); - if (channel.isWritable()) { + // call flush if the channel is writable or not connected. flush(..) will take care of the rest + + if (channel.isWritable() || !channel.isConnected()) { this.ctx = ctx; flush(ctx, false); - } else if (!channel.isConnected()) { - this.ctx = ctx; - discard(ctx, false); } } @@ -146,7 +145,6 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns private void discard(ChannelHandlerContext ctx, boolean fireNow) { ClosedChannelException cause = null; - boolean fireExceptionCaught = false; for (;;) { MessageEvent currentEvent = this.currentEvent; @@ -172,15 +170,16 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns cause = new ClosedChannelException(); } currentEvent.getFuture().setFailure(cause); - fireExceptionCaught = true; + + currentEvent = null; } - if (fireExceptionCaught) { + if (cause != null) { if (fireNow) { - fireExceptionCaught(ctx, cause); + Channels.fireExceptionCaught(ctx.getChannel(), cause); } else { - fireExceptionCaughtLater(ctx, cause); + Channels.fireExceptionCaughtLater(ctx.getChannel(), cause); } } } @@ -195,6 +194,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns if (!channel.isConnected()) { discard(ctx, fireNow); + return; } while (channel.isWritable()) { @@ -244,8 +244,8 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns if (suspend) { // ChunkedInput.nextChunk() returned null and it has - // not reached at the end of input. Let's wait until - // more chunks arrive. Nothing to write or notify. + // not reached at the end of input. Let's wait until + // more chunks arrive. Nothing to write or notify. break; } else { ChannelFuture writeFuture; @@ -253,7 +253,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns this.currentEvent = null; writeFuture = currentEvent.getFuture(); - // Register a listener which will close the input once the write is complete. This is needed because the Chunk may have + // Register a listener which will close the input once the write is complete. This is needed because the Chunk may have // some resource bound that can not be closed before its not written // // See https://github.com/netty/netty/issues/303 @@ -287,7 +287,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns if (!channel.isConnected()) { discard(ctx, fireNow); - break; + return; } } } finally { @@ -297,11 +297,12 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } - if (acquired && !channel.isConnected() || (channel.isWritable() && !queue.isEmpty())) { + if (acquired && (!channel.isConnected() || (channel.isWritable() && !queue.isEmpty()))) { flush(ctx, fireNow); } } + static void closeInput(ChunkedInput chunks) { try { chunks.close(); diff --git a/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java b/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java new file mode 100644 index 0000000000..b9e66b8864 --- /dev/null +++ b/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2011 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */package io.netty.handler.stream; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.Channels; + +import junit.framework.Assert; + +import io.netty.buffer.ChannelBuffer; +import io.netty.handler.codec.embedder.EncoderEmbedder; +import io.netty.handler.stream.ChunkedFile; +import io.netty.handler.stream.ChunkedInput; +import io.netty.handler.stream.ChunkedNioFile; +import io.netty.handler.stream.ChunkedNioStream; +import io.netty.handler.stream.ChunkedStream; +import io.netty.handler.stream.ChunkedWriteHandler; +import org.junit.Test; + +public class ChunkedWriteHandlerTest { + private static final byte[] BYTES = new byte[1024 * 64]; + private static final File TMP; + + static { + for (int i = 0; i < BYTES.length; i++) { + BYTES[i] = (byte) i; + } + + FileOutputStream out = null; + try { + TMP = File.createTempFile("netty-chunk-", ".tmp"); + TMP.deleteOnExit(); + out = new FileOutputStream(TMP); + out.write(BYTES); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + // See #310 + @Test + public void testChunkedStream() { + check(new ChunkedStream(new ByteArrayInputStream(BYTES))); + + check(new ChunkedStream(new ByteArrayInputStream(BYTES)), new ChunkedStream(new ByteArrayInputStream(BYTES)), new ChunkedStream(new ByteArrayInputStream(BYTES))); + + } + + @Test + public void testChunkedNioStream() { + check(new ChunkedNioStream(Channels.newChannel(new ByteArrayInputStream(BYTES)))); + + check(new ChunkedNioStream(Channels.newChannel(new ByteArrayInputStream(BYTES))), new ChunkedNioStream(Channels.newChannel(new ByteArrayInputStream(BYTES))), new ChunkedNioStream(Channels.newChannel(new ByteArrayInputStream(BYTES)))); + + } + + + @Test + public void testChunkedFile() throws IOException { + check(new ChunkedFile(TMP)); + + check(new ChunkedFile(TMP), new ChunkedFile(TMP), new ChunkedFile(TMP)); + } + + @Test + public void testChunkedNioFile() throws IOException { + check(new ChunkedNioFile(TMP)); + + check(new ChunkedNioFile(TMP), new ChunkedNioFile(TMP), new ChunkedNioFile(TMP)); + } + + private void check(ChunkedInput... inputs) { + EncoderEmbedder embedder = new EncoderEmbedder(new ChunkedWriteHandler()); + for (ChunkedInput input: inputs) { + embedder.offer(input); + } + + Assert.assertTrue(embedder.finish()); + + int i = 0; + int read = 0; + for (;;) { + ChannelBuffer buffer = embedder.poll(); + if (buffer == null) { + break; + } + while (buffer.readable()) { + Assert.assertEquals(BYTES[i++], buffer.readByte()); + read++; + if (i == BYTES.length) { + i = 0; + } + } + } + + Assert.assertEquals(BYTES.length * inputs.length, read); + } +} From 2cd6386a373d1441f12fd00418f104d43d41186f Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 9 May 2012 07:45:40 +0200 Subject: [PATCH 086/134] Make sure all MultiCast configuration settings are possible with NIO. See #313 --- .../nio/DefaultNioDatagramChannelConfig.java | 81 +++++++++++++++++++ .../DefaultNioDatagramChannelConfigTest.java | 53 ++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java diff --git a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java index 8a329b546f..30c98d000c 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java @@ -23,10 +23,15 @@ import io.netty.util.internal.ConversionUtil; import io.netty.util.internal.DetectionUtil; import java.io.IOException; +import java.net.InetAddress; +import java.net.MulticastSocket; import java.net.NetworkInterface; +import java.net.SocketException; import java.net.StandardSocketOptions; import java.nio.channels.DatagramChannel; +import java.util.Enumeration; import java.util.Map; +import java.util.Set; /** * The default {@link NioSocketChannelConfig} implementation. @@ -173,4 +178,80 @@ class DefaultNioDatagramChannelConfig extends DefaultDatagramChannelConfig } } + @Override + public int getTimeToLive() { + if (DetectionUtil.javaVersion() < 7) { + throw new UnsupportedOperationException(); + } else { + try { + return (int) channel.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + } catch (IOException e) { + throw new ChannelException(e); + } + } + } + + @Override + public void setTimeToLive(int ttl) { + if (DetectionUtil.javaVersion() < 7) { + throw new UnsupportedOperationException(); + } else { + try { + channel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, ttl); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + } + + @Override + public InetAddress getInterface() { + NetworkInterface inf = getNetworkInterface(); + if (inf == null) { + return null; + } else { + Enumeration addresses = inf.getInetAddresses(); + if (addresses.hasMoreElements()) { + return addresses.nextElement(); + } + return null; + } + } + + @Override + public void setInterface(InetAddress interfaceAddress) { + try { + setNetworkInterface(NetworkInterface.getByInetAddress(interfaceAddress)); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isLoopbackModeDisabled() { + if (DetectionUtil.javaVersion() < 7) { + throw new UnsupportedOperationException(); + } else { + try { + return (Boolean) channel.getOption(StandardSocketOptions.IP_MULTICAST_LOOP); + } catch (IOException e) { + throw new ChannelException(e); + } + } + } + + @Override + public void setLoopbackModeDisabled(boolean loopbackModeDisabled) { + if (DetectionUtil.javaVersion() < 7) { + throw new UnsupportedOperationException(); + } else { + try { + channel.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, loopbackModeDisabled); + } catch (IOException e) { + throw new ChannelException(e); + } + } + } + } diff --git a/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java b/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java new file mode 100644 index 0000000000..5545cb61f9 --- /dev/null +++ b/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.socket.nio; + +import io.netty.util.internal.DetectionUtil; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.StandardProtocolFamily; +import java.nio.channels.DatagramChannel; + +import junit.framework.Assert; + +import org.junit.Test; + +public class DefaultNioDatagramChannelConfigTest { + + @Test + public void testMulticastOptions() throws IOException { + if (DetectionUtil.javaVersion() < 7) { + return; + } + DefaultNioDatagramChannelConfig config = new DefaultNioDatagramChannelConfig(DatagramChannel.open(StandardProtocolFamily.INET)); + NetworkInterface inf = NetworkInterface.getNetworkInterfaces().nextElement(); + config.setNetworkInterface(inf); + Assert.assertEquals(inf, config.getNetworkInterface()); + + InetAddress localhost = inf.getInetAddresses().nextElement(); + config.setInterface(localhost); + Assert.assertEquals(localhost, config.getInterface()); + + config.setTimeToLive(100); + Assert.assertEquals(100, config.getTimeToLive()); + + config.setLoopbackModeDisabled(false); + Assert.assertEquals(false, config.isLoopbackModeDisabled()); + + } +} From 27358352ac7722cc907f3d86ec0c5bd8e7d8a04d Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 9 May 2012 07:49:18 +0200 Subject: [PATCH 087/134] Make sure all MultiCast configuration settings are possible with NIO. See #313 --- .../channel/socket/nio/DefaultNioDatagramChannelConfig.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java index 30c98d000c..4d854dbb12 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java @@ -24,14 +24,12 @@ import io.netty.util.internal.DetectionUtil; import java.io.IOException; import java.net.InetAddress; -import java.net.MulticastSocket; import java.net.NetworkInterface; import java.net.SocketException; import java.net.StandardSocketOptions; import java.nio.channels.DatagramChannel; import java.util.Enumeration; import java.util.Map; -import java.util.Set; /** * The default {@link NioSocketChannelConfig} implementation. From d62977b061d3ccadc89a336533605f984771d09e Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 10 May 2012 08:09:31 +0200 Subject: [PATCH 088/134] Set source java version to 1.7 where needed. See #312 --- codec-http/pom.xml | 4 ++++ pom.xml | 9 +++++++-- testsuite/pom.xml | 4 ++++ transport/pom.xml | 4 ++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/codec-http/pom.xml b/codec-http/pom.xml index 82417dac2a..d34ccf83aa 100644 --- a/codec-http/pom.xml +++ b/codec-http/pom.xml @@ -40,5 +40,9 @@ ${project.version} + + + 1.7 + diff --git a/pom.xml b/pom.xml index 5cd75226a5..fba7660035 100644 --- a/pom.xml +++ b/pom.xml @@ -229,8 +229,8 @@ 2.3.2 UTF-8 - 1.6 - 1.6 + ${sourceJavaVersion} + ${targetJavaVersion} true true true @@ -340,5 +340,10 @@ + + + 1.6 + 1.7 + diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 83a0a10452..06695247ee 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -35,5 +35,9 @@ ${project.version} + + + 1.7 + diff --git a/transport/pom.xml b/transport/pom.xml index 89f4adee74..9af30368c0 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -35,5 +35,9 @@ ${project.version} + + + 1.7 + From d56aa76911fcc5f10f62263a3ccc6023b1d69ca4 Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 10 May 2012 10:06:38 +0200 Subject: [PATCH 089/134] Revert "Set source java version to 1.7 where needed. See #312" as it breaks the build This reverts commit d62977b061d3ccadc89a336533605f984771d09e. --- codec-http/pom.xml | 4 ---- pom.xml | 9 ++------- testsuite/pom.xml | 4 ---- transport/pom.xml | 4 ---- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/codec-http/pom.xml b/codec-http/pom.xml index d34ccf83aa..82417dac2a 100644 --- a/codec-http/pom.xml +++ b/codec-http/pom.xml @@ -40,9 +40,5 @@ ${project.version} - - - 1.7 - diff --git a/pom.xml b/pom.xml index fba7660035..5cd75226a5 100644 --- a/pom.xml +++ b/pom.xml @@ -229,8 +229,8 @@ 2.3.2 UTF-8 - ${sourceJavaVersion} - ${targetJavaVersion} + 1.6 + 1.6 true true true @@ -340,10 +340,5 @@ - - - 1.6 - 1.7 - diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 06695247ee..83a0a10452 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -35,9 +35,5 @@ ${project.version} - - - 1.7 - diff --git a/transport/pom.xml b/transport/pom.xml index 9af30368c0..89f4adee74 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -35,9 +35,5 @@ ${project.version} - - - 1.7 - From abd10d9089bb40dc75b067bdfe90a9befbfbe694 Mon Sep 17 00:00:00 2001 From: vibul Date: Sat, 12 May 2012 21:05:15 +1000 Subject: [PATCH 090/134] Fixed bug where subprotocol not sent by client --- .../websocketx/WebSocketClientHandshaker.java | 9 ++++--- .../WebSocketClientHandshaker00.java | 10 +++++--- .../WebSocketClientHandshaker08.java | 11 +++++--- .../WebSocketClientHandshaker13.java | 11 +++++--- .../websocketx/WebSocketServerHandshaker.java | 25 ++++++++++++++++--- .../WebSocketServerHandshaker00.java | 12 ++++++--- .../WebSocketServerHandshaker08.java | 13 +++++++--- .../WebSocketServerHandshaker13.java | 13 +++++++--- 8 files changed, 75 insertions(+), 29 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index 04d6e2c1f7..ca91699a52 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -50,7 +50,7 @@ public abstract class WebSocketClientHandshaker { * @param version * Version of web socket specification to use to connect to the server * @param subprotocol - * Sub protocol request sent to the server. + * CSV of requested subprotocol(s) sent to the server. * @param customHeaders * Map of custom headers to add to the client request */ @@ -78,7 +78,7 @@ public abstract class WebSocketClientHandshaker { Map customHeaders, long maxFramePayloadLength) { this.webSocketUrl = webSocketUrl; this.version = version; - expectedSubprotocol = subprotocol; + this.expectedSubprotocol = subprotocol; this.customHeaders = customHeaders; this.maxFramePayloadLength = maxFramePayloadLength; } @@ -116,14 +116,15 @@ public abstract class WebSocketClientHandshaker { } /** - * Returns the sub protocol request sent to the server as specified in the constructor + * Returns the CSV of requested subprotocol(s) sent to the server as specified in the constructor */ public String getExpectedSubprotocol() { return expectedSubprotocol; } /** - * Returns the sub protocol response and sent by the server. Only available after end of handshake. + * Returns the subprotocol response sent by the server. Only available after end of handshake. + * Null if no subprotocol was requested or confirmed by the server. */ public String getActualSubprotocol() { return actualSubprotocol; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index b52b840907..0797e9132f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -169,10 +169,12 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); - if (getExpectedSubprotocol() != null && !getExpectedSubprotocol().equals("")) { - request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, getExpectedSubprotocol()); + String expectedSubprotocol = this.getExpectedSubprotocol(); + if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) { + request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol); } + if (customHeaders != null) { for (String header : customHeaders.keySet()) { request.addHeader(header, customHeaders.get(header)); @@ -235,8 +237,8 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { throw new WebSocketHandshakeException("Invalid challenge"); } - String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); - setActualSubprotocol(protocol); + String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + setActualSubprotocol(subprotocol); channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder(this.getMaxFramePayloadLength())); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 5ba568e3cc..67bbca0c30 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -49,8 +49,6 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { private String expectedChallengeResponseString; - private static final String protocol = null; - private final boolean allowExtensions; /** @@ -157,9 +155,11 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { // See https://github.com/netty/netty/issues/264 request.addHeader(Names.SEC_WEBSOCKET_ORIGIN, originValue); - if (protocol != null && !protocol.equals("")) { - request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); + String expectedSubprotocol = this.getExpectedSubprotocol(); + if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) { + request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol); } + request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8"); if (customHeaders != null) { @@ -224,6 +224,9 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { expectedChallengeResponseString)); } + String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + setActualSubprotocol(subprotocol); + channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket08FrameDecoder(false, allowExtensions, this.getMaxFramePayloadLength())); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 5f31eed5b0..a4a079f3d3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -49,8 +49,6 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { private String expectedChallengeResponseString; - private static final String protocol = null; - private final boolean allowExtensions; /** @@ -154,9 +152,11 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { } request.addHeader(Names.ORIGIN, originValue); - if (protocol != null && !protocol.equals("")) { - request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); + String expectedSubprotocol = this.getExpectedSubprotocol(); + if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) { + request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol); } + request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13"); if (customHeaders != null) { @@ -221,6 +221,9 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { expectedChallengeResponseString)); } + String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + setActualSubprotocol(subprotocol); + channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket13FrameDecoder(false, allowExtensions, this.getMaxFramePayloadLength())); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index e648915a6f..4ccc548682 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -35,6 +35,8 @@ public abstract class WebSocketServerHandshaker { private final long maxFramePayloadLength; + private String selectedSubprotocol = null; + /** * Constructor using default values * @@ -49,7 +51,7 @@ public abstract class WebSocketServerHandshaker { protected WebSocketServerHandshaker(WebSocketVersion version, String webSocketUrl, String subprotocols) { this(version, webSocketUrl, subprotocols, Long.MAX_VALUE); } - + /** * Constructor specifying the destination web socket location * @@ -105,7 +107,7 @@ public abstract class WebSocketServerHandshaker { } /** - * Returns the max length for any frame's payload + * Returns the max length for any frame's payload */ public long getMaxFramePayloadLength() { return maxFramePayloadLength; @@ -143,8 +145,8 @@ public abstract class WebSocketServerHandshaker { return null; } - String[] requesteSubprotocolArray = requestedSubprotocols.split(","); - for (String p : requesteSubprotocolArray) { + String[] requestedSubprotocolArray = requestedSubprotocols.split(","); + for (String p : requestedSubprotocolArray) { String requestedSubprotocol = p.trim(); for (String supportedSubprotocol : subprotocols) { @@ -157,4 +159,19 @@ public abstract class WebSocketServerHandshaker { // No match found return null; } + + /** + * Returns the selected subprotocol. Null if no subprotocol has been selected. + *

+ * This is only available AFTER handshake() has been called. + *

+ */ + public String getSelectedSubprotocol() { + return selectedSubprotocol; + } + + protected void setSelectedSubprotocol(String value) { + selectedSubprotocol = value; + } + } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java index 78a5f7669b..2e1c426c30 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java @@ -151,9 +151,15 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { // New handshake method with a challenge: res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketUrl()); - String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL); - if (protocol != null) { - res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol)); + String subprotocols = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + if (subprotocols != null) { + String selectedSubprotocol = selectSubprotocol(subprotocols); + if (selectedSubprotocol == null) { + throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols); + } else { + res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol); + this.setSelectedSubprotocol(selectedSubprotocol); + } } // Calculate the answer of the challenge. diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index 96eedfc580..a246347f2e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -148,11 +148,18 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); - String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); - if (protocol != null) { - res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol)); + String subprotocols = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + if (subprotocols != null) { + String selectedSubprotocol = selectSubprotocol(subprotocols); + if (selectedSubprotocol == null) { + throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols); + } else { + res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol); + this.setSelectedSubprotocol(selectedSubprotocol); + } } + ChannelFuture future = channel.write(res); // Upgrade the connection and send the handshake response. diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index dab9a8f8d9..cdb410c386 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -149,11 +149,18 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); - String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); - if (protocol != null) { - res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol)); + String subprotocols = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + if (subprotocols != null) { + String selectedSubprotocol = selectSubprotocol(subprotocols); + if (selectedSubprotocol == null) { + throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols); + } else { + res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol); + this.setSelectedSubprotocol(selectedSubprotocol); + } } + ChannelFuture future = channel.write(res); // Upgrade the connection and send the handshake response. From b09962f4c26c5231523584f82fc6cf86fb687970 Mon Sep 17 00:00:00 2001 From: vibul Date: Sat, 12 May 2012 21:22:33 +1000 Subject: [PATCH 091/134] forgot 1 more change --- .../codec/http/websocketx/WebSocketClientHandshaker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index ca91699a52..2dfd44b670 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -35,7 +35,7 @@ public abstract class WebSocketClientHandshaker { private final String expectedSubprotocol; - private String actualSubprotocol; + private String actualSubprotocol = null; protected final Map customHeaders; From 9f6cc0a0d28cd3ebd456c5cf3cff39a974979ccc Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 14 May 2012 07:32:52 +0200 Subject: [PATCH 092/134] Fix checkstyle errors --- .../codec/http/websocketx/WebSocketClientHandshaker.java | 2 +- .../codec/http/websocketx/WebSocketServerHandshaker.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index 2dfd44b670..ca91699a52 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -35,7 +35,7 @@ public abstract class WebSocketClientHandshaker { private final String expectedSubprotocol; - private String actualSubprotocol = null; + private String actualSubprotocol; protected final Map customHeaders; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index 4ccc548682..b9b25883c6 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -35,7 +35,7 @@ public abstract class WebSocketServerHandshaker { private final long maxFramePayloadLength; - private String selectedSubprotocol = null; + private String selectedSubprotocol; /** * Constructor using default values From 52a7d28cb59e3806fda322aecf7a85a6adaeb305 Mon Sep 17 00:00:00 2001 From: norman Date: Tue, 15 May 2012 13:59:33 +0200 Subject: [PATCH 093/134] Make sure CompositeChanneBuffer does not throw a UnsupportedOperationException if discardReadBytes() discard the whole content of the buffer. See #325 --- .../java/io/netty/buffer/CompositeChannelBuffer.java | 9 +++++++++ .../java/io/netty/buffer/AbstractChannelBufferTest.java | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java index 659ec30177..b8b673bf70 100644 --- a/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java @@ -660,6 +660,15 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { final int bytesToMove = capacity() - localReaderIndex; List list = decompose(localReaderIndex, bytesToMove); + + // If the list is empty we need to assign a new one because + // we get a List that is immutable. + // + // See https://github.com/netty/netty/issues/325 + if (list.isEmpty()) { + list = new ArrayList(1); + } + // Add a new buffer so that the capacity of this composite buffer does // not decrease due to the discarded components. // XXX Might create too many components if discarded by small amount. diff --git a/buffer/src/test/java/io/netty/buffer/AbstractChannelBufferTest.java b/buffer/src/test/java/io/netty/buffer/AbstractChannelBufferTest.java index 8e3df60f54..73e7ef80ee 100644 --- a/buffer/src/test/java/io/netty/buffer/AbstractChannelBufferTest.java +++ b/buffer/src/test/java/io/netty/buffer/AbstractChannelBufferTest.java @@ -1641,4 +1641,12 @@ public abstract class AbstractChannelBufferTest { assertFalse(set.contains(elemB)); assertEquals(0, set.size()); } + + // Test case for https://github.com/netty/netty/issues/325 + @Test + public void testDiscardAllReadBytes() { + buffer.writerIndex(buffer.capacity()); + buffer.readerIndex(buffer.writerIndex()); + buffer.discardReadBytes(); + } } From ef384a7b2181a79fcc1e44401aab55f13cd69099 Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 16 May 2012 13:00:49 +0200 Subject: [PATCH 094/134] Add JBoss Marshalling Encoder/Decoder. See #324 --- codec/pom.xml | 17 +++ .../marshalling/ChannelBufferByteInput.java | 75 ++++++++++ .../marshalling/ChannelBufferByteOutput.java | 77 ++++++++++ .../codec/marshalling/LimitingByteInput.java | 108 ++++++++++++++ .../codec/marshalling/MarshallingDecoder.java | 122 ++++++++++++++++ .../codec/marshalling/MarshallingEncoder.java | 59 ++++++++ .../ThreadLocalMarshallingDecoder.java | 52 +++++++ .../codec/marshalling/package-info.java | 21 +++ .../AbstractMarshallingDecoderTest.java | 135 ++++++++++++++++++ .../AbstractMarshallingEncoderTest.java | 63 ++++++++ .../RiverMarshallingDecoderTest.java | 37 +++++ .../RiverMarshallingEncoderTest.java | 38 +++++ ...iverThreadLocalMarshallingDecoderTest.java | 27 ++++ .../SerialMarshallingDecoderTest.java | 37 +++++ .../SerialMarshallingEncoderTest.java | 38 +++++ ...rialThreadLocalMarshallingDecoderTest.java | 28 ++++ pom.xml | 27 ++++ 17 files changed, 961 insertions(+) create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteInput.java create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteOutput.java create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/LimitingByteInput.java create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingDecoder.java create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingEncoder.java create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallingDecoder.java create mode 100644 codec/src/main/java/io/netty/handler/codec/marshalling/package-info.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingDecoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingEncoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingDecoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingEncoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/RiverThreadLocalMarshallingDecoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingDecoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingEncoderTest.java create mode 100644 codec/src/test/java/io/netty/handler/codec/marshalling/SerialThreadLocalMarshallingDecoderTest.java diff --git a/codec/pom.xml b/codec/pom.xml index 7dde784f51..e52aa7a64f 100644 --- a/codec/pom.xml +++ b/codec/pom.xml @@ -39,6 +39,23 @@ protobuf-java true + + org.jboss.marshalling + jboss-marshalling + true + + + + + org.jboss.marshalling + jboss-marshalling-serial + test + + + org.jboss.marshalling + jboss-marshalling-river + test + diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteInput.java b/codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteInput.java new file mode 100644 index 0000000000..d2ec032cef --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteInput.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import java.io.IOException; + +import org.jboss.marshalling.ByteInput; +import io.netty.buffer.ChannelBuffer; + +/** + * {@link ByteInput} implementation which reads its data from a {@link ChannelBuffer} + * + * + */ +class ChannelBufferByteInput implements ByteInput { + + private final ChannelBuffer buffer; + + public ChannelBufferByteInput(ChannelBuffer buffer) { + this.buffer = buffer; + } + + public void close() throws IOException { + // nothing to do + } + + public int available() throws IOException { + return buffer.readableBytes(); + } + + public int read() throws IOException { + if (buffer.readable()) { + return buffer.readByte() & 0xff; + } + return -1; + } + + public int read(byte[] array) throws IOException { + return read(array, 0, array.length); + } + + public int read(byte[] dst, int dstIndex, int length) throws IOException { + int available = available(); + if (available == 0) { + return -1; + } + + length = Math.min(available, length); + buffer.readBytes(dst, dstIndex, length); + return length; + } + + public long skip(long bytes) throws IOException { + int readable = buffer.readableBytes(); + if (readable < bytes) { + bytes = readable; + } + buffer.readerIndex((int) (buffer.readerIndex() + bytes)); + return bytes; + } + +} diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteOutput.java b/codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteOutput.java new file mode 100644 index 0000000000..4c2e915648 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/ChannelBufferByteOutput.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import java.io.IOException; + +import org.jboss.marshalling.ByteOutput; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBufferFactory; +import io.netty.buffer.ChannelBuffers; + +/** + * {@link ByteOutput} implementation which writes the data to a {@link ChannelBuffer} + * + * + */ +class ChannelBufferByteOutput implements ByteOutput { + + private final ChannelBuffer buffer; + + + /** + * Create a new instance which use the given {@link ChannelBuffer} + */ + public ChannelBufferByteOutput(ChannelBuffer buffer) { + this.buffer = buffer; + } + + /** + * Calls {@link #ChannelBufferByteOutput(ChannelBuffer)} with a dynamic {@link ChannelBuffer} + */ + public ChannelBufferByteOutput(ChannelBufferFactory factory, int estimatedLength) { + this(ChannelBuffers.dynamicBuffer(estimatedLength, factory)); + } + + public void close() throws IOException { + // Nothing todo + } + + public void flush() throws IOException { + // nothing to do + } + + public void write(int b) throws IOException { + buffer.writeByte(b); + } + + public void write(byte[] bytes) throws IOException { + buffer.writeBytes(bytes); + } + + public void write(byte[] bytes, int srcIndex, int length) throws IOException { + buffer.writeBytes(bytes, srcIndex, length); + } + + /** + * Return the {@link ChannelBuffer} which contains the written content + * + */ + public ChannelBuffer getBuffer() { + return buffer; + } + +} diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/LimitingByteInput.java b/codec/src/main/java/io/netty/handler/codec/marshalling/LimitingByteInput.java new file mode 100644 index 0000000000..6ad9777951 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/LimitingByteInput.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import java.io.IOException; + +import org.jboss.marshalling.ByteInput; + +/** + * {@link ByteInput} implementation which wraps another {@link ByteInput} and throws a {@link TooBigObjectException} + * if the read limit was reached. + * + * + */ +class LimitingByteInput implements ByteInput { + + // Use a static instance here to remove the overhead of fillStacktrace + private static final TooBigObjectException EXCEPTION = new TooBigObjectException(); + + private final ByteInput input; + private final long limit; + private long read; + + public LimitingByteInput(ByteInput input, long limit) { + if (limit <= 0) { + throw new IllegalArgumentException("The limit MUST be > 0"); + } + this.input = input; + this.limit = limit; + } + + public void close() throws IOException { + // Nothing todo + } + + public int available() throws IOException { + int available = input.available(); + int readable = readable(available); + return readable; + } + + public int read() throws IOException { + int readable = readable(1); + if (readable > 0) { + int b = input.read(); + read++; + return b; + } else { + throw EXCEPTION; + } + } + + public int read(byte[] array) throws IOException { + return read(array, 0, array.length); + } + + public int read(byte[] array, int offset, int length) throws IOException { + int readable = readable(length); + if (readable > 0) { + int i = input.read(array, offset, readable); + read += i; + return i; + } else { + throw EXCEPTION; + } + } + + public long skip(long bytes) throws IOException { + int readable = readable((int) bytes); + if (readable > 0) { + long i = input.skip(readable); + read += i; + return i; + } else { + throw EXCEPTION; + } + } + + private int readable(int length) { + return (int) Math.min(length, limit - read); + } + + /** + * Exception that will get thrown if the {@link Object} is to big to unmarshall + * + */ + static final class TooBigObjectException extends IOException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingDecoder.java b/codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingDecoder.java new file mode 100644 index 0000000000..14c60c4670 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingDecoder.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import java.io.IOException; +import java.io.ObjectStreamConstants; + +import org.jboss.marshalling.ByteInput; +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.MarshallingConfiguration; +import org.jboss.marshalling.Unmarshaller; +import io.netty.buffer.ChannelBuffer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ExceptionEvent; +import io.netty.handler.codec.frame.TooLongFrameException; +import io.netty.handler.codec.replay.ReplayingDecoder; +import io.netty.handler.codec.replay.VoidEnum; + +/** + * {@link ReplayingDecoder} which use an {@link Unmarshaller} to read the Object out of the {@link ChannelBuffer}. + * + * Most times you want to use {@link ThreadLocalMarshallingDecoder} to get a better performance and less overhead. + * + * + */ +public class MarshallingDecoder extends ReplayingDecoder { + protected final MarshallingConfiguration config; + protected final MarshallerFactory factory; + protected final long maxObjectSize; + + /** + * Create a new instance of {@link MarshallingDecoder}. + * + * @param factory the {@link MarshallerFactory} which is used to obtain the {@link Unmarshaller} from + * @param config the {@link MarshallingConfiguration} to use + * @param maxObjectSize the maximal size (in bytes) of the {@link Object} to unmarshal. Once the size is exceeded + * the {@link Channel} will get closed. Use a a maxObjectSize of <= 0 to disable this. + * You should only do this if you are sure that the received Objects will never be big and the + * sending side are trusted, as this opens the possibility for a DOS-Attack due an {@link OutOfMemoryError}. + * + */ + public MarshallingDecoder(MarshallerFactory factory, MarshallingConfiguration config, long maxObjectSize) { + this.factory = factory; + this.config = config; + this.maxObjectSize = maxObjectSize; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state) throws Exception { + Unmarshaller unmarshaller = factory.createUnmarshaller(config); + ByteInput input = new ChannelBufferByteInput(buffer); + if (maxObjectSize > 0) { + input = new LimitingByteInput(input, maxObjectSize); + } + try { + unmarshaller.start(input); + Object obj = unmarshaller.readObject(); + unmarshaller.finish(); + return obj; + } catch (LimitingByteInput.TooBigObjectException e) { + throw new TooLongFrameException("Object to big to unmarshal"); + } finally { + // Call close in a finally block as the ReplayingDecoder will throw an Error if not enough bytes are + // readable. This helps to be sure that we do not leak resource + unmarshaller.close(); + } + } + + @Override + protected Object decodeLast(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer, VoidEnum state) + throws Exception { + switch (buffer.readableBytes()) { + case 0: + return null; + case 1: + // Ignore the last TC_RESET + if (buffer.getByte(buffer.readerIndex()) == ObjectStreamConstants.TC_RESET) { + buffer.skipBytes(1); + return null; + } + } + + Object decoded = decode(ctx, channel, buffer, state); + return decoded; + } + + /** + * Calls {@link Channel#close()} if a TooLongFrameException was thrown + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + if (e.getCause() instanceof TooLongFrameException) { + e.getChannel().close(); + + } else { + super.exceptionCaught(ctx, e); + } + } + + /** + * Create a new {@link Unmarshaller} for the given {@link Channel} + * + */ + protected Unmarshaller getUnmarshaller(Channel channel) throws IOException { + return factory.createUnmarshaller(config); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingEncoder.java b/codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingEncoder.java new file mode 100644 index 0000000000..c8595f214f --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/MarshallingEncoder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.Marshaller; +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.MarshallingConfiguration; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * {@link OneToOneEncoder} implementation which uses JBoss Marshalling to marshal + * an Object. + * + * See JBoss Marshalling website + * for more informations + * + */ +@Sharable +public class MarshallingEncoder extends OneToOneEncoder { + + private final MarshallerFactory factory; + private final MarshallingConfiguration config; + + + public MarshallingEncoder(MarshallerFactory factory, MarshallingConfiguration config) { + this.factory = factory; + this.config = config; + } + + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + Marshaller marshaller = factory.createMarshaller(config); + ChannelBufferByteOutput output = new ChannelBufferByteOutput(ctx.getChannel().getConfig().getBufferFactory(), 256); + marshaller.start(output); + marshaller.writeObject(msg); + marshaller.finish(); + marshaller.close(); + + return output.getBuffer(); + } + +} diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallingDecoder.java b/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallingDecoder.java new file mode 100644 index 0000000000..ffbed31876 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallingDecoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import java.io.IOException; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.MarshallingConfiguration; +import org.jboss.marshalling.Unmarshaller; +import io.netty.channel.Channel; + +/** + * A subclass of {@link MarshallingDecoder} which use one {@link Unmarshaller} per Thread via a {@link ThreadLocal}. + * + * For more informations see {@link MarshallingDecoder}. + * + */ +public class ThreadLocalMarshallingDecoder extends MarshallingDecoder { + + private static final ThreadLocal UNMARSHALLERS = new ThreadLocal(); + + /** + * See {@link MarshallingDecoder#MarshallingDecoder(MarshallerFactory, MarshallingConfiguration, long)} + */ + public ThreadLocalMarshallingDecoder(MarshallerFactory factory, MarshallingConfiguration config, long maxObjectSize) { + super(factory, config, maxObjectSize); + } + + @Override + protected Unmarshaller getUnmarshaller(Channel channel) throws IOException { + Unmarshaller unmarshaller = UNMARSHALLERS.get(); + if (unmarshaller == null) { + unmarshaller = factory.createUnmarshaller(config); + UNMARSHALLERS.set(unmarshaller); + } + return unmarshaller; + } + +} diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/package-info.java b/codec/src/main/java/io/netty/handler/codec/marshalling/package-info.java new file mode 100644 index 0000000000..c95b607278 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Decoder and Encoder which uses JBoss Marshalling. + * + */ +package io.netty.handler.codec.marshalling; diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingDecoderTest.java new file mode 100644 index 0000000000..7271f19157 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingDecoderTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import junit.framework.Assert; + +import org.jboss.marshalling.Marshaller; +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.handler.codec.embedder.CodecEmbedderException; +import io.netty.handler.codec.embedder.DecoderEmbedder; +import io.netty.handler.codec.frame.TooLongFrameException; +import org.junit.Test; + +public abstract class AbstractMarshallingDecoderTest { + private final String testObject = new String("test"); + + @Test + public void testSimpleUnmarshalling() throws IOException { + MarshallerFactory marshallerFactory = createMarshallerFactory(); + MarshallingConfiguration configuration = createMarshallingConfig(); + + DecoderEmbedder decoder = new DecoderEmbedder(createDecoder(marshallerFactory, configuration, 0)); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + Marshaller marshaller = marshallerFactory.createMarshaller(configuration); + marshaller.start(Marshalling.createByteOutput(bout)); + marshaller.writeObject(testObject); + marshaller.finish(); + marshaller.close(); + + byte[] testBytes = bout.toByteArray(); + + decoder.offer(ChannelBuffers.wrappedBuffer(testBytes)); + assertTrue(decoder.finish()); + + String unmarshalled = (String) decoder.poll(); + + Assert.assertEquals(testObject, unmarshalled); + + Assert.assertNull(decoder.poll()); + } + + + @Test + public void testFragmentedUnmarshalling() throws IOException { + MarshallerFactory marshallerFactory = createMarshallerFactory(); + MarshallingConfiguration configuration = createMarshallingConfig(); + + DecoderEmbedder decoder = new DecoderEmbedder(createDecoder(marshallerFactory, configuration, 0)); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + Marshaller marshaller = marshallerFactory.createMarshaller(configuration); + marshaller.start(Marshalling.createByteOutput(bout)); + marshaller.writeObject(testObject); + marshaller.finish(); + marshaller.close(); + + byte[] testBytes = bout.toByteArray(); + + ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(testBytes); + ChannelBuffer slice = buffer.readSlice(2); + + decoder.offer(slice); + decoder.offer(buffer); + assertTrue(decoder.finish()); + + + String unmarshalled = (String) decoder.poll(); + + Assert.assertEquals(testObject, unmarshalled); + + Assert.assertNull(decoder.poll()); + } + + @Test + public void testTooBigObject() throws IOException { + MarshallerFactory marshallerFactory = createMarshallerFactory(); + MarshallingConfiguration configuration = createMarshallingConfig(); + + MarshallingDecoder mDecoder = createDecoder(marshallerFactory, configuration, 1); + DecoderEmbedder decoder = new DecoderEmbedder(mDecoder); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + Marshaller marshaller = marshallerFactory.createMarshaller(configuration); + marshaller.start(Marshalling.createByteOutput(bout)); + marshaller.writeObject(testObject); + marshaller.finish(); + marshaller.close(); + + byte[] testBytes = bout.toByteArray(); + + try { + decoder.offer(ChannelBuffers.wrappedBuffer(testBytes)); + fail(); + } catch (CodecEmbedderException e) { + assertEquals(TooLongFrameException.class, e.getCause().getClass()); + + + } + + } + + protected MarshallingDecoder createDecoder(MarshallerFactory factory, MarshallingConfiguration config, long maxObjectSize) { + return new MarshallingDecoder(factory, config, maxObjectSize); + } + + + protected abstract MarshallerFactory createMarshallerFactory(); + protected abstract MarshallingConfiguration createMarshallingConfig(); + +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingEncoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingEncoderTest.java new file mode 100644 index 0000000000..a67acdc7a7 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractMarshallingEncoderTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import java.io.IOException; + +import junit.framework.Assert; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; +import org.jboss.marshalling.Unmarshaller; +import io.netty.buffer.ChannelBuffer; +import io.netty.handler.codec.embedder.EncoderEmbedder; +import org.junit.Test; + +public abstract class AbstractMarshallingEncoderTest { + + @Test + public void testMarshalling() throws IOException, ClassNotFoundException { + String testObject = new String("test"); + + final MarshallerFactory marshallerFactory = createMarshallerFactory(); + final MarshallingConfiguration configuration = createMarshallingConfig(); + + EncoderEmbedder encoder = new EncoderEmbedder(new MarshallingEncoder(marshallerFactory, configuration)); + + encoder.offer(testObject); + Assert.assertTrue(encoder.finish()); + + ChannelBuffer buffer = encoder.poll(); + + Unmarshaller unmarshaller = marshallerFactory.createUnmarshaller(configuration); + unmarshaller.start(Marshalling.createByteInput(buffer.toByteBuffer())); + String read = (String) unmarshaller.readObject(); + Assert.assertEquals(testObject, read); + + Assert.assertEquals(-1, unmarshaller.read()); + + Assert.assertNull(encoder.poll()); + + unmarshaller.finish(); + unmarshaller.close(); + } + + + protected abstract MarshallerFactory createMarshallerFactory(); + + protected abstract MarshallingConfiguration createMarshallingConfig(); +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingDecoderTest.java new file mode 100644 index 0000000000..c259a6807d --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingDecoderTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; + +public class RiverMarshallingDecoderTest extends AbstractMarshallingDecoderTest { + + @Override + protected MarshallerFactory createMarshallerFactory() { + return Marshalling.getProvidedMarshallerFactory("river"); + } + + @Override + protected MarshallingConfiguration createMarshallingConfig() { + // Create a configuration + final MarshallingConfiguration configuration = new MarshallingConfiguration(); + configuration.setVersion(3); + return configuration; + } + +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingEncoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingEncoderTest.java new file mode 100644 index 0000000000..24711e831f --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/RiverMarshallingEncoderTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; + +public class RiverMarshallingEncoderTest extends AbstractMarshallingEncoderTest { + + + @Override + protected MarshallerFactory createMarshallerFactory() { + return Marshalling.getProvidedMarshallerFactory("river"); + } + + @Override + protected MarshallingConfiguration createMarshallingConfig() { + // Create a configuration + final MarshallingConfiguration configuration = new MarshallingConfiguration(); + configuration.setVersion(3); + return configuration; + } + +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/RiverThreadLocalMarshallingDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/RiverThreadLocalMarshallingDecoderTest.java new file mode 100644 index 0000000000..92d9ffd2c9 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/RiverThreadLocalMarshallingDecoderTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.MarshallingConfiguration; + +public class RiverThreadLocalMarshallingDecoderTest extends RiverMarshallingDecoderTest { + + @Override + protected MarshallingDecoder createDecoder(MarshallerFactory factory, MarshallingConfiguration config, long maxObjectSize) { + return new ThreadLocalMarshallingDecoder(factory, config, maxObjectSize); + } +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingDecoderTest.java new file mode 100644 index 0000000000..0cab76e0e8 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingDecoderTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; + +public class SerialMarshallingDecoderTest extends AbstractMarshallingDecoderTest { + + @Override + protected MarshallerFactory createMarshallerFactory() { + return Marshalling.getProvidedMarshallerFactory("serial"); + } + + @Override + protected MarshallingConfiguration createMarshallingConfig() { + // Create a configuration + final MarshallingConfiguration configuration = new MarshallingConfiguration(); + configuration.setVersion(5); + return configuration; + } + +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingEncoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingEncoderTest.java new file mode 100644 index 0000000000..dd4c5e6d50 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/SerialMarshallingEncoderTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.Marshalling; +import org.jboss.marshalling.MarshallingConfiguration; + +public class SerialMarshallingEncoderTest extends AbstractMarshallingEncoderTest { + + + @Override + protected MarshallerFactory createMarshallerFactory() { + return Marshalling.getProvidedMarshallerFactory("serial"); + } + + @Override + protected MarshallingConfiguration createMarshallingConfig() { + // Create a configuration + final MarshallingConfiguration configuration = new MarshallingConfiguration(); + configuration.setVersion(5); + return configuration; + } + +} diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/SerialThreadLocalMarshallingDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/SerialThreadLocalMarshallingDecoderTest.java new file mode 100644 index 0000000000..d71b59886e --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/SerialThreadLocalMarshallingDecoderTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.marshalling; + +import org.jboss.marshalling.MarshallerFactory; +import org.jboss.marshalling.MarshallingConfiguration; + +public class SerialThreadLocalMarshallingDecoderTest extends SerialMarshallingDecoderTest { + + @Override + protected MarshallingDecoder createDecoder(MarshallerFactory factory, MarshallingConfiguration config, long maxObjectSize) { + return new ThreadLocalMarshallingDecoder(factory, config, maxObjectSize); + } + +} diff --git a/pom.xml b/pom.xml index 5cd75226a5..8b335c4218 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,15 @@ + + + org.jboss.marshalling + jboss-marshalling + ${jboss.marshalling.version} + compile + true + + com.google.protobuf protobuf-java @@ -161,6 +170,19 @@ true + + + org.jboss.marshalling + jboss-marshalling-serial + ${jboss.marshalling.version} + test + + + org.jboss.marshalling + jboss-marshalling-river + ${jboss.marshalling.version} + test + @@ -196,8 +218,13 @@ 1.6.4 test + + + 1.3.14.GA + + From a99f2589842ef2c010df2d304a084dd40c9c36d3 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 18 May 2012 08:10:34 +0200 Subject: [PATCH 095/134] Make sure SslHandler also works if SslBufferPool use non heap ByteBuffers. See #329 --- .../java/io/netty/handler/ssl/SslHandler.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 557764296d..dede5b48a8 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -710,8 +710,14 @@ public class SslHandler extends FrameDecoder if (result.bytesProduced() > 0) { outNetBuf.flip(); - msg = ChannelBuffers.buffer(outNetBuf.remaining()); - msg.writeBytes(outNetBuf.array(), 0, msg.capacity()); + int remaining = outNetBuf.remaining(); + msg = ChannelBuffers.buffer(remaining); + + // Transfer the bytes to the new ChannelBuffer using some safe method that will also + // work with "non" heap buffers + // + // See https://github.com/netty/netty/issues/329 + msg.writeBytes(outNetBuf); outNetBuf.clear(); if (pendingWrite.outAppBuf.hasRemaining()) { @@ -850,7 +856,12 @@ public class SslHandler extends FrameDecoder if (result.bytesProduced() > 0) { outNetBuf.flip(); ChannelBuffer msg = ChannelBuffers.buffer(outNetBuf.remaining()); - msg.writeBytes(outNetBuf.array(), 0, msg.capacity()); + + // Transfer the bytes to the new ChannelBuffer using some safe method that will also + // work with "non" heap buffers + // + // See https://github.com/netty/netty/issues/329 + msg.writeBytes(outNetBuf); outNetBuf.clear(); future = future(channel); @@ -993,7 +1004,12 @@ public class SslHandler extends FrameDecoder if (outAppBuf.hasRemaining()) { ChannelBuffer frame = ctx.getChannel().getConfig().getBufferFactory().getBuffer(outAppBuf.remaining()); - frame.writeBytes(outAppBuf.array(), 0, frame.capacity()); + // Transfer the bytes to the new ChannelBuffer using some safe method that will also + // work with "non" heap buffers + // + // See https://github.com/netty/netty/issues/329 + frame.writeBytes(outAppBuf); + return frame; } else { return null; From d2ec45e57315a3146f1499d20bdfc724c4215422 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 18 May 2012 08:11:45 +0200 Subject: [PATCH 096/134] Use the correct ChannelBufferFactory when creating new ChannelBuffers. See #335 --- handler/src/main/java/io/netty/handler/ssl/SslHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index dede5b48a8..9f1b9a3a71 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -711,7 +711,7 @@ public class SslHandler extends FrameDecoder if (result.bytesProduced() > 0) { outNetBuf.flip(); int remaining = outNetBuf.remaining(); - msg = ChannelBuffers.buffer(remaining); + msg = ctx.getChannel().getConfig().getBufferFactory().getBuffer(remaining); // Transfer the bytes to the new ChannelBuffer using some safe method that will also // work with "non" heap buffers @@ -855,7 +855,8 @@ public class SslHandler extends FrameDecoder if (result.bytesProduced() > 0) { outNetBuf.flip(); - ChannelBuffer msg = ChannelBuffers.buffer(outNetBuf.remaining()); + ChannelBuffer msg = ctx.getChannel().getConfig().getBufferFactory().getBuffer(outNetBuf.remaining()); + // Transfer the bytes to the new ChannelBuffer using some safe method that will also // work with "non" heap buffers From 88d60c15c7f44c924dc0afd88c6654086b4ad14c Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 18 May 2012 08:12:42 +0200 Subject: [PATCH 097/134] Make all methods of SslBufferPool public so a subclass can be placed in another package. See #336 --- .../io/netty/handler/ssl/SslBufferPool.java | 30 +++++++++++++++++++ .../java/io/netty/handler/ssl/SslHandler.java | 12 ++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java b/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java index aef2694508..164513a968 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java @@ -89,6 +89,19 @@ public class SslBufferPool { return index * MAX_PACKET_SIZE; } + /** + * Acquire a new {@link ByteBuffer} out of the {@link SslBufferPool} + * + */ + public ByteBuffer acquireBuffer() { + return acquire(); + } + + /** + * Will get removed. Please use {@link #acquireBuffer()} + * + */ + @Deprecated synchronized ByteBuffer acquire() { if (index == 0) { return ByteBuffer.allocate(MAX_PACKET_SIZE); @@ -96,7 +109,24 @@ public class SslBufferPool { return (ByteBuffer) pool[-- index].clear(); } } + + /** + * Release a previous acquired {@link ByteBuffer} + * + * @param buffer + */ + public void releaseBuffer(ByteBuffer buffer) { + release(buffer); + } + + /** + * Will get removed. Please use {@link #releaseBuffer(ByteBuffer)} + * + * @deprecated + * + */ + @Deprecated synchronized void release(ByteBuffer buffer) { if (index < maxBufferCount) { pool[index ++] = buffer; diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 9f1b9a3a71..3247d614a4 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -670,7 +670,7 @@ public class SslHandler extends FrameDecoder ChannelFuture future = null; ChannelBuffer msg; - ByteBuffer outNetBuf = bufferPool.acquire(); + ByteBuffer outNetBuf = bufferPool.acquireBuffer(); boolean success = true; boolean offered = false; boolean needsUnwrap = false; @@ -776,7 +776,7 @@ public class SslHandler extends FrameDecoder setHandshakeFailure(channel, e); throw e; } finally { - bufferPool.release(outNetBuf); + bufferPool.releaseBuffer(outNetBuf); if (offered) { flushPendingEncryptedWrites(context); @@ -844,7 +844,7 @@ public class SslHandler extends FrameDecoder private ChannelFuture wrapNonAppData(ChannelHandlerContext ctx, Channel channel) throws SSLException { ChannelFuture future = null; - ByteBuffer outNetBuf = bufferPool.acquire(); + ByteBuffer outNetBuf = bufferPool.acquireBuffer(); SSLEngineResult result; try { @@ -915,7 +915,7 @@ public class SslHandler extends FrameDecoder setHandshakeFailure(channel, e); throw e; } finally { - bufferPool.release(outNetBuf); + bufferPool.releaseBuffer(outNetBuf); } if (future == null) { @@ -928,7 +928,7 @@ public class SslHandler extends FrameDecoder private ChannelBuffer unwrap( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, int offset, int length) throws SSLException { ByteBuffer inNetBuf = buffer.toByteBuffer(offset, length); - ByteBuffer outAppBuf = bufferPool.acquire(); + ByteBuffer outAppBuf = bufferPool.acquireBuffer(); try { boolean needsWrap = false; @@ -1019,7 +1019,7 @@ public class SslHandler extends FrameDecoder setHandshakeFailure(channel, e); throw e; } finally { - bufferPool.release(outAppBuf); + bufferPool.releaseBuffer(outAppBuf); } } From 923498de92804a7e41caf13db97475f5e8cbc5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:17:02 +0300 Subject: [PATCH 098/134] Add support for ObjectSizeEstimator --- .../traffic/ChannelTrafficShapingHandler.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java index 0c8ca4b2ba..ef5b51f7da 100644 --- a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java @@ -23,6 +23,7 @@ import io.netty.channel.ChannelStateEvent; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; +import org.jboss.netty.util.ObjectSizeEstimator; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for channel @@ -79,6 +80,69 @@ public class ChannelTrafficShapingHandler extends AbstractTrafficShapingHandler long readLimit) { super(executor, writeLimit, readLimit); } + + + /** + * @param executor + * @param checkInterval + */ + public ChannelTrafficShapingHandler(Executor executor, long checkInterval) { + super(executor, checkInterval); + } + + /** + * @param executor + */ + public ChannelTrafficShapingHandler(Executor executor) { + super(executor); + } + + /** + * @param objectSizeEstimator + * @param executor + * @param writeLimit + * @param readLimit + * @param checkInterval + */ + public ChannelTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor, + long writeLimit, long readLimit, long checkInterval) { + super(objectSizeEstimator, executor, writeLimit, readLimit, + checkInterval); + } + + /** + * @param objectSizeEstimator + * @param executor + * @param writeLimit + * @param readLimit + */ + public ChannelTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor, + long writeLimit, long readLimit) { + super(objectSizeEstimator, executor, writeLimit, readLimit); + } + + /** + * @param objectSizeEstimator + * @param executor + * @param checkInterval + */ + public ChannelTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor, + long checkInterval) { + super(objectSizeEstimator, executor, checkInterval); + } + + /** + * @param objectSizeEstimator + * @param executor + */ + public ChannelTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor) { + super(objectSizeEstimator, executor); + } + @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) From 0996bac7fffc2a4d0e68d05149b8af6ae2ed42f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:20:46 +0300 Subject: [PATCH 099/134] Add support for ObjectSizeEstimator: in order to allow special optimization if possible from user code --- .../AbstractTrafficShapingHandler.java | 125 +++++++++++++++++- 1 file changed, 119 insertions(+), 6 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index 45219228b3..12edb6d788 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -28,7 +28,9 @@ import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelHandler; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; -import io.netty.util.ExternalResourceReleasable; +import org.jboss.netty.util.DefaultObjectSizeEstimator; +import org.jboss.netty.util.ExternalResourceReleasable; +import org.jboss.netty.util.ObjectSizeEstimator; import io.netty.util.internal.ExecutorUtil; /** @@ -72,6 +74,11 @@ public abstract class AbstractTrafficShapingHandler extends */ protected TrafficCounter trafficCounter; + /** + * ObjectSizeEstimator + */ + private ObjectSizeEstimator objectSizeEstimator; + /** * Executor to associated to any TrafficCounter */ @@ -99,8 +106,9 @@ public abstract class AbstractTrafficShapingHandler extends */ final AtomicBoolean release = new AtomicBoolean(false); - private void init( + private void init(ObjectSizeEstimator newObjectSizeEstimator, Executor newExecutor, long newWriteLimit, long newReadLimit, long newCheckInterval) { + objectSizeEstimator = newObjectSizeEstimator; executor = newExecutor; writeLimit = newWriteLimit; readLimit = newReadLimit; @@ -117,6 +125,8 @@ public abstract class AbstractTrafficShapingHandler extends } /** + * Constructor using default {@link ObjectSizeEstimator} + * * @param executor * created for instance like Executors.newCachedThreadPool * @param writeLimit @@ -129,10 +139,36 @@ public abstract class AbstractTrafficShapingHandler extends */ public AbstractTrafficShapingHandler(Executor executor, long writeLimit, long readLimit, long checkInterval) { - init(executor, writeLimit, readLimit, checkInterval); + init(new DefaultObjectSizeEstimator(), executor, writeLimit, readLimit, + checkInterval); } /** + * Constructor using the specified ObjectSizeEstimator + * + * @param objectSizeEstimator + * the {@link ObjectSizeEstimator} that will be used to compute + * the size of the message + * @param executor + * created for instance like Executors.newCachedThreadPool + * @param writeLimit + * 0 or a limit in bytes/s + * @param readLimit + * 0 or a limit in bytes/s + * @param checkInterval + * The delay between two computations of performances for + * channels or 0 if no stats are to be computed + */ + public AbstractTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor, + long writeLimit, long readLimit, long checkInterval) { + init(objectSizeEstimator, executor, writeLimit, readLimit, + checkInterval); + } + + /** + * Constructor using default {@link ObjectSizeEstimator} and using default Check Interval + * * @param executor * created for instance like Executors.newCachedThreadPool * @param writeLimit @@ -142,7 +178,84 @@ public abstract class AbstractTrafficShapingHandler extends */ public AbstractTrafficShapingHandler(Executor executor, long writeLimit, long readLimit) { - init(executor, writeLimit, readLimit, DEFAULT_CHECK_INTERVAL); + init(new DefaultObjectSizeEstimator(), executor, writeLimit, readLimit, + DEFAULT_CHECK_INTERVAL); + } + + /** + * Constructor using the specified ObjectSizeEstimator and using default Check Interval + * + * @param objectSizeEstimator + * the {@link ObjectSizeEstimator} that will be used to compute + * the size of the message + * @param executor + * created for instance like Executors.newCachedThreadPool + * @param writeLimit + * 0 or a limit in bytes/s + * @param readLimit + * 0 or a limit in bytes/s + */ + public AbstractTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor, + long writeLimit, long readLimit) { + init(objectSizeEstimator, executor, writeLimit, readLimit, + DEFAULT_CHECK_INTERVAL); + } + + /** + * Constructor using default {@link ObjectSizeEstimator} and using NO LIMIT and default Check Interval + * + * @param executor + * created for instance like Executors.newCachedThreadPool + */ + public AbstractTrafficShapingHandler(Executor executor) { + init(new DefaultObjectSizeEstimator(), executor, 0, 0, + DEFAULT_CHECK_INTERVAL); + } + + /** + * Constructor using the specified ObjectSizeEstimator and using NO LIMIT and default Check Interval + * + * @param objectSizeEstimator + * the {@link ObjectSizeEstimator} that will be used to compute + * the size of the message + * @param executor + * created for instance like Executors.newCachedThreadPool + */ + public AbstractTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor) { + init(objectSizeEstimator, executor, 0, 0, DEFAULT_CHECK_INTERVAL); + } + + /** + * Constructor using default {@link ObjectSizeEstimator} and using NO LIMIT + * + * @param executor + * created for instance like Executors.newCachedThreadPool + * @param checkInterval + * The delay between two computations of performances for + * channels or 0 if no stats are to be computed + */ + public AbstractTrafficShapingHandler(Executor executor, long checkInterval) { + init(new DefaultObjectSizeEstimator(), executor, 0, 0, checkInterval); + } + + /** + * Constructor using the specified ObjectSizeEstimator and using NO LIMIT + * + * @param objectSizeEstimator + * the {@link ObjectSizeEstimator} that will be used to compute + * the size of the message + * @param executor + * created for instance like Executors.newCachedThreadPool + * @param checkInterval + * The delay between two computations of performances for + * channels or 0 if no stats are to be computed + */ + public AbstractTrafficShapingHandler( + ObjectSizeEstimator objectSizeEstimator, Executor executor, + long checkInterval) { + init(objectSizeEstimator, executor, 0, 0, checkInterval); } /** @@ -255,7 +368,7 @@ public abstract class AbstractTrafficShapingHandler extends throws Exception { try { long curtime = System.currentTimeMillis(); - long size = ((ChannelBuffer) arg1.getMessage()).readableBytes(); + long size = objectSizeEstimator.estimateSize(arg1.getMessage()); if (trafficCounter != null) { trafficCounter.bytesRecvFlowControl(arg0, size); if (readLimit == 0) { @@ -315,7 +428,7 @@ public abstract class AbstractTrafficShapingHandler extends throws Exception { try { long curtime = System.currentTimeMillis(); - long size = ((ChannelBuffer) arg1.getMessage()).readableBytes(); + long size = objectSizeEstimator.estimateSize(arg1.getMessage()); if (trafficCounter != null) { trafficCounter.bytesWriteFlowControl(size); if (writeLimit == 0) { From a1a60ec5b6d585e1503061f8435be27fd269d5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:23:11 +0300 Subject: [PATCH 100/134] Add support for ObjectSizeEstimator --- .../traffic/GlobalTrafficShapingHandler.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java index 73fc97df6d..dc9a6d2091 100644 --- a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java @@ -21,6 +21,7 @@ import io.netty.channel.ChannelHandler.Sharable; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; +import org.jboss.netty.util.ObjectSizeEstimator; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for global @@ -91,4 +92,68 @@ public class GlobalTrafficShapingHandler extends AbstractTrafficShapingHandler { super(executor, writeLimit, readLimit); createGlobalTrafficCounter(); } + /** + * @param executor + * @param checkInterval + */ + public GlobalTrafficShapingHandler(Executor executor, long checkInterval) { + super(executor, checkInterval); + createGlobalTrafficCounter(); + } + + /** + * @param executor + */ + public GlobalTrafficShapingHandler(Executor executor) { + super(executor); + createGlobalTrafficCounter(); + } + + /** + * @param objectSizeEstimator + * @param executor + * @param writeLimit + * @param readLimit + * @param checkInterval + */ + public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, + Executor executor, long writeLimit, long readLimit, + long checkInterval) { + super(objectSizeEstimator, executor, writeLimit, readLimit, + checkInterval); + createGlobalTrafficCounter(); + } + + /** + * @param objectSizeEstimator + * @param executor + * @param writeLimit + * @param readLimit + */ + public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, + Executor executor, long writeLimit, long readLimit) { + super(objectSizeEstimator, executor, writeLimit, readLimit); + createGlobalTrafficCounter(); + } + + /** + * @param objectSizeEstimator + * @param executor + * @param checkInterval + */ + public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, + Executor executor, long checkInterval) { + super(objectSizeEstimator, executor, checkInterval); + createGlobalTrafficCounter(); + } + + /** + * @param objectSizeEstimator + * @param executor + */ + public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, + Executor executor) { + super(objectSizeEstimator, executor); + createGlobalTrafficCounter(); + } } From 7d4a276ab096d6bd892844a66f02c06218fd480d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:26:21 +0300 Subject: [PATCH 101/134] Fix private class to static private class (dynamicity is not necessary there) --- .../main/java/io/netty/handler/traffic/TrafficCounter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java index 4904d36840..6976ac08c5 100644 --- a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java +++ b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java @@ -117,7 +117,7 @@ public class TrafficCounter { /** * Class to implement monitoring at fix delay */ - private class TrafficMonitoring implements Runnable { + private static class TrafficMonitoring implements Runnable { /** * The associated TrafficShapingHandler */ @@ -145,8 +145,8 @@ public class TrafficCounter { @Override public void run() { try { - Thread.currentThread().setName(name); - for (; monitorActive.get();) { + Thread.currentThread().setName(counter.name); + for (; counter.monitorActive.get();) { long check = counter.checkInterval.get(); if (check > 0) { Thread.sleep(check); From 51debe12873fd2f08c8e7443d882219e2ef162b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:32:36 +0300 Subject: [PATCH 102/134] import fix --- .../handler/traffic/AbstractTrafficShapingHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index 12edb6d788..7da4b29a96 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -28,9 +28,9 @@ import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelHandler; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; -import org.jboss.netty.util.DefaultObjectSizeEstimator; -import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.ObjectSizeEstimator; +import io.netty.util.DefaultObjectSizeEstimator; +import io.netty.util.ExternalResourceReleasable; +import io.netty.util.ObjectSizeEstimator; import io.netty.util.internal.ExecutorUtil; /** From 9c2262716fdcb6fdfee1893c496d76159441621d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:32:59 +0300 Subject: [PATCH 103/134] Import fix --- .../io/netty/handler/traffic/ChannelTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java index ef5b51f7da..4fc4151124 100644 --- a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java @@ -23,7 +23,7 @@ import io.netty.channel.ChannelStateEvent; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; -import org.jboss.netty.util.ObjectSizeEstimator; +import io.netty.util.ObjectSizeEstimator; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for channel From 1afb209010d7db97f2bc76fdad44ff8ca569c755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 15:33:23 +0300 Subject: [PATCH 104/134] import fix --- .../io/netty/handler/traffic/GlobalTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java index dc9a6d2091..06055e2cb9 100644 --- a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java @@ -21,7 +21,7 @@ import io.netty.channel.ChannelHandler.Sharable; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; -import org.jboss.netty.util.ObjectSizeEstimator; +import io.netty.util.ObjectSizeEstimator; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for global From bca791af4c5b55c1e0daf7847704039b12ff7e0a Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 18 May 2012 15:13:13 +0200 Subject: [PATCH 105/134] Fix compile errors which were introduced by #338 --- .../netty/handler/traffic/AbstractTrafficShapingHandler.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index 7da4b29a96..5c8ec1bf1f 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -18,7 +18,6 @@ package io.netty.handler.traffic; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; -import io.netty.buffer.ChannelBuffer; import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelHandlerContext; @@ -26,11 +25,11 @@ import io.netty.channel.ChannelState; import io.netty.channel.ChannelStateEvent; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelHandler; +import io.netty.handler.execution.ObjectSizeEstimator; +import io.netty.handler.execution.DefaultObjectSizeEstimator; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; -import io.netty.util.DefaultObjectSizeEstimator; import io.netty.util.ExternalResourceReleasable; -import io.netty.util.ObjectSizeEstimator; import io.netty.util.internal.ExecutorUtil; /** From a5fc2d82a5f1eddf6efd20219bd9bc9905fcb9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 16:37:46 +0300 Subject: [PATCH 106/134] Same fix than other for correct import --- .../io/netty/handler/traffic/ChannelTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java index 4fc4151124..4609c2ad36 100644 --- a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java @@ -23,7 +23,7 @@ import io.netty.channel.ChannelStateEvent; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; -import io.netty.util.ObjectSizeEstimator; +import io.netty.handler.execution.ObjectSizeEstimator; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for channel From e54662f7d0c31c4679ea0e06cc00464ca7978bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Fri, 18 May 2012 16:38:13 +0300 Subject: [PATCH 107/134] Same fix than other for correct import --- .../io/netty/handler/traffic/GlobalTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java index 06055e2cb9..a9a96a436c 100644 --- a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java @@ -21,7 +21,7 @@ import io.netty.channel.ChannelHandler.Sharable; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; -import io.netty.util.ObjectSizeEstimator; +import io.netty.handler.execution.ObjectSizeEstimator; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for global From ddb7d75c96806b0bac1fea3d8a17b8a9c982a18d Mon Sep 17 00:00:00 2001 From: Jeff Pinner Date: Fri, 18 May 2012 13:44:23 -0700 Subject: [PATCH 108/134] Make SslBufferPool an interface --- .../handler/ssl/DefaultSslBufferPool.java | 93 ++++++++++++++++ .../io/netty/handler/ssl/SslBufferPool.java | 105 ++---------------- .../java/io/netty/handler/ssl/SslHandler.java | 2 +- 3 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java diff --git a/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java b/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java new file mode 100644 index 0000000000..6fe44d5894 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java @@ -0,0 +1,93 @@ +/* + * Copyright 2011 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import java.nio.ByteBuffer; + +import javax.net.ssl.SSLEngine; + +public class DefaultSslBufferPool implements SslBufferPool { + + private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE * 1024; + + private final ByteBuffer[] pool; + private final int maxBufferCount; + private int index; + + /** + * Creates a new buffer pool whose size is {@code 18113536}, which can + * hold {@code 1024} buffers. + */ + public DefaultSslBufferPool() { + this(DEFAULT_POOL_SIZE); + } + + /** + * Creates a new buffer pool. + * + * @param maxPoolSize the maximum number of bytes that this pool can hold + */ + public DefaultSslBufferPool(int maxPoolSize) { + if (maxPoolSize <= 0) { + throw new IllegalArgumentException("maxPoolSize: " + maxPoolSize); + } + + int maxBufferCount = maxPoolSize / MAX_PACKET_SIZE; + if (maxPoolSize % MAX_PACKET_SIZE != 0) { + maxBufferCount ++; + } + + pool = new ByteBuffer[maxBufferCount]; + this.maxBufferCount = maxBufferCount; + } + + /** + * Returns the maximum size of this pool in byte unit. The returned value + * can be somewhat different from what was specified in the constructor. + */ + public int getMaxPoolSize() { + return maxBufferCount * MAX_PACKET_SIZE; + } + + /** + * Returns the number of bytes which were allocated but have not been + * acquired yet. You can estimate how optimal the specified maximum pool + * size is from this value. If it keeps returning {@code 0}, it means the + * pool is getting exhausted. If it keeps returns a unnecessarily big + * value, it means the pool is wasting the heap space. + */ + public synchronized int getUnacquiredPoolSize() { + return index * MAX_PACKET_SIZE; + } + + public ByteBuffer acquireBuffer() { + synchronized { + if (index == 0) { + return ByteBuffer.allocate(MAX_PACKET_SIZE); + } else { + return (ByteBuffer) pool[-- index].clear(); + } + } + } + + public void releaseBuffer(ByteBuffer buffer) { + synchronized { + if (index < maxBufferCount) { + pool[index ++] = buffer; + } + } + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java b/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java index 164513a968..6cd3e18de1 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslBufferPool.java @@ -17,15 +17,10 @@ package io.netty.handler.ssl; import java.nio.ByteBuffer; -import javax.net.ssl.SSLEngine; - /** * A {@link ByteBuffer} pool dedicated for {@link SslHandler} performance * improvement. - *

- * In most cases, you won't need to create a new pool instance because - * {@link SslHandler} has a default pool instance internally. - *

+ * * The reason why {@link SslHandler} requires a buffer pool is because the * current {@link SSLEngine} implementation always requires a 17KiB buffer for * every 'wrap' and 'unwrap' operation. In most cases, the actual size of the @@ -33,103 +28,21 @@ import javax.net.ssl.SSLEngine; * buffer for every 'wrap' and 'unwrap' operation wastes a lot of memory * bandwidth, resulting in the application performance degradation. */ -public class SslBufferPool { +public interface SslBufferPool { - // Add 1024 as a room for compressed data and another 1024 for Apache Harmony compatibility. - private static final int MAX_PACKET_SIZE = 16665 + 2048; - private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE * 1024; - - private final ByteBuffer[] pool; - private final int maxBufferCount; - private int index; - - /** - * Creates a new buffer pool whose size is {@code 18113536}, which can - * hold {@code 1024} buffers. - */ - public SslBufferPool() { - this(DEFAULT_POOL_SIZE); - } - - /** - * Creates a new buffer pool. - * - * @param maxPoolSize the maximum number of bytes that this pool can hold - */ - public SslBufferPool(int maxPoolSize) { - if (maxPoolSize <= 0) { - throw new IllegalArgumentException("maxPoolSize: " + maxPoolSize); - } - - int maxBufferCount = maxPoolSize / MAX_PACKET_SIZE; - if (maxPoolSize % MAX_PACKET_SIZE != 0) { - maxBufferCount ++; - } - - pool = new ByteBuffer[maxBufferCount]; - this.maxBufferCount = maxBufferCount; - } - - /** - * Returns the maximum size of this pool in byte unit. The returned value - * can be somewhat different from what was specified in the constructor. - */ - public int getMaxPoolSize() { - return maxBufferCount * MAX_PACKET_SIZE; - } - - /** - * Returns the number of bytes which were allocated but have not been - * acquired yet. You can estimate how optimal the specified maximum pool - * size is from this value. If it keeps returning {@code 0}, it means the - * pool is getting exhausted. If it keeps returns a unnecessarily big - * value, it means the pool is wasting the heap space. - */ - public synchronized int getUnacquiredPoolSize() { - return index * MAX_PACKET_SIZE; - } + // Returned buffers must be large enough to accomodate the maximum SSL record size. + // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256) + int MAX_PACKET_SIZE = 18713; /** * Acquire a new {@link ByteBuffer} out of the {@link SslBufferPool} - * */ - public ByteBuffer acquireBuffer() { - return acquire(); - } - - /** - * Will get removed. Please use {@link #acquireBuffer()} - * - */ - @Deprecated - synchronized ByteBuffer acquire() { - if (index == 0) { - return ByteBuffer.allocate(MAX_PACKET_SIZE); - } else { - return (ByteBuffer) pool[-- index].clear(); - } - } - + ByteBuffer acquireBuffer(); /** - * Release a previous acquired {@link ByteBuffer} - * + * Release a previously acquired {@link ByteBuffer} + * * @param buffer */ - public void releaseBuffer(ByteBuffer buffer) { - release(buffer); - } - - /** - * Will get removed. Please use {@link #releaseBuffer(ByteBuffer)} - * - * @deprecated - * - */ - @Deprecated - synchronized void release(ByteBuffer buffer) { - if (index < maxBufferCount) { - pool[index ++] = buffer; - } - } + void releaseBuffer(ByteBuffer buffer); } diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 3247d614a4..5a55075188 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -170,7 +170,7 @@ public class SslHandler extends FrameDecoder */ public static synchronized SslBufferPool getDefaultBufferPool() { if (defaultBufferPool == null) { - defaultBufferPool = new SslBufferPool(); + defaultBufferPool = new DefaultSslBufferPool(); } return defaultBufferPool; } From ed538209e588e8ba7e94371a801067d73eb2ab74 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 19 May 2012 13:19:24 +0200 Subject: [PATCH 109/134] Fix syntax. See #342 --- .../handler/ssl/DefaultSslBufferPool.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java b/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java index 6fe44d5894..bd3e155814 100644 --- a/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java +++ b/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java @@ -73,21 +73,17 @@ public class DefaultSslBufferPool implements SslBufferPool { return index * MAX_PACKET_SIZE; } - public ByteBuffer acquireBuffer() { - synchronized { - if (index == 0) { - return ByteBuffer.allocate(MAX_PACKET_SIZE); - } else { - return (ByteBuffer) pool[-- index].clear(); - } + public synchronized ByteBuffer acquireBuffer() { + if (index == 0) { + return ByteBuffer.allocate(MAX_PACKET_SIZE); + } else { + return (ByteBuffer) pool[-- index].clear(); } } - public void releaseBuffer(ByteBuffer buffer) { - synchronized { - if (index < maxBufferCount) { - pool[index ++] = buffer; - } + public synchronized void releaseBuffer(ByteBuffer buffer) { + if (index < maxBufferCount) { + pool[index ++] = buffer; } } } From 2320a5919f49c9e5705215b4273db291ea40d1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sat, 19 May 2012 14:54:59 +0300 Subject: [PATCH 110/134] Proposal for fix related to 1rst issue of #345 => (getTimeToWait /10)*10 (see http://www.javamex.com/tutorials/threads/sleep_issues.shtml) --- .../io/netty/handler/traffic/AbstractTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index 7da4b29a96..b0c6fcfda9 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -360,7 +360,7 @@ public abstract class AbstractTrafficShapingHandler extends // Time is too short, so just lets continue return 0; } - return bytes * 1000 / limit - interval; + return ((bytes * 1000 / limit - interval)/10)*10; } @Override From bc540d5ee1b1c865bc442d08d3991a800180ca5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sat, 19 May 2012 14:59:52 +0300 Subject: [PATCH 111/134] Proposal for fix related to 1rst issue of #345 => in configure (newcheckInterval/10)*10 (see http://www.javamex.com/tutorials/threads/sleep_issues.shtml) --- .../main/java/io/netty/handler/traffic/TrafficCounter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java index 6976ac08c5..d8cfdb3fb6 100644 --- a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java +++ b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java @@ -248,9 +248,10 @@ public class TrafficCounter { * @param newcheckInterval */ public void configure(long newcheckInterval) { - if (checkInterval.get() != newcheckInterval) { - checkInterval.set(newcheckInterval); - if (newcheckInterval <= 0) { + long newInterval = (newcheckInterval/10)*10; + if (checkInterval.get() != newInterval) { + checkInterval.set(newInterval); + if (newInterval <= 0) { stop(); // No more active monitoring lastTime.set(System.currentTimeMillis()); From 3ca2a53e91b4a56f632f592ee266bc8119b1ebbd Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 19 May 2012 16:35:22 +0200 Subject: [PATCH 112/134] Add a replace(..) method to FrameDecoder and also to ReplayDecoder as it now extend FrameDecoder. This also fix #332 --- .../WebSocketClientHandshaker00.java | 6 +- .../WebSocketClientHandshaker13.java | 5 +- .../handler/codec/frame/FrameDecoder.java | 89 +++++++++++++- .../codec/replay/ReplayingDecoder.java | 110 ++++-------------- 4 files changed, 113 insertions(+), 97 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index 0797e9132f..37c600dd46 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -240,10 +240,10 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); setActualSubprotocol(subprotocol); - channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", - new WebSocket00FrameDecoder(this.getMaxFramePayloadLength())); - setHandshakeComplete(); + + channel.getPipeline().get(HttpResponseDecoder.class).replace("ws-decoder", + new WebSocket00FrameDecoder(this.getMaxFramePayloadLength())); } private String insertRandomCharacters(String key) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index a4a079f3d3..e0ddc9809f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -224,9 +224,10 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); setActualSubprotocol(subprotocol); - channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", + setHandshakeComplete(); + + channel.getPipeline().get(HttpResponseDecoder.class).replace("ws-decoder", new WebSocket13FrameDecoder(false, allowExtensions, this.getMaxFramePayloadLength())); - setHandshakeComplete(); } } diff --git a/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java index 3e1be81a8d..7d831d29ce 100644 --- a/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/frame/FrameDecoder.java @@ -28,9 +28,9 @@ import io.netty.channel.ChannelStateEvent; import io.netty.channel.ChannelUpstreamHandler; import io.netty.channel.Channels; import io.netty.channel.ExceptionEvent; +import io.netty.channel.LifeCycleAwareChannelHandler; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; -import io.netty.handler.codec.replay.ReplayingDecoder; /** * Decodes the received {@link ChannelBuffer}s into a meaningful frame object. @@ -174,11 +174,12 @@ import io.netty.handler.codec.replay.ReplayingDecoder; * * @apiviz.landmark */ -public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { +public abstract class FrameDecoder extends SimpleChannelUpstreamHandler implements LifeCycleAwareChannelHandler { private final boolean unfold; - private ChannelBuffer cumulation; - + protected ChannelBuffer cumulation; + private volatile ChannelHandlerContext ctx; + protected FrameDecoder() { this(false); } @@ -335,7 +336,7 @@ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { } } - private void unfoldAndFireMessageReceived(ChannelHandlerContext context, SocketAddress remoteAddress, Object result) { + protected final void unfoldAndFireMessageReceived(ChannelHandlerContext context, SocketAddress remoteAddress, Object result) { if (unfold) { if (result instanceof Object[]) { for (Object r: (Object[]) result) { @@ -353,7 +354,11 @@ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { } } - private void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) + /** + * Gets called on {@link #channelDisconnected(ChannelHandlerContext, ChannelStateEvent)} and {@link #channelClosed(ChannelHandlerContext, ChannelStateEvent)} + * + */ + protected void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { try { ChannelBuffer cumulation = this.cumulation; @@ -392,4 +397,76 @@ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler { ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); return factory.getBuffer(Math.max(minimumCapacity, 256)); } + + /** + * Replace this {@link FrameDecoder} in the {@link ChannelPipeline} with the given {@link ChannelHandler}. All + * remaining bytes in the {@link ChannelBuffer} will get send to the new {@link ChannelHandler} that was used + * as replacement + * + */ + public void replace(String handlerName, ChannelHandler handler) { + if (ctx == null) { + throw new IllegalStateException("Replace cann only be called once the FrameDecoder is added to the ChannelPipeline"); + } + ChannelPipeline pipeline = ctx.getPipeline(); + pipeline.addAfter(ctx.getName(), handlerName, handler); + + try { + if (cumulation != null) { + Channels.fireMessageReceived(ctx, cumulation.readBytes(actualReadableBytes())); + } + } finally { + pipeline.remove(this); + } + + } + + /** + * Returns the actual number of readable bytes in the internal cumulative + * buffer of this decoder. You usually do not need to rely on this value + * to write a decoder. Use it only when you muse use it at your own risk. + * This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}. + */ + protected int actualReadableBytes() { + return internalBuffer().readableBytes(); + } + + + + /** + * Returns the internal cumulative buffer of this decoder. You usually + * do not need to access the internal buffer directly to write a decoder. + * Use it only when you must use it at your own risk. + */ + protected ChannelBuffer internalBuffer() { + ChannelBuffer buf = this.cumulation; + if (buf == null) { + return ChannelBuffers.EMPTY_BUFFER; + } + return buf; + } + + @Override + public void beforeAdd(ChannelHandlerContext ctx) throws Exception { + this.ctx = ctx; + } + + @Override + public void afterAdd(ChannelHandlerContext ctx) throws Exception { + // Nothing to do.. + + } + + @Override + public void beforeRemove(ChannelHandlerContext ctx) throws Exception { + // Nothing to do.. + + } + + @Override + public void afterRemove(ChannelHandlerContext ctx) throws Exception { + // Nothing to do.. + + } + } diff --git a/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java b/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java index 7bbd8e5ade..6b2ea17225 100644 --- a/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/replay/ReplayingDecoder.java @@ -15,22 +15,18 @@ */ package io.netty.handler.codec.replay; -import java.net.SocketAddress; - import io.netty.buffer.ChannelBuffer; -import io.netty.buffer.ChannelBufferFactory; import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelStateEvent; -import io.netty.channel.Channels; -import io.netty.channel.ExceptionEvent; import io.netty.channel.MessageEvent; -import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.handler.codec.frame.FrameDecoder; +import java.net.SocketAddress; + /** * A specialized variation of {@link FrameDecoder} which enables implementation * of a non-blocking decoder in the blocking I/O paradigm. @@ -285,11 +281,9 @@ import io.netty.handler.codec.frame.FrameDecoder; * @apiviz.has io.netty.handler.codec.replay.UnreplayableOperationException oneway - - throws */ public abstract class ReplayingDecoder> - extends SimpleChannelUpstreamHandler { + extends FrameDecoder { - private ChannelBuffer cumulation; - private final boolean unfold; private ReplayingDecoderBuffer replayable; private T state; private int checkpoint; @@ -315,8 +309,8 @@ public abstract class ReplayingDecoder> } protected ReplayingDecoder(T initialState, boolean unfold) { + super(unfold); this.state = initialState; - this.unfold = unfold; } /** @@ -358,29 +352,6 @@ public abstract class ReplayingDecoder> return oldState; } - /** - * Returns the actual number of readable bytes in the internal cumulative - * buffer of this decoder. You usually do not need to rely on this value - * to write a decoder. Use it only when you muse use it at your own risk. - * This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}. - */ - protected int actualReadableBytes() { - return internalBuffer().readableBytes(); - } - - /** - * Returns the internal cumulative buffer of this decoder. You usually - * do not need to access the internal buffer directly to write a decoder. - * Use it only when you must use it at your own risk. - */ - protected ChannelBuffer internalBuffer() { - ChannelBuffer buf = this.cumulation; - if (buf == null) { - return ChannelBuffers.EMPTY_BUFFER; - } - return buf; - } - /** * Decodes the received packets so far into a frame. * @@ -416,6 +387,21 @@ public abstract class ReplayingDecoder> return decode(ctx, channel, buffer, state); } + /** + * Calls {@link #decode(ChannelHandlerContext, Channel, ChannelBuffer, Enum)}. This method should be never used by {@link ReplayingDecoder} itself. + * But to be safe we should handle it anyway + */ + @Override + protected final Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + return decode(ctx, channel, buffer, state); + } + + @Override + protected final Object decodeLast( + ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + return decodeLast(ctx, channel, buffer, state); + } + @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { @@ -520,24 +506,6 @@ public abstract class ReplayingDecoder> } } - @Override - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - cleanup(ctx, e); - } - - @Override - public void channelClosed(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - cleanup(ctx, e); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) - throws Exception { - ctx.sendUpstream(e); - } - private void callDecode(ChannelHandlerContext context, Channel channel, ChannelBuffer input, ChannelBuffer replayableInput, SocketAddress remoteAddress) throws Exception { while (input.readable()) { int oldReaderIndex = checkpoint = input.readerIndex(); @@ -580,30 +548,12 @@ public abstract class ReplayingDecoder> } // A successful decode - unfoldAndFireMessageReceived(context, result, remoteAddress); + unfoldAndFireMessageReceived(context, remoteAddress, result); } } - private void unfoldAndFireMessageReceived( - ChannelHandlerContext context, Object result, SocketAddress remoteAddress) { - if (unfold) { - if (result instanceof Object[]) { - for (Object r: (Object[]) result) { - Channels.fireMessageReceived(context, r, remoteAddress); - } - } else if (result instanceof Iterable) { - for (Object r: (Iterable) result) { - Channels.fireMessageReceived(context, r, remoteAddress); - } - } else { - Channels.fireMessageReceived(context, result, remoteAddress); - } - } else { - Channels.fireMessageReceived(context, result, remoteAddress); - } - } - - private void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) + @Override + protected void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { try { ChannelBuffer cumulation = this.cumulation; @@ -626,7 +576,7 @@ public abstract class ReplayingDecoder> // notify a user that the connection was closed explicitly. Object partiallyDecoded = decodeLast(ctx, e.getChannel(), replayable, state); if (partiallyDecoded != null) { - unfoldAndFireMessageReceived(ctx, partiallyDecoded, null); + unfoldAndFireMessageReceived(ctx, null, partiallyDecoded); } } catch (ReplayError replay) { // Ignore @@ -635,17 +585,5 @@ public abstract class ReplayingDecoder> ctx.sendUpstream(e); } } - - /** - * Create a new {@link ChannelBuffer} which is used for the cumulation. - * Sub-classes may override this. - * - * @param ctx {@link ChannelHandlerContext} for this handler - * @return buffer the {@link ChannelBuffer} which is used for cumulation - */ - protected ChannelBuffer newCumulationBuffer( - ChannelHandlerContext ctx, int minimumCapacity) { - ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); - return factory.getBuffer(Math.max(minimumCapacity, 256)); - } + } From b6abefb5b803c82c0fd85409f614fc45d10eaba9 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 19 May 2012 17:08:45 +0200 Subject: [PATCH 113/134] Add a replace(..) method to FrameDecoder and also to ReplayDecoder as it now extend FrameDecoder. This also fix #332 --- .../codec/http/websocketx/WebSocketClientHandshaker08.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 67bbca0c30..0dd1df9bd7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -227,9 +227,10 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { String subprotocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); setActualSubprotocol(subprotocol); + setHandshakeComplete(); + channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket08FrameDecoder(false, allowExtensions, this.getMaxFramePayloadLength())); - setHandshakeComplete(); } } From 7018b8453f0a910438dde28b327aa57b26e819fd Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 19 May 2012 17:10:52 +0200 Subject: [PATCH 114/134] Cleanup --- .../main/java/io/netty/handler/ssl/DefaultSslBufferPool.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java b/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java index bd3e155814..7a41d4a607 100644 --- a/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java +++ b/handler/src/main/java/io/netty/handler/ssl/DefaultSslBufferPool.java @@ -17,8 +17,6 @@ package io.netty.handler.ssl; import java.nio.ByteBuffer; -import javax.net.ssl.SSLEngine; - public class DefaultSslBufferPool implements SslBufferPool { private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE * 1024; @@ -73,6 +71,7 @@ public class DefaultSslBufferPool implements SslBufferPool { return index * MAX_PACKET_SIZE; } + @Override public synchronized ByteBuffer acquireBuffer() { if (index == 0) { return ByteBuffer.allocate(MAX_PACKET_SIZE); @@ -81,6 +80,7 @@ public class DefaultSslBufferPool implements SslBufferPool { } } + @Override public synchronized void releaseBuffer(ByteBuffer buffer) { if (index < maxBufferCount) { pool[index ++] = buffer; From 54c97d07202d98eac734beacf885579973b515b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 16:36:45 +0300 Subject: [PATCH 115/134] Same fix than in version 3.5 for Master branch (refer to issue #345) Will be proposed once the one in 3.5 will be validated --- .../AbstractTrafficShapingHandler.java | 209 +++++++++--------- 1 file changed, 104 insertions(+), 105 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index d97fb5a9a1..bc7b9d406a 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -15,7 +15,7 @@ */ package io.netty.handler.traffic; -import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import io.netty.channel.Channel; @@ -30,7 +30,9 @@ import io.netty.handler.execution.DefaultObjectSizeEstimator; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import io.netty.util.ExternalResourceReleasable; -import io.netty.util.internal.ExecutorUtil; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; /** * AbstractTrafficShapingHandler allows to limit the global bandwidth @@ -41,6 +43,10 @@ import io.netty.util.internal.ExecutorUtil; * the method doAccounting of this handler.
*
* + * An {@link ObjectSizeEstimator} can be passed at construction to specify what + * is the size of the object to be read or write accordingly to the type of + * object. If not specified, it will used the {@link DefaultObjectSizeEstimator} implementation.

+ * * If you want for any particular reasons to stop the monitoring (accounting) or to change * the read/write limit or the check interval, several methods allow that for you:
*

    @@ -79,10 +85,14 @@ public abstract class AbstractTrafficShapingHandler extends private ObjectSizeEstimator objectSizeEstimator; /** - * Executor to associated to any TrafficCounter + * Timer to associated to any TrafficCounter */ - protected Executor executor; - + protected Timer timer; + /** + * used in releaseExternalResources() to cancel the timer + */ + volatile private Timeout timeout = null; + /** * Limit in B/s to apply to write */ @@ -105,15 +115,16 @@ public abstract class AbstractTrafficShapingHandler extends */ final AtomicBoolean release = new AtomicBoolean(false); - private void init(ObjectSizeEstimator newObjectSizeEstimator, - Executor newExecutor, long newWriteLimit, long newReadLimit, long newCheckInterval) { - objectSizeEstimator = newObjectSizeEstimator; - executor = newExecutor; - writeLimit = newWriteLimit; - readLimit = newReadLimit; - checkInterval = newCheckInterval; - //logger.info("TSH: "+writeLimit+":"+readLimit+":"+checkInterval+":"+isPerChannel()); - } + private void init(ObjectSizeEstimator newObjectSizeEstimator, + Timer newTimer, long newWriteLimit, long newReadLimit, + long newCheckInterval) { + objectSizeEstimator = newObjectSizeEstimator; + timer = newTimer; + writeLimit = newWriteLimit; + readLimit = newReadLimit; + checkInterval = newCheckInterval; + //logger.warn("TSH: "+writeLimit+":"+readLimit+":"+checkInterval); + } /** * @@ -126,8 +137,8 @@ public abstract class AbstractTrafficShapingHandler extends /** * Constructor using default {@link ObjectSizeEstimator} * - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) * @param writeLimit * 0 or a limit in bytes/s * @param readLimit @@ -136,10 +147,9 @@ public abstract class AbstractTrafficShapingHandler extends * The delay between two computations of performances for * channels or 0 if no stats are to be computed */ - public AbstractTrafficShapingHandler(Executor executor, long writeLimit, + public AbstractTrafficShapingHandler(Timer timer, long writeLimit, long readLimit, long checkInterval) { - init(new DefaultObjectSizeEstimator(), executor, writeLimit, readLimit, - checkInterval); + init(new DefaultObjectSizeEstimator(), timer, writeLimit, readLimit, checkInterval); } /** @@ -148,8 +158,8 @@ public abstract class AbstractTrafficShapingHandler extends * @param objectSizeEstimator * the {@link ObjectSizeEstimator} that will be used to compute * the size of the message - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) * @param writeLimit * 0 or a limit in bytes/s * @param readLimit @@ -159,26 +169,24 @@ public abstract class AbstractTrafficShapingHandler extends * channels or 0 if no stats are to be computed */ public AbstractTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor, + ObjectSizeEstimator objectSizeEstimator, Timer timer, long writeLimit, long readLimit, long checkInterval) { - init(objectSizeEstimator, executor, writeLimit, readLimit, - checkInterval); + init(objectSizeEstimator, timer, writeLimit, readLimit, checkInterval); } /** * Constructor using default {@link ObjectSizeEstimator} and using default Check Interval * - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) * @param writeLimit * 0 or a limit in bytes/s * @param readLimit * 0 or a limit in bytes/s */ - public AbstractTrafficShapingHandler(Executor executor, long writeLimit, + public AbstractTrafficShapingHandler(Timer timer, long writeLimit, long readLimit) { - init(new DefaultObjectSizeEstimator(), executor, writeLimit, readLimit, - DEFAULT_CHECK_INTERVAL); + init(new DefaultObjectSizeEstimator(), timer, writeLimit, readLimit, DEFAULT_CHECK_INTERVAL); } /** @@ -187,29 +195,27 @@ public abstract class AbstractTrafficShapingHandler extends * @param objectSizeEstimator * the {@link ObjectSizeEstimator} that will be used to compute * the size of the message - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) * @param writeLimit * 0 or a limit in bytes/s * @param readLimit * 0 or a limit in bytes/s */ public AbstractTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor, + ObjectSizeEstimator objectSizeEstimator, Timer timer, long writeLimit, long readLimit) { - init(objectSizeEstimator, executor, writeLimit, readLimit, - DEFAULT_CHECK_INTERVAL); + init(objectSizeEstimator, timer, writeLimit, readLimit, DEFAULT_CHECK_INTERVAL); } /** * Constructor using default {@link ObjectSizeEstimator} and using NO LIMIT and default Check Interval * - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) */ - public AbstractTrafficShapingHandler(Executor executor) { - init(new DefaultObjectSizeEstimator(), executor, 0, 0, - DEFAULT_CHECK_INTERVAL); + public AbstractTrafficShapingHandler(Timer timer) { + init(new DefaultObjectSizeEstimator(), timer, 0, 0, DEFAULT_CHECK_INTERVAL); } /** @@ -218,25 +224,25 @@ public abstract class AbstractTrafficShapingHandler extends * @param objectSizeEstimator * the {@link ObjectSizeEstimator} that will be used to compute * the size of the message - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) */ public AbstractTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor) { - init(objectSizeEstimator, executor, 0, 0, DEFAULT_CHECK_INTERVAL); + ObjectSizeEstimator objectSizeEstimator, Timer timer) { + init(objectSizeEstimator, timer, 0, 0, DEFAULT_CHECK_INTERVAL); } /** * Constructor using default {@link ObjectSizeEstimator} and using NO LIMIT * - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) * @param checkInterval * The delay between two computations of performances for * channels or 0 if no stats are to be computed */ - public AbstractTrafficShapingHandler(Executor executor, long checkInterval) { - init(new DefaultObjectSizeEstimator(), executor, 0, 0, checkInterval); + public AbstractTrafficShapingHandler(Timer timer, long checkInterval) { + init(new DefaultObjectSizeEstimator(), timer, 0, 0, checkInterval); } /** @@ -245,20 +251,24 @@ public abstract class AbstractTrafficShapingHandler extends * @param objectSizeEstimator * the {@link ObjectSizeEstimator} that will be used to compute * the size of the message - * @param executor - * created for instance like Executors.newCachedThreadPool + * @param timer + * created once for instance like HashedWheelTimer(10, TimeUnit.MILLISECONDS, 1024) * @param checkInterval * The delay between two computations of performances for * channels or 0 if no stats are to be computed */ public AbstractTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor, + ObjectSizeEstimator objectSizeEstimator, Timer timer, long checkInterval) { - init(objectSizeEstimator, executor, 0, 0, checkInterval); + init(objectSizeEstimator, timer, 0, 0, checkInterval); } /** * Change the underlying limitations and check interval. + * + * @param newWriteLimit + * @param newReadLimit + * @param newCheckInterval */ public void configure(long newWriteLimit, long newReadLimit, long newCheckInterval) { @@ -268,17 +278,22 @@ public abstract class AbstractTrafficShapingHandler extends /** * Change the underlying limitations. + * + * @param newWriteLimit + * @param newReadLimit */ public void configure(long newWriteLimit, long newReadLimit) { writeLimit = newWriteLimit; readLimit = newReadLimit; if (trafficCounter != null) { - trafficCounter.resetAccounting(System.currentTimeMillis() + 1); + trafficCounter.resetAccounting(System.currentTimeMillis()+1); } } /** * Change the check interval. + * + * @param newCheckInterval */ public void configure(long newCheckInterval) { checkInterval = newCheckInterval; @@ -300,46 +315,25 @@ public abstract class AbstractTrafficShapingHandler extends /** * Class to implement setReadable at fix time - */ - private class ReopenRead implements Runnable { - /** - * Associated ChannelHandlerContext - */ - private ChannelHandlerContext ctx; - - /** - * Time to wait before clearing the channel - */ - private long timeToWait; - - /** - * @param ctx - * the associated channelHandlerContext - * @param timeToWait - */ - protected ReopenRead(ChannelHandlerContext ctx, long timeToWait) { + */ + private class ReopenReadTimerTask implements TimerTask { + ChannelHandlerContext ctx; + ReopenReadTimerTask(ChannelHandlerContext ctx) { this.ctx = ctx; - this.timeToWait = timeToWait; } - - /** - * Truly run the waken up of the channel - */ - @Override - public void run() { - try { - if (release.get()) { - return; - } - Thread.sleep(timeToWait); - } catch (InterruptedException e) { - // interruption so exit + public void run(Timeout timeoutArg) throws Exception { + //logger.warn("Start RRTT: "+release.get()); + if (release.get()) { return; } - // logger.info("WAKEUP!"); + /* + logger.warn("WAKEUP! "+ + (ctx != null && ctx.getChannel() != null && + ctx.getChannel().isConnected())); + */ if (ctx != null && ctx.getChannel() != null && ctx.getChannel().isConnected()) { - //logger.info(" setReadable TRUE: "+timeToWait); + //logger.warn(" setReadable TRUE: "); // readSuspended = false; ctx.setAttachment(null); ctx.getChannel().setReadable(true); @@ -375,17 +369,18 @@ public abstract class AbstractTrafficShapingHandler extends return; } // compute the number of ms to wait before reopening the channel - long wait = getTimeToWait(readLimit, trafficCounter - .getCurrentReadBytes(), trafficCounter.getLastTime(), - curtime); - if (wait > MINIMAL_WAIT) { // At least 10ms seems a minimal time in order to + long wait = getTimeToWait(readLimit, + trafficCounter.getCurrentReadBytes(), + trafficCounter.getLastTime(), curtime); + if (wait >= MINIMAL_WAIT) { // At least 10ms seems a minimal + // time in order to Channel channel = arg0.getChannel(); // try to limit the traffic if (channel != null && channel.isConnected()) { // Channel version - if (executor == null) { + if (timer == null) { // Sleep since no executor - //logger.info("Read sleep since no executor for "+wait+" ms for "+this); + // logger.warn("Read sleep since no timer for "+wait+" ms for "+this); if (release.get()) { return; } @@ -396,11 +391,14 @@ public abstract class AbstractTrafficShapingHandler extends // readSuspended = true; arg0.setAttachment(Boolean.TRUE); channel.setReadable(false); - //logger.info("Read will wakeup after "+wait+" ms "+this); - executor.execute(new ReopenRead(arg0, wait)); + // logger.warn("Read will wakeup after "+wait+" ms "+this); + TimerTask timerTask = new ReopenReadTimerTask(arg0); + timeout = timer.newTimeout(timerTask, wait, + TimeUnit.MILLISECONDS); } else { - // should be waiting: but can occurs sometime so as a FIX - //logger.info("Read sleep ok but should not be here: "+wait+" "+this); + // should be waiting: but can occurs sometime so as + // a FIX + // logger.warn("Read sleep ok but should not be here: "+wait+" "+this); if (release.get()) { return; } @@ -408,7 +406,7 @@ public abstract class AbstractTrafficShapingHandler extends } } else { // Not connected or no channel - //logger.info("Read sleep "+wait+" ms for "+this); + // logger.warn("Read sleep "+wait+" ms for "+this); if (release.get()) { return; } @@ -433,11 +431,12 @@ public abstract class AbstractTrafficShapingHandler extends if (writeLimit == 0) { return; } - // compute the number of ms to wait before continue with the channel - long wait = getTimeToWait(writeLimit, trafficCounter - .getCurrentWrittenBytes(), trafficCounter.getLastTime(), - curtime); - if (wait > MINIMAL_WAIT) { + // compute the number of ms to wait before continue with the + // channel + long wait = getTimeToWait(writeLimit, + trafficCounter.getCurrentWrittenBytes(), + trafficCounter.getLastTime(), curtime); + if (wait >= MINIMAL_WAIT) { // Global or Channel if (release.get()) { return; @@ -450,7 +449,6 @@ public abstract class AbstractTrafficShapingHandler extends super.writeRequested(arg0, arg1); } } - @Override public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception { @@ -481,13 +479,14 @@ public abstract class AbstractTrafficShapingHandler extends return trafficCounter; } - @Override public void releaseExternalResources() { if (trafficCounter != null) { trafficCounter.stop(); } release.set(true); - ExecutorUtil.terminate(executor); + if (timeout != null) { + timeout.cancel(); + } } @Override From 714e3d682eadfa2ad8ca2edc70e0076000ef038c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 16:37:34 +0300 Subject: [PATCH 116/134] Same fix than in version 3.5 for Master branch (refer to issue #345) Will be proposed once the one in 3.5 will be validated --- .../traffic/ChannelTrafficShapingHandler.java | 94 +++++-------------- 1 file changed, 26 insertions(+), 68 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java index 4609c2ad36..bd2f281dec 100644 --- a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java @@ -15,8 +15,6 @@ */ package io.netty.handler.traffic; -import java.util.concurrent.Executor; - import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipelineFactory; import io.netty.channel.ChannelStateEvent; @@ -24,6 +22,7 @@ import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import io.netty.handler.execution.ObjectSizeEstimator; +import io.netty.util.Timer; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for channel @@ -33,8 +32,8 @@ import io.netty.handler.execution.ObjectSizeEstimator; *
      *
    • Add in your pipeline a new ChannelTrafficShapingHandler, before a recommended {@link ExecutionHandler} (like * {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).
      - * ChannelTrafficShapingHandler myHandler = new ChannelTrafficShapingHandler(executor);
      - * executor could be created using Executors.newCachedThreadPool();
      + * ChannelTrafficShapingHandler myHandler = new ChannelTrafficShapingHandler(timer);
      + * timer could be created using HashedWheelTimer
      * pipeline.addLast("CHANNEL_TRAFFIC_SHAPING", myHandler);

      * * Note that this handler has a Pipeline Coverage of "one" which means a new handler must be created @@ -52,7 +51,7 @@ import io.netty.handler.execution.ObjectSizeEstimator; * the less precise the traffic shaping will be. It is suggested as higher value something close * to 5 or 10 minutes.
      *
    • - *
    • When you shutdown your application, release all the external resources like the executor + *
    • When you shutdown your application, release all the external resources (except the timer internal itself) * by calling:
      * myHandler.releaseExternalResources();
      *
    • @@ -60,96 +59,53 @@ import io.netty.handler.execution.ObjectSizeEstimator; */ public class ChannelTrafficShapingHandler extends AbstractTrafficShapingHandler { - /** - * @param executor - * @param writeLimit - * @param readLimit - * @param checkInterval - */ - public ChannelTrafficShapingHandler(Executor executor, long writeLimit, + public ChannelTrafficShapingHandler(Timer timer, long writeLimit, long readLimit, long checkInterval) { - super(executor, writeLimit, readLimit, checkInterval); + super(timer, writeLimit, readLimit, checkInterval); } - /** - * @param executor - * @param writeLimit - * @param readLimit - */ - public ChannelTrafficShapingHandler(Executor executor, long writeLimit, + public ChannelTrafficShapingHandler(Timer timer, long writeLimit, long readLimit) { - super(executor, writeLimit, readLimit); - } - - - /** - * @param executor - * @param checkInterval - */ - public ChannelTrafficShapingHandler(Executor executor, long checkInterval) { - super(executor, checkInterval); + super(timer, writeLimit, readLimit); } - /** - * @param executor - */ - public ChannelTrafficShapingHandler(Executor executor) { - super(executor); + public ChannelTrafficShapingHandler(Timer timer, long checkInterval) { + super(timer, checkInterval); + } + + public ChannelTrafficShapingHandler(Timer timer) { + super(timer); } - /** - * @param objectSizeEstimator - * @param executor - * @param writeLimit - * @param readLimit - * @param checkInterval - */ public ChannelTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor, + ObjectSizeEstimator objectSizeEstimator, Timer timer, long writeLimit, long readLimit, long checkInterval) { - super(objectSizeEstimator, executor, writeLimit, readLimit, + super(objectSizeEstimator, timer, writeLimit, readLimit, checkInterval); } - /** - * @param objectSizeEstimator - * @param executor - * @param writeLimit - * @param readLimit - */ public ChannelTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor, + ObjectSizeEstimator objectSizeEstimator, Timer timer, long writeLimit, long readLimit) { - super(objectSizeEstimator, executor, writeLimit, readLimit); + super(objectSizeEstimator, timer, writeLimit, readLimit); } - /** - * @param objectSizeEstimator - * @param executor - * @param checkInterval - */ public ChannelTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor, + ObjectSizeEstimator objectSizeEstimator, Timer timer, long checkInterval) { - super(objectSizeEstimator, executor, checkInterval); + super(objectSizeEstimator, timer, checkInterval); } - /** - * @param objectSizeEstimator - * @param executor - */ public ChannelTrafficShapingHandler( - ObjectSizeEstimator objectSizeEstimator, Executor executor) { - super(objectSizeEstimator, executor); + ObjectSizeEstimator objectSizeEstimator, Timer timer) { + super(objectSizeEstimator, timer); } - @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { if (trafficCounter != null) { trafficCounter.stop(); - trafficCounter = null; } super.channelClosed(ctx, e); } @@ -162,8 +118,10 @@ public class ChannelTrafficShapingHandler extends AbstractTrafficShapingHandler ctx.getChannel().setReadable(false); if (trafficCounter == null) { // create a new counter now - trafficCounter = new TrafficCounter(this, executor, "ChannelTC" + - ctx.getChannel().getId(), checkInterval); + if (timer != null) { + trafficCounter = new TrafficCounter(this, timer, "ChannelTC" + + ctx.getChannel().getId(), checkInterval); + } } if (trafficCounter != null) { trafficCounter.start(); From 3bd77e93f17439c00cd59f41eb7d310b0121b8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 16:38:21 +0300 Subject: [PATCH 117/134] Same fix than in version 3.5 for Master branch (refer to issue #345) Will be proposed once the one in 3.5 will be validated --- .../traffic/GlobalTrafficShapingHandler.java | 98 ++++++------------- 1 file changed, 31 insertions(+), 67 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java index a9a96a436c..b6617d8847 100644 --- a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java @@ -15,13 +15,12 @@ */ package io.netty.handler.traffic; -import java.util.concurrent.Executor; - import io.netty.channel.ChannelHandler.Sharable; import io.netty.handler.execution.ExecutionHandler; import io.netty.handler.execution.MemoryAwareThreadPoolExecutor; import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import io.netty.handler.execution.ObjectSizeEstimator; +import io.netty.util.Timer; /** * This implementation of the {@link AbstractTrafficShapingHandler} is for global @@ -31,8 +30,8 @@ import io.netty.handler.execution.ObjectSizeEstimator; * The general use should be as follow:
      *
        *
      • Create your unique GlobalTrafficShapingHandler like:

        - * GlobalTrafficShapingHandler myHandler = new GlobalTrafficShapingHandler(executor);

        - * executor could be created using Executors.newCachedThreadPool();
        + * GlobalTrafficShapingHandler myHandler = new GlobalTrafficShapingHandler(timer);

        + * timer could be created using HashedWheelTimer
        * pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);

        * * Note that this handler has a Pipeline Coverage of "all" which means only one such handler must be created @@ -52,7 +51,7 @@ import io.netty.handler.execution.ObjectSizeEstimator; * {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).
        * pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);

        *
      • - *
      • When you shutdown your application, release all the external resources like the executor + *
      • When you shutdown your application, release all the external resources (except the timer internal itself) * by calling:
        * myHandler.releaseExternalResources();
        *
      • @@ -64,96 +63,61 @@ public class GlobalTrafficShapingHandler extends AbstractTrafficShapingHandler { * Create the global TrafficCounter */ void createGlobalTrafficCounter() { - TrafficCounter tc = new TrafficCounter(this, executor, "GlobalTC", - checkInterval); - setTrafficCounter(tc); - tc.start(); + TrafficCounter tc; + if (timer != null) { + tc = new TrafficCounter(this, timer, "GlobalTC", + checkInterval); + setTrafficCounter(tc); + tc.start(); + } } - /** - * @param executor - * @param writeLimit - * @param readLimit - * @param checkInterval - */ - public GlobalTrafficShapingHandler(Executor executor, long writeLimit, + public GlobalTrafficShapingHandler(Timer timer, long writeLimit, long readLimit, long checkInterval) { - super(executor, writeLimit, readLimit, checkInterval); + super(timer, writeLimit, readLimit, checkInterval); createGlobalTrafficCounter(); } - /** - * @param executor - * @param writeLimit - * @param readLimit - */ - public GlobalTrafficShapingHandler(Executor executor, long writeLimit, + public GlobalTrafficShapingHandler(Timer timer, long writeLimit, long readLimit) { - super(executor, writeLimit, readLimit); - createGlobalTrafficCounter(); - } - /** - * @param executor - * @param checkInterval - */ - public GlobalTrafficShapingHandler(Executor executor, long checkInterval) { - super(executor, checkInterval); + super(timer, writeLimit, readLimit); createGlobalTrafficCounter(); } - /** - * @param executor - */ - public GlobalTrafficShapingHandler(Executor executor) { - super(executor); + public GlobalTrafficShapingHandler(Timer timer, long checkInterval) { + super(timer, checkInterval); + createGlobalTrafficCounter(); + } + + public GlobalTrafficShapingHandler(Timer timer) { + super(timer); createGlobalTrafficCounter(); } - /** - * @param objectSizeEstimator - * @param executor - * @param writeLimit - * @param readLimit - * @param checkInterval - */ public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, - Executor executor, long writeLimit, long readLimit, + Timer timer, long writeLimit, long readLimit, long checkInterval) { - super(objectSizeEstimator, executor, writeLimit, readLimit, + super(objectSizeEstimator, timer, writeLimit, readLimit, checkInterval); createGlobalTrafficCounter(); } - /** - * @param objectSizeEstimator - * @param executor - * @param writeLimit - * @param readLimit - */ public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, - Executor executor, long writeLimit, long readLimit) { - super(objectSizeEstimator, executor, writeLimit, readLimit); + Timer timer, long writeLimit, long readLimit) { + super(objectSizeEstimator, timer, writeLimit, readLimit); createGlobalTrafficCounter(); } - /** - * @param objectSizeEstimator - * @param executor - * @param checkInterval - */ public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, - Executor executor, long checkInterval) { - super(objectSizeEstimator, executor, checkInterval); + Timer timer, long checkInterval) { + super(objectSizeEstimator, timer, checkInterval); createGlobalTrafficCounter(); } - /** - * @param objectSizeEstimator - * @param executor - */ public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator, - Executor executor) { - super(objectSizeEstimator, executor); + Timer timer) { + super(objectSizeEstimator, timer); createGlobalTrafficCounter(); } + } From 792035cd3816536140463a6e683d084cafe3fcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 16:39:14 +0300 Subject: [PATCH 118/134] Same fix than in version 3.5 for Master branch (refer to issue #345) Will be proposed once the one in 3.5 will be validated --- .../netty/handler/traffic/TrafficCounter.java | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java index d8cfdb3fb6..9c0dd31a94 100644 --- a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java +++ b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java @@ -15,11 +15,14 @@ */ package io.netty.handler.traffic; -import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import io.netty.channel.ChannelHandlerContext; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; /** * TrafficCounter is associated with {@link AbstractTrafficShapingHandler}.
        @@ -97,27 +100,31 @@ public class TrafficCounter { /** * The associated TrafficShapingHandler */ - private AbstractTrafficShapingHandler trafficShapingHandler; + private final AbstractTrafficShapingHandler trafficShapingHandler; /** - * Default Executor + * One Timer for all Counter */ - private Executor executor; + private final Timer timer; // replace executor + /** + * Monitor created once in start() + */ + private TimerTask timerTask; + /** + * used in stop() to cancel the timer + */ + volatile private Timeout timeout = null; /** * Is Monitor active */ AtomicBoolean monitorActive = new AtomicBoolean(); - /** - * Monitor - */ - private TrafficMonitoring trafficMonitoring; - /** * Class to implement monitoring at fix delay - */ - private static class TrafficMonitoring implements Runnable { + * + */ + private static class TrafficMonitoringTask implements TimerTask { /** * The associated TrafficShapingHandler */ @@ -132,43 +139,30 @@ public class TrafficCounter { * @param trafficShapingHandler * @param counter */ - protected TrafficMonitoring( + protected TrafficMonitoringTask( AbstractTrafficShapingHandler trafficShapingHandler, TrafficCounter counter) { trafficShapingHandler1 = trafficShapingHandler; this.counter = counter; } - /** - * Default run - */ - @Override - public void run() { - try { - Thread.currentThread().setName(counter.name); - for (; counter.monitorActive.get();) { - long check = counter.checkInterval.get(); - if (check > 0) { - Thread.sleep(check); - } else { - // Delay goes to 0, so exit - return; - } - long endTime = System.currentTimeMillis(); - counter.resetAccounting(endTime); - if (trafficShapingHandler1 != null) { - trafficShapingHandler1.doAccounting(counter); - } - } - } catch (InterruptedException e) { - // End of computations + public void run(Timeout timeout) throws Exception { + if (!counter.monitorActive.get()) { + return; } + long endTime = System.currentTimeMillis(); + counter.resetAccounting(endTime); + if (trafficShapingHandler1 != null) { + trafficShapingHandler1.doAccounting(counter); + } + timeout = + counter.timer.newTimeout(this, counter.checkInterval.get(), TimeUnit.MILLISECONDS); } } /** * Start the monitoring process - */ + */ public void start() { synchronized (lastTime) { if (monitorActive.get()) { @@ -177,16 +171,16 @@ public class TrafficCounter { lastTime.set(System.currentTimeMillis()); if (checkInterval.get() > 0) { monitorActive.set(true); - trafficMonitoring = new TrafficMonitoring( - trafficShapingHandler, this); - executor.execute(trafficMonitoring); + timerTask = new TrafficMonitoringTask(trafficShapingHandler, this); + timeout = + timer.newTimeout(timerTask, checkInterval.get(), TimeUnit.MILLISECONDS); } } } /** * Stop the monitoring process - */ + */ public void stop() { synchronized (lastTime) { if (!monitorActive.get()) { @@ -197,6 +191,9 @@ public class TrafficCounter { if (trafficShapingHandler != null) { trafficShapingHandler.doAccounting(this); } + if (timeout != null) { + timeout.cancel(); + } } } @@ -222,20 +219,20 @@ public class TrafficCounter { } /** - * Constructor with the {@link AbstractTrafficShapingHandler} that hosts it, the executorService to use, its + * Constructor with the {@link AbstractTrafficShapingHandler} that hosts it, the Timer to use, its * name, the checkInterval between two computations in millisecond * @param trafficShapingHandler the associated AbstractTrafficShapingHandler - * @param executor - * Should be a CachedThreadPool for efficiency + * @param timer + * Could be a HashedWheelTimer * @param name * the name given to this monitor * @param checkInterval * the checkInterval in millisecond between two computations */ public TrafficCounter(AbstractTrafficShapingHandler trafficShapingHandler, - Executor executor, String name, long checkInterval) { + Timer timer, String name, long checkInterval) { this.trafficShapingHandler = trafficShapingHandler; - this.executor = executor; + this.timer = timer; this.name = name; lastCumulativeTime = System.currentTimeMillis(); configure(checkInterval); From 8846947081b37a53f884961c33b6f126a2cbaaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 16:40:06 +0300 Subject: [PATCH 119/134] Same fix than in version 3.5 for Master branch (refer to issue #345) Will be proposed once the one in 3.5 will be validated --- .../netty/handler/traffic/package-info.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/package-info.java b/handler/src/main/java/io/netty/handler/traffic/package-info.java index 2d2c2c2025..f39d8f335c 100644 --- a/handler/src/main/java/io/netty/handler/traffic/package-info.java +++ b/handler/src/main/java/io/netty/handler/traffic/package-info.java @@ -17,26 +17,28 @@ /** * Implementation of a Traffic Shaping Handler and Dynamic Statistics.
        *

        + * + * *

        The main goal of this package is to allow to shape the traffic (bandwidth limitation), * but also to get statistics on how many bytes are read or written. Both functions can * be active or inactive (traffic or statistics).

        * *

        Two classes implement this behavior:
        *

          - *
        • {@link io.netty.handler.traffic.TrafficCounter}: this class implements the counters needed by the handlers. + *
        • {@link TrafficCounter}: this class implements the counters needed by the handlers. * It can be accessed to get some extra information like the read or write bytes since last check, the read and write * bandwidth from last check...


        • * - *
        • {@link io.netty.handler.traffic.AbstractTrafficShapingHandler}: this abstract class implements the kernel + *
        • {@link AbstractTrafficShapingHandler}: this abstract class implements the kernel * of the traffic shaping. It could be extended to fit your needs. Two classes are proposed as default - * implementations: see {@link io.netty.handler.traffic.ChannelTrafficShapingHandler} and see {@link io.netty.handler.traffic.GlobalTrafficShapingHandler} + * implementations: see {@link ChannelTrafficShapingHandler} and see {@link GlobalTrafficShapingHandler} * respectively for Channel traffic shaping and Global traffic shaping.


        • * * The insertion in the pipeline of one of those handlers can be wherever you want, but - * it must be placed before any {@link io.netty.handler.execution.MemoryAwareThreadPoolExecutor} - * in your pipeline.
          - * It is really recommended to have such a {@link io.netty.handler.execution.MemoryAwareThreadPoolExecutor} - * (either non ordered or {@link io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor} + * it must be placed before any {@link MemoryAwareThreadPoolExecutor} + * in your pipeline.
          + * It is really recommended to have such a {@link MemoryAwareThreadPoolExecutor} + * (either non ordered or {@link OrderedMemoryAwareThreadPoolExecutor} * ) in your pipeline * when you want to use this feature with some real traffic shaping, since it will allow to relax the constraint on * NioWorker to do other jobs if necessary.
          @@ -48,9 +50,9 @@ * 60KB/s for each channel since NioWorkers are stopping by this handler.
          * When it is used as a read traffic shaper, the handler will set the channel as not readable, so as to relax the * NioWorkers.

          - * An {@link io.netty.util.ObjectSizeEstimator} can be passed at construction to specify what + * An {@link ObjectSizeEstimator} can be passed at construction to specify what * is the size of the object to be read or write accordingly to the type of - * object. If not specified, it will used the {@link io.netty.util.DefaultObjectSizeEstimator} implementation.

          + * object. If not specified, it will used the {@link DefaultObjectSizeEstimator} implementation.

          *

        * *

        Standard use could be as follow:

        @@ -60,27 +62,27 @@ * [Global or per Channel] [Write or Read] Limitation in byte/s.
        * A value of 0 * stands for no limitation, so the traffic shaping is deactivate (on what you specified).
        - * You can either change those values with the method configure in {@link io.netty.handler.traffic.AbstractTrafficShapingHandler}.
        + * You can either change those values with the method configure in {@link AbstractTrafficShapingHandler}.
        *
        * *
      • To activate or deactivate the statistics, you can adjust the delay to a low (suggested not less than 200ms * for efficiency reasons) or a high value (let say 24H in millisecond is huge enough to not get the problem) * or even using 0 which means no computation will be done.

      • * If you want to do anything with this statistics, just override the doAccounting method.
        - * This interval can be changed either from the method configure in {@link io.netty.handler.traffic.AbstractTrafficShapingHandler} - * or directly using the method configure of {@link io.netty.handler.traffic.TrafficCounter}.

        + * This interval can be changed either from the method configure in {@link AbstractTrafficShapingHandler} + * or directly using the method configure of {@link TrafficCounter}.

        * *



      * *

      So in your application you will create your own TrafficShapingHandler and set the values to fit your needs.

      - * XXXXXTrafficShapingHandler myHandler = new XXXXXTrafficShapingHandler(executor);

      - * where executor could be created using Executors.newCachedThreadPool(); and XXXXX could be either + * XXXXXTrafficShapingHandler myHandler = new XXXXXTrafficShapingHandler(timer);

      + * timer could be created using HashedWheelTimer and XXXXX could be either * Global or Channel
      * pipeline.addLast("XXXXX_TRAFFIC_SHAPING", myHandler);
      * ...
      * pipeline.addLast("MemoryExecutor",new ExecutionHandler(memoryAwareThreadPoolExecutor));

      - *

      Note that a new {@link io.netty.handler.traffic.ChannelTrafficShapingHandler} must be created for each new channel, - * but only one {@link io.netty.handler.traffic.GlobalTrafficShapingHandler} must be created for all channels.

      + *

      Note that a new {@link ChannelTrafficShapingHandler} must be created for each new channel, + * but only one {@link GlobalTrafficShapingHandler} must be created for all channels.

      * *

      Note also that you can create different GlobalTrafficShapingHandler if you want to separate classes of * channels (for instance either from business point of view or from bind address point of view).

      From c634539faa81f9f551ca5beaf15243b6c34c3e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 18:20:21 +0300 Subject: [PATCH 120/134] Fix checkstyle --- .../src/main/java/io/netty/handler/traffic/TrafficCounter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java index 9c0dd31a94..4590fb578e 100644 --- a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java +++ b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java @@ -245,7 +245,7 @@ public class TrafficCounter { * @param newcheckInterval */ public void configure(long newcheckInterval) { - long newInterval = (newcheckInterval/10)*10; + long newInterval = (newcheckInterval / 10) * 10; if (checkInterval.get() != newInterval) { checkInterval.set(newInterval); if (newInterval <= 0) { From 41c6aefdab40b5ee58c9974e432a8d3fbc780957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 18:21:14 +0300 Subject: [PATCH 121/134] Fix checkstyle --- .../io/netty/handler/traffic/AbstractTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index bc7b9d406a..40b2a4f4cd 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -353,7 +353,7 @@ public abstract class AbstractTrafficShapingHandler extends // Time is too short, so just lets continue return 0; } - return ((bytes * 1000 / limit - interval)/10)*10; + return ((bytes * 1000 / limit - interval) / 10) * 10; } @Override From 327e4349259d5b14d3b5265e7c536bb22ac8a762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Br=C3=A9gier?= Date: Sun, 20 May 2012 18:25:10 +0300 Subject: [PATCH 122/134] Fix checkstyle --- .../io/netty/handler/traffic/AbstractTrafficShapingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index 40b2a4f4cd..3e8f294aca 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -286,7 +286,7 @@ public abstract class AbstractTrafficShapingHandler extends writeLimit = newWriteLimit; readLimit = newReadLimit; if (trafficCounter != null) { - trafficCounter.resetAccounting(System.currentTimeMillis()+1); + trafficCounter.resetAccounting(System.currentTimeMillis() + 1); } } From 10cd871df7993ab4a3e247cb248115e998aa2459 Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 21 May 2012 11:48:52 +0200 Subject: [PATCH 123/134] Release the resources of the BossWorkerPool. See #328 --- .../channel/socket/nio/NioServerSocketChannelFactory.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java index aa94a6a5f2..724cd48826 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannelFactory.java @@ -194,9 +194,13 @@ public class NioServerSocketChannelFactory implements ServerSocketChannelFactory @Override public void releaseExternalResources() { + if (bossWorkerPool instanceof ExternalResourceReleasable) { + ((ExternalResourceReleasable) bossWorkerPool).releaseExternalResources(); + } if (workerPool instanceof ExternalResourceReleasable) { ((ExternalResourceReleasable) workerPool).releaseExternalResources(); } + } } From 0a95ce81045d96c0936604bfe3141bc4cf79e532 Mon Sep 17 00:00:00 2001 From: norman Date: Mon, 21 May 2012 11:57:55 +0200 Subject: [PATCH 124/134] Fix checkstyle --- .../netty/handler/traffic/AbstractTrafficShapingHandler.java | 2 +- .../netty/handler/traffic/ChannelTrafficShapingHandler.java | 2 +- .../io/netty/handler/traffic/GlobalTrafficShapingHandler.java | 2 +- .../main/java/io/netty/handler/traffic/TrafficCounter.java | 2 +- .../src/main/java/io/netty/handler/traffic/package-info.java | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java index 3e8f294aca..ff97783fd7 100644 --- a/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -91,7 +91,7 @@ public abstract class AbstractTrafficShapingHandler extends /** * used in releaseExternalResources() to cancel the timer */ - volatile private Timeout timeout = null; + private volatile Timeout timeout; /** * Limit in B/s to apply to write diff --git a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java index bd2f281dec..7e3a659c82 100644 --- a/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java @@ -33,7 +33,7 @@ import io.netty.util.Timer; *
    • Add in your pipeline a new ChannelTrafficShapingHandler, before a recommended {@link ExecutionHandler} (like * {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).
      * ChannelTrafficShapingHandler myHandler = new ChannelTrafficShapingHandler(timer);
      - * timer could be created using HashedWheelTimer
      + * timer could be created using HashedWheelTimer
      * pipeline.addLast("CHANNEL_TRAFFIC_SHAPING", myHandler);

      * * Note that this handler has a Pipeline Coverage of "one" which means a new handler must be created diff --git a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java index b6617d8847..a52e0500f1 100644 --- a/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java +++ b/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java @@ -31,7 +31,7 @@ import io.netty.util.Timer; *
        *
      • Create your unique GlobalTrafficShapingHandler like:

        * GlobalTrafficShapingHandler myHandler = new GlobalTrafficShapingHandler(timer);

        - * timer could be created using HashedWheelTimer
        + * timer could be created using HashedWheelTimer
        * pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);

        * * Note that this handler has a Pipeline Coverage of "all" which means only one such handler must be created diff --git a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java index 4590fb578e..ac49367a77 100644 --- a/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java +++ b/handler/src/main/java/io/netty/handler/traffic/TrafficCounter.java @@ -113,7 +113,7 @@ public class TrafficCounter { /** * used in stop() to cancel the timer */ - volatile private Timeout timeout = null; + private volatile Timeout timeout; /** * Is Monitor active diff --git a/handler/src/main/java/io/netty/handler/traffic/package-info.java b/handler/src/main/java/io/netty/handler/traffic/package-info.java index f39d8f335c..fd7e408d0d 100644 --- a/handler/src/main/java/io/netty/handler/traffic/package-info.java +++ b/handler/src/main/java/io/netty/handler/traffic/package-info.java @@ -36,7 +36,7 @@ * * The insertion in the pipeline of one of those handlers can be wherever you want, but * it must be placed before any {@link MemoryAwareThreadPoolExecutor} - * in your pipeline.

      • + * in your pipeline
        .
        * It is really recommended to have such a {@link MemoryAwareThreadPoolExecutor} * (either non ordered or {@link OrderedMemoryAwareThreadPoolExecutor} * ) in your pipeline @@ -76,7 +76,7 @@ * *

        So in your application you will create your own TrafficShapingHandler and set the values to fit your needs.

        * XXXXXTrafficShapingHandler myHandler = new XXXXXTrafficShapingHandler(timer);

        - * timer could be created using HashedWheelTimer and XXXXX could be either + * timer could be created using HashedWheelTimer and XXXXX could be either * Global or Channel
        * pipeline.addLast("XXXXX_TRAFFIC_SHAPING", myHandler);
        * ...
        From 128309367cd0c295947eb27b830c43639b9eb705 Mon Sep 17 00:00:00 2001 From: norman Date: Tue, 22 May 2012 09:36:14 +0200 Subject: [PATCH 125/134] Also handle the case of missing shared lib for sctp --- .../io/netty/testsuite/util/SctpTestUtil.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/transport-sctp/src/test/java/io/netty/testsuite/util/SctpTestUtil.java b/transport-sctp/src/test/java/io/netty/testsuite/util/SctpTestUtil.java index 5a3b3abd63..6497a1e428 100644 --- a/transport-sctp/src/test/java/io/netty/testsuite/util/SctpTestUtil.java +++ b/transport-sctp/src/test/java/io/netty/testsuite/util/SctpTestUtil.java @@ -15,8 +15,11 @@ */ package io.netty.testsuite.util; +import java.io.IOException; import java.util.Locale; +import com.sun.nio.sctp.SctpChannel; + public class SctpTestUtil { //io.netty.util.SocketAddresses.LOCALHOST interface has MTU SIZE issues with SCTP, we have to use local loop back interface for testing public final static String LOOP_BACK = "127.0.0.1"; @@ -29,6 +32,18 @@ public class SctpTestUtil { public static boolean isSctpSupported() { String os = System.getProperty("os.name").toLowerCase(Locale.UK); if (os.equals("unix") || os.equals("linux") || os.equals("sun") || os.equals("solaris")) { + try { + SctpChannel.open(); + } catch (IOException e) { + // ignore + } catch (UnsupportedOperationException e) { + // This exception may get thrown if the OS does not have + // the shared libs installed. + System.out.print("Not supported: " + e.getMessage()); + return false; + + } + return true; } return false; From bd4358b3add80a2d3e9085006c65c6f298b522e7 Mon Sep 17 00:00:00 2001 From: norman Date: Tue, 22 May 2012 09:51:45 +0200 Subject: [PATCH 126/134] Make sure the test also work on ipv6 interfaces --- .../DefaultNioDatagramChannelConfigTest.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java b/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java index 5545cb61f9..41d7f5c13e 100644 --- a/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java +++ b/transport/src/test/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfigTest.java @@ -18,10 +18,12 @@ package io.netty.channel.socket.nio; import io.netty.util.internal.DetectionUtil; import java.io.IOException; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.StandardProtocolFamily; import java.nio.channels.DatagramChannel; +import java.util.Enumeration; import junit.framework.Assert; @@ -34,20 +36,45 @@ public class DefaultNioDatagramChannelConfigTest { if (DetectionUtil.javaVersion() < 7) { return; } - DefaultNioDatagramChannelConfig config = new DefaultNioDatagramChannelConfig(DatagramChannel.open(StandardProtocolFamily.INET)); - NetworkInterface inf = NetworkInterface.getNetworkInterfaces().nextElement(); + + StandardProtocolFamily family = null; + NetworkInterface inf = null; + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + inf = interfaces.nextElement(); + Enumeration addresses = inf.getInetAddresses(); + while(addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr instanceof Inet4Address) { + family = StandardProtocolFamily.INET; + break; + } else { + family = StandardProtocolFamily.INET6; + } + } + } + if (inf == null) { + // No usable interface found so just skip the test + return; + } + + DefaultNioDatagramChannelConfig config = new DefaultNioDatagramChannelConfig(DatagramChannel.open(family)); + config.setNetworkInterface(inf); Assert.assertEquals(inf, config.getNetworkInterface()); - + InetAddress localhost = inf.getInetAddresses().nextElement(); config.setInterface(localhost); Assert.assertEquals(localhost, config.getInterface()); - + config.setTimeToLive(100); Assert.assertEquals(100, config.getTimeToLive()); - + config.setLoopbackModeDisabled(false); Assert.assertEquals(false, config.isLoopbackModeDisabled()); + + + } } From 03f890a882c3cc7eb11e08b0b5004827945fbe0b Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 22 May 2012 22:57:36 +0200 Subject: [PATCH 127/134] Only send event upstream once the Ssl handshake was completed successfull. See #358 --- .../java/io/netty/handler/ssl/SslHandler.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 5a55075188..28de7affae 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -197,16 +197,6 @@ public class SslHandler extends FrameDecoder private final NonReentrantLock pendingEncryptedWritesLock = new NonReentrantLock(); private volatile boolean issueHandshake; - private static final ChannelFutureListener HANDSHAKE_LISTENER = new ChannelFutureListener() { - - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (!future.isSuccess()) { - Channels.fireExceptionCaught(future.getChannel(), future.getCause()); - } - } - - }; private final SSLEngineInboundCloseFuture sslEngineCloseFuture = new SSLEngineInboundCloseFuture(); @@ -1270,13 +1260,26 @@ public class SslHandler extends FrameDecoder * Calls {@link #handshake()} once the {@link Channel} is connected */ @Override - public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + public void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { if (issueHandshake) { - // issue and handshake and add a listener to it which will fire an exception event if an exception was thrown - // while doing the handshake - handshake().addListener(HANDSHAKE_LISTENER); + // issue and handshake and add a listener to it which will fire an exception event if an exception was thrown while doing the handshake + handshake().addListener(new ChannelFutureListener() { + + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + Channels.fireExceptionCaught(future.getChannel(), future.getCause()); + } else { + // Send the event upstream after the handshake was completed without an error. + // + // See https://github.com/netty/netty/issues/358 + ctx.sendUpstream(e); + } + + } + }); + } else { + super.channelConnected(ctx, e); } - super.channelConnected(ctx, e); } /** From 71c1a2575de2abd8ae106df2705e5901da93f271 Mon Sep 17 00:00:00 2001 From: Cruz Julian Bishop Date: Wed, 23 May 2012 21:22:56 +1000 Subject: [PATCH 128/134] Added messages to all IndexOutOfBoundsExceptions I need to implement this to help myself finish more future pull requests which, so far, are plagued by these exceptions with no information available. --- .../netty/buffer/AbstractChannelBuffer.java | 34 +++++++++----- .../buffer/BigEndianHeapChannelBuffer.java | 3 +- .../buffer/ByteBufferBackedChannelBuffer.java | 9 ++-- .../buffer/ChannelBufferInputStream.java | 8 ++-- .../netty/buffer/CompositeChannelBuffer.java | 46 +++++++++++++------ .../buffer/LittleEndianHeapChannelBuffer.java | 3 +- .../io/netty/buffer/SlicedChannelBuffer.java | 14 ++++-- .../netty/buffer/TruncatedChannelBuffer.java | 9 ++-- 8 files changed, 85 insertions(+), 41 deletions(-) diff --git a/buffer/src/main/java/io/netty/buffer/AbstractChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/AbstractChannelBuffer.java index 76789fae35..57d0633ef2 100644 --- a/buffer/src/main/java/io/netty/buffer/AbstractChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/AbstractChannelBuffer.java @@ -42,7 +42,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void readerIndex(int readerIndex) { if (readerIndex < 0 || readerIndex > writerIndex) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid readerIndex: " + + readerIndex + " - Maximum is " + writerIndex); } this.readerIndex = readerIndex; } @@ -55,7 +56,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void writerIndex(int writerIndex) { if (writerIndex < readerIndex || writerIndex > capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid writerIndex: " + + writerIndex + " - Maximum is " + readerIndex + " or " + capacity()); } this.writerIndex = writerIndex; } @@ -63,7 +65,9 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void setIndex(int readerIndex, int writerIndex) { if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid indexes: readerIndex is " + + readerIndex + ", writerIndex is " + + writerIndex + ", capacity is " + capacity()); } this.readerIndex = readerIndex; this.writerIndex = writerIndex; @@ -129,7 +133,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void ensureWritableBytes(int writableBytes) { if (writableBytes > writableBytes()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Writable bytes exceeded: Got " + + writableBytes + ", maximum is " + writableBytes()); } } @@ -190,7 +195,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void getBytes(int index, ChannelBuffer dst, int length) { if (length > dst.writableBytes()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be read: Need " + + length + ", maximum is " + dst.writableBytes()); } getBytes(index, dst, dst.writerIndex(), length); dst.writerIndex(dst.writerIndex() + length); @@ -229,7 +235,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void setBytes(int index, ChannelBuffer src, int length) { if (length > src.readableBytes()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to write: Need " + + length + ", maximum is " + src.readableBytes()); } setBytes(index, src, src.readerIndex(), length); src.readerIndex(src.readerIndex() + length); @@ -271,7 +278,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public byte readByte() { if (readerIndex == writerIndex) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Readable byte limit exceeded: " + + readerIndex); } return getByte(readerIndex ++); } @@ -391,7 +399,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void readBytes(ChannelBuffer dst, int length) { if (length > dst.writableBytes()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be read: Need " + + length + ", maximum is " + dst.writableBytes()); } readBytes(dst, dst.writerIndex(), length); dst.writerIndex(dst.writerIndex() + length); @@ -432,7 +441,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { public void skipBytes(int length) { int newReaderIndex = readerIndex + length; if (newReaderIndex > writerIndex) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Readable bytes exceeded - Need " + + newReaderIndex + ", maximum is " + writerIndex); } readerIndex = newReaderIndex; } @@ -505,7 +515,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { @Override public void writeBytes(ChannelBuffer src, int length) { if (length > src.readableBytes()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to write - Need " + + length + ", maximum is " + src.readableBytes()); } writeBytes(src, src.readerIndex(), length); src.readerIndex(src.readerIndex() + length); @@ -697,7 +708,8 @@ public abstract class AbstractChannelBuffer implements ChannelBuffer { */ protected void checkReadableBytes(int minimumReadableBytes) { if (readableBytes() < minimumReadableBytes) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Not enough readable bytes - Need " + + minimumReadableBytes + ", maximum is " + readableBytes()); } } } diff --git a/buffer/src/main/java/io/netty/buffer/BigEndianHeapChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/BigEndianHeapChannelBuffer.java index 854749987f..ceab18d876 100644 --- a/buffer/src/main/java/io/netty/buffer/BigEndianHeapChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/BigEndianHeapChannelBuffer.java @@ -130,7 +130,8 @@ public class BigEndianHeapChannelBuffer extends HeapChannelBuffer { @Override public ChannelBuffer copy(int index, int length) { if (index < 0 || length < 0 || index + length > array.length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to copy - Need " + + (index + length) + ", maximum is " + array.length); } byte[] copiedArray = new byte[length]; diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufferBackedChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/ByteBufferBackedChannelBuffer.java index 9e99c206e3..03a9e5d5c6 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufferBackedChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufferBackedChannelBuffer.java @@ -143,7 +143,8 @@ public class ByteBufferBackedChannelBuffer extends AbstractChannelBuffer { try { data.limit(index + length).position(index); } catch (IllegalArgumentException e) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to read - Need " + + (index + length) + ", maximum is " + data.limit()); } data.get(dst, dstIndex, length); } @@ -155,7 +156,8 @@ public class ByteBufferBackedChannelBuffer extends AbstractChannelBuffer { try { data.limit(index + bytesToCopy).position(index); } catch (IllegalArgumentException e) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to read - Need " + + (index + bytesToCopy) + ", maximum is " + data.limit()); } dst.put(data); } @@ -351,7 +353,8 @@ public class ByteBufferBackedChannelBuffer extends AbstractChannelBuffer { try { src = (ByteBuffer) buffer.duplicate().position(index).limit(index + length); } catch (IllegalArgumentException e) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to read - Need " + + (index + length)); } ByteBuffer dst = buffer.isDirect() ? ByteBuffer.allocateDirect(length) : ByteBuffer.allocate(length); diff --git a/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java b/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java index 05c8c0f24a..63c14f1109 100644 --- a/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java +++ b/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java @@ -69,7 +69,8 @@ public class ChannelBufferInputStream extends InputStream implements DataInput { throw new IllegalArgumentException("length: " + length); } if (length > buffer.readableBytes()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be read - Needs " + + length + ", maximum is " + buffer.readableBytes()); } this.buffer = buffer; @@ -237,10 +238,11 @@ public class ChannelBufferInputStream extends InputStream implements DataInput { private void checkAvailable(int fieldSize) throws IOException { if (fieldSize < 0) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("fieldSize cannot be a negative number"); } if (fieldSize > available()) { - throw new EOFException(); + throw new EOFException("fieldSize is too long! Length is " + fieldSize + + ", but maximum is " + available()); } } } diff --git a/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java index b8b673bf70..a1d7a526c1 100644 --- a/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/CompositeChannelBuffer.java @@ -55,7 +55,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { } if (index + length > capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to decompose - Need " + + (index + length) + ", capacity is " + capacity()); } int componentId = componentId(index); @@ -228,7 +229,9 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { public void getBytes(int index, byte[] dst, int dstIndex, int length) { int componentId = componentId(index); if (index > capacity() - length || dstIndex > dst.length - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to read - Needs " + + (index + length) + ", maximum is " + capacity() + " or " + + dst.length); } int i = componentId; @@ -250,7 +253,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { int limit = dst.limit(); int length = dst.remaining(); if (index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be read - Needs " + + (index + length) + ", maximum is " + capacity()); } int i = componentId; @@ -274,7 +278,9 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { public void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) { int componentId = componentId(index); if (index > capacity() - length || dstIndex > dst.capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be read - Needs " + + (index + length) + " or " + (dstIndex + length) + ", maximum is " + + capacity() + " or " + dst.capacity()); } int i = componentId; @@ -307,7 +313,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { throws IOException { int componentId = componentId(index); if (index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be read - needs " + + (index + length) + ", maximum of " + capacity()); } int i = componentId; @@ -388,7 +395,9 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { public void setBytes(int index, byte[] src, int srcIndex, int length) { int componentId = componentId(index); if (index > capacity() - length || srcIndex > src.length - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to read - needs " + + (index + length) + " or " + (srcIndex + length) + ", maximum is " + + capacity() + " or " + src.length); } int i = componentId; @@ -410,7 +419,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { int limit = src.limit(); int length = src.remaining(); if (index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be written - Needs " + + (index + length) + ", maximum is " + capacity()); } int i = componentId; @@ -434,7 +444,9 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { public void setBytes(int index, ChannelBuffer src, int srcIndex, int length) { int componentId = componentId(index); if (index > capacity() - length || srcIndex > src.capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to be written - Needs " + + (index + length) + " or " + (srcIndex + length) + ", maximum is " + + capacity() + " or " + src.capacity()); } int i = componentId; @@ -455,7 +467,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { throws IOException { int componentId = componentId(index); if (index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to write - Needs " + + (index + length) + ", maximum is " + capacity()); } int i = componentId; @@ -494,7 +507,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { throws IOException { int componentId = componentId(index); if (index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to write - Needs " + + (index + length) + ", maximum is " + capacity()); } int i = componentId; @@ -531,7 +545,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { public ChannelBuffer copy(int index, int length) { int componentId = componentId(index); if (index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to copy - Needs " + + (index + length) + ", maximum is " + capacity()); } ChannelBuffer dst = factory().getBuffer(order(), length); @@ -564,7 +579,9 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { return ChannelBuffers.EMPTY_BUFFER; } } else if (index < 0 || index > capacity() - length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid index: " + index + + " - Bytes needed: " + (index + length) + ", maximum is " + + capacity()); } else if (length == 0) { return ChannelBuffers.EMPTY_BUFFER; } @@ -599,7 +616,8 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { public ByteBuffer[] toByteBuffers(int index, int length) { int componentId = componentId(index); if (index + length > capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Too many bytes to convert - Needs" + + (index + length) + ", maximum is " + capacity()); } List buffers = new ArrayList(components.length); @@ -642,7 +660,7 @@ public class CompositeChannelBuffer extends AbstractChannelBuffer { } } - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid index: " + index + ", maximum: " + indices.length); } @Override diff --git a/buffer/src/main/java/io/netty/buffer/LittleEndianHeapChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/LittleEndianHeapChannelBuffer.java index 399cb49afc..eb2809b232 100644 --- a/buffer/src/main/java/io/netty/buffer/LittleEndianHeapChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/LittleEndianHeapChannelBuffer.java @@ -130,7 +130,8 @@ public class LittleEndianHeapChannelBuffer extends HeapChannelBuffer { @Override public ChannelBuffer copy(int index, int length) { if (index < 0 || length < 0 || index + length > array.length) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Copy could not be completed. Bytes needed: " + + (index + length) + ", maximum: " + array.length); } byte[] copiedArray = new byte[length]; diff --git a/buffer/src/main/java/io/netty/buffer/SlicedChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/SlicedChannelBuffer.java index 40c163005d..b478dc53bb 100644 --- a/buffer/src/main/java/io/netty/buffer/SlicedChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/SlicedChannelBuffer.java @@ -38,11 +38,13 @@ public class SlicedChannelBuffer extends AbstractChannelBuffer implements Wrappe public SlicedChannelBuffer(ChannelBuffer buffer, int index, int length) { if (index < 0 || index > buffer.capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid index of " + index + + ", maximum is " + buffer.capacity()); } if (index + length > buffer.capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid combined index of " + + (index + length) + ", maximum is " + buffer.capacity()); } this.buffer = buffer; @@ -245,7 +247,8 @@ public class SlicedChannelBuffer extends AbstractChannelBuffer implements Wrappe private void checkIndex(int index) { if (index < 0 || index >= capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid index: " + index + + ", maximum is " + capacity()); } } @@ -255,10 +258,11 @@ public class SlicedChannelBuffer extends AbstractChannelBuffer implements Wrappe "length is negative: " + length); } if (startIndex < 0) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("startIndex cannot be negative"); } if (startIndex + length > capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Index too big - Bytes needed: " + + (startIndex + length) + ", maximum is " + capacity()); } } } diff --git a/buffer/src/main/java/io/netty/buffer/TruncatedChannelBuffer.java b/buffer/src/main/java/io/netty/buffer/TruncatedChannelBuffer.java index 998136a1b7..47cb7c5b6f 100644 --- a/buffer/src/main/java/io/netty/buffer/TruncatedChannelBuffer.java +++ b/buffer/src/main/java/io/netty/buffer/TruncatedChannelBuffer.java @@ -37,7 +37,8 @@ public class TruncatedChannelBuffer extends AbstractChannelBuffer implements Wra public TruncatedChannelBuffer(ChannelBuffer buffer, int length) { if (length > buffer.capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Length is too large, got " + + length + " but can't go higher than " + buffer.capacity()); } this.buffer = buffer; @@ -239,7 +240,8 @@ public class TruncatedChannelBuffer extends AbstractChannelBuffer implements Wra private void checkIndex(int index) { if (index < 0 || index >= capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid index of " + index + + ", maximum is " + capacity()); } } @@ -249,7 +251,8 @@ public class TruncatedChannelBuffer extends AbstractChannelBuffer implements Wra "length is negative: " + length); } if (index + length > capacity()) { - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException("Invalid index of " + + (index + length) + ", maximum is " + capacity()); } } } From 3a7ed4b75cda8c3a7deefe2b8737a52370baa931 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 23 May 2012 15:12:04 +0200 Subject: [PATCH 129/134] Add workaround to let the sleep work correctly in windows too. See #356 --- .../java/io/netty/util/HashedWheelTimer.java | 14 ++++++++++++-- .../io/netty/util/internal/DetectionUtil.java | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/io/netty/util/HashedWheelTimer.java b/common/src/main/java/io/netty/util/HashedWheelTimer.java index 701f01623e..1f10f879b7 100644 --- a/common/src/main/java/io/netty/util/HashedWheelTimer.java +++ b/common/src/main/java/io/netty/util/HashedWheelTimer.java @@ -31,6 +31,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import io.netty.util.internal.ConcurrentIdentityHashMap; +import io.netty.util.internal.DetectionUtil; import io.netty.util.internal.ReusableIterator; import io.netty.util.internal.SharedResourceMisuseDetector; @@ -442,8 +443,17 @@ public class HashedWheelTimer implements Timer { for (;;) { final long currentTime = System.currentTimeMillis(); - final long sleepTime = tickDuration * tick - (currentTime - startTime); - + long sleepTime = tickDuration * tick - (currentTime - startTime); + + // Check if we run on windows, as if thats the case we will need + // to round the sleepTime as workaround for a bug that only affect + // the JVM if it runs on windows. + // + // See https://github.com/netty/netty/issues/356 + if (DetectionUtil.isWindows()) { + sleepTime = (sleepTime / 10) * 10; + } + if (sleepTime <= 0) { break; } diff --git a/common/src/main/java/io/netty/util/internal/DetectionUtil.java b/common/src/main/java/io/netty/util/internal/DetectionUtil.java index b16bca6298..a5fc94ac6d 100644 --- a/common/src/main/java/io/netty/util/internal/DetectionUtil.java +++ b/common/src/main/java/io/netty/util/internal/DetectionUtil.java @@ -36,7 +36,21 @@ public final class DetectionUtil { private static final int JAVA_VERSION = javaVersion0(); private static final boolean HAS_UNSAFE = hasUnsafe(AtomicInteger.class.getClassLoader()); - + private static final boolean IS_WINDOWS; + static { + String os = System.getProperty("os.name").toLowerCase(); + // windows + IS_WINDOWS = os.indexOf("win") >= 0; + } + + /** + * Return true if the JVM is running on Windows + * + */ + public static boolean isWindows() { + return IS_WINDOWS; + } + public static boolean hasUnsafe() { return HAS_UNSAFE; } From 69d5be4225085512aea5f9676d2f6ae105226c5c Mon Sep 17 00:00:00 2001 From: Jeff Pinner Date: Wed, 23 May 2012 08:52:50 -0700 Subject: [PATCH 130/134] SPDY: remove frame size limit in frame decoder --- .../codec/spdy/DefaultSpdyDataFrame.java | 11 - .../handler/codec/spdy/SpdyCodecUtil.java | 29 +- .../handler/codec/spdy/SpdyDataFrame.java | 10 - .../handler/codec/spdy/SpdyFrameCodec.java | 12 +- .../handler/codec/spdy/SpdyFrameDecoder.java | 794 ++++++++++++------ .../handler/codec/spdy/SpdyFrameEncoder.java | 19 +- .../handler/codec/spdy/SpdyHttpDecoder.java | 4 +- .../handler/codec/spdy/SpdyNoOpFrame.java | 1 + .../codec/spdy/SpdySessionHandler.java | 31 +- .../codec/spdy/SpdySessionHandlerTest.java | 26 +- 10 files changed, 608 insertions(+), 329 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java index fc1c203a6c..d64d2e3846 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyDataFrame.java @@ -26,7 +26,6 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame { private int streamID; private boolean last; - private boolean compressed; private ChannelBuffer data = ChannelBuffers.EMPTY_BUFFER; /** @@ -58,14 +57,6 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame { this.last = last; } - public boolean isCompressed() { - return compressed; - } - - public void setCompressed(boolean compressed) { - this.compressed = compressed; - } - public ChannelBuffer getData() { return data; } @@ -87,8 +78,6 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame { buf.append(getClass().getSimpleName()); buf.append("(last: "); buf.append(isLast()); - buf.append("; compressed: "); - buf.append(isCompressed()); buf.append(')'); buf.append(StringUtil.NEWLINE); buf.append("--> Stream-ID = "); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java index bafe9a660a..342fd36e51 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java @@ -28,8 +28,7 @@ final class SpdyCodecUtil { static final int SPDY_MAX_LENGTH = 0xFFFFFF; // Length is a 24-bit field - static final byte SPDY_DATA_FLAG_FIN = 0x01; - static final byte SPDY_DATA_FLAG_COMPRESS = 0x02; + static final byte SPDY_DATA_FLAG_FIN = 0x01; static final int SPDY_SYN_STREAM_FRAME = 1; static final int SPDY_SYN_REPLY_FRAME = 2; @@ -91,37 +90,37 @@ final class SpdyCodecUtil { * Reads a big-endian unsigned short integer from the buffer. */ static int getUnsignedShort(ChannelBuffer buf, int offset) { - return (int) ((buf.getByte(offset) & 0xFF) << 8 | - (buf.getByte(offset + 1) & 0xFF)); + return (buf.getByte(offset) & 0xFF) << 8 | + buf.getByte(offset + 1) & 0xFF; } /** * Reads a big-endian unsigned medium integer from the buffer. */ static int getUnsignedMedium(ChannelBuffer buf, int offset) { - return (int) ((buf.getByte(offset) & 0xFF) << 16 | - (buf.getByte(offset + 1) & 0xFF) << 8 | - (buf.getByte(offset + 2) & 0xFF)); + return (buf.getByte(offset) & 0xFF) << 16 | + (buf.getByte(offset + 1) & 0xFF) << 8 | + buf.getByte(offset + 2) & 0xFF; } /** * Reads a big-endian (31-bit) integer from the buffer. */ static int getUnsignedInt(ChannelBuffer buf, int offset) { - return (int) ((buf.getByte(offset) & 0x7F) << 24 | - (buf.getByte(offset + 1) & 0xFF) << 16 | - (buf.getByte(offset + 2) & 0xFF) << 8 | - (buf.getByte(offset + 3) & 0xFF)); + return (buf.getByte(offset) & 0x7F) << 24 | + (buf.getByte(offset + 1) & 0xFF) << 16 | + (buf.getByte(offset + 2) & 0xFF) << 8 | + buf.getByte(offset + 3) & 0xFF; } /** * Reads a big-endian signed integer from the buffer. */ static int getSignedInt(ChannelBuffer buf, int offset) { - return (int) ((buf.getByte(offset) & 0xFF) << 24 | - (buf.getByte(offset + 1) & 0xFF) << 16 | - (buf.getByte(offset + 2) & 0xFF) << 8 | - (buf.getByte(offset + 3) & 0xFF)); + return (buf.getByte(offset) & 0xFF) << 24 | + (buf.getByte(offset + 1) & 0xFF) << 16 | + (buf.getByte(offset + 2) & 0xFF) << 8 | + buf.getByte(offset + 3) & 0xFF; } /** diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java index 27e4123e79..7f08a9aaf7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyDataFrame.java @@ -44,16 +44,6 @@ public interface SpdyDataFrame { */ void setLast(boolean last); - /** - * Returns {@code true} if the data in this frame has been compressed. - */ - boolean isCompressed(); - - /** - * Sets if the data in this frame has been compressed. - */ - void setCompressed(boolean compressed); - /** * Returns the data payload of this frame. If there is no data payload * {@link ChannelBuffers#EMPTY_BUFFER} is returned. diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java index 8fe63d66c2..35350bfafb 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java @@ -33,21 +33,21 @@ public class SpdyFrameCodec implements ChannelUpstreamHandler, /** * Creates a new instance with the default decoder and encoder options - * ({@code maxChunkSize (8192)}, {@code maxFrameSize (65536)}, - * {@code maxHeaderSize (16384)}, {@code compressionLevel (6)}, - * {@code windowBits (15)}, and {@code memLevel (8)}). + * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)}, + * {@code compressionLevel (6)}, {@code windowBits (15)}, and + * {@code memLevel (8)}). */ public SpdyFrameCodec() { - this(8192, 65536, 16384, 6, 15, 8); + this(8192, 16384, 6, 15, 8); } /** * Creates a new instance with the specified decoder and encoder options. */ public SpdyFrameCodec( - int maxChunkSize, int maxFrameSize, int maxHeaderSize, + int maxChunkSize, int maxHeaderSize, int compressionLevel, int windowBits, int memLevel) { - decoder = new SpdyFrameDecoder(maxChunkSize, maxFrameSize, maxHeaderSize); + decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize); encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java index 0ee869b07c..d2a5a04657 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java @@ -15,13 +15,15 @@ */ package io.netty.handler.codec.spdy; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; + import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.Channels; import io.netty.handler.codec.frame.FrameDecoder; - -import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; +import io.netty.handler.codec.frame.TooLongFrameException; /** * Decodes {@link ChannelBuffer}s into SPDY Data and Control Frames. @@ -29,41 +31,62 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; public class SpdyFrameDecoder extends FrameDecoder { private final int maxChunkSize; - private final int maxFrameSize; private final int maxHeaderSize; private final SpdyHeaderBlockDecompressor headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(); + private State state; + private SpdySettingsFrame spdySettingsFrame; + private SpdyHeaderBlock spdyHeaderBlock; + + // SPDY common header fields + private byte flags; + private int length; + private int version; + private int type; + private int streamID; + + // Header block decoding fields + private int headerSize; + private int numHeaders; + private ChannelBuffer decompressed; + + private static enum State { + READ_COMMON_HEADER, + READ_CONTROL_FRAME, + READ_SETTINGS_FRAME, + READ_HEADER_BLOCK_FRAME, + READ_HEADER_BLOCK, + READ_DATA_FRAME, + DISCARD_FRAME, + FRAME_ERROR + } + /** - * Creates a new instance with the default {@code maxChunkSize (8192)}, - * {@code maxFrameSize (65536)}, and {@code maxHeaderSize (16384)}. + * Creates a new instance with the default {@code maxChunkSize (8192)} + * and {@code maxHeaderSize (16384)}. */ public SpdyFrameDecoder() { - this(8192, 65536, 16384); + this(8192, 16384); } /** * Creates a new instance with the specified parameters. */ - public SpdyFrameDecoder( - int maxChunkSize, int maxFrameSize, int maxHeaderSize) { - super(true); // Enable unfold for data frames + public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) { + super(false); if (maxChunkSize <= 0) { throw new IllegalArgumentException( "maxChunkSize must be a positive integer: " + maxChunkSize); } - if (maxFrameSize <= 0) { - throw new IllegalArgumentException( - "maxFrameSize must be a positive integer: " + maxFrameSize); - } if (maxHeaderSize <= 0) { throw new IllegalArgumentException( "maxHeaderSize must be a positive integer: " + maxHeaderSize); } this.maxChunkSize = maxChunkSize; - this.maxFrameSize = maxFrameSize; this.maxHeaderSize = maxHeaderSize; + state = State.READ_COMMON_HEADER; } @Override @@ -78,310 +101,470 @@ public class SpdyFrameDecoder extends FrameDecoder { } } - @Override protected Object decode( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { - - // Must read common header to determine frame length - if (buffer.readableBytes() < SPDY_HEADER_SIZE) { - return null; - } - - // Get frame length from common header - int frameOffset = buffer.readerIndex(); - int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET; - int dataLength = getUnsignedMedium(buffer, lengthOffset); - int frameLength = SPDY_HEADER_SIZE + dataLength; - - // Throw exception if frameLength exceeds maxFrameSize - if (frameLength > maxFrameSize) { - throw new SpdyProtocolException( - "Frame length exceeds " + maxFrameSize + ": " + frameLength); - } - - // Wait until entire frame is readable - if (buffer.readableBytes() < frameLength) { - return null; - } - - // Read common header fields - boolean control = (buffer.getByte(frameOffset) & 0x80) != 0; - int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET; - byte flags = buffer.getByte(flagsOffset); - - if (control) { - // Decode control frame common header - int version = getUnsignedShort(buffer, frameOffset) & 0x7FFF; - - // Spdy versioning spec is broken - if (version != SPDY_VERSION) { - buffer.skipBytes(frameLength); - throw new SpdyProtocolException( - "Unsupported version: " + version); - } - - int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET; - int type = getUnsignedShort(buffer, typeOffset); - buffer.skipBytes(SPDY_HEADER_SIZE); - - int readerIndex = buffer.readerIndex(); - buffer.skipBytes(dataLength); - return decodeControlFrame(type, flags, buffer.slice(readerIndex, dataLength)); - } else { - // Decode data frame common header - int streamID = getUnsignedInt(buffer, frameOffset); - buffer.skipBytes(SPDY_HEADER_SIZE); - - // Generate data frames that do not exceed maxChunkSize - int numFrames = dataLength / maxChunkSize; - if (dataLength % maxChunkSize != 0) { - numFrames ++; - } - SpdyDataFrame[] frames = new SpdyDataFrame[numFrames]; - for (int i = 0; i < numFrames; i++) { - int chunkSize = Math.min(maxChunkSize, dataLength); - SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); - spdyDataFrame.setCompressed((flags & SPDY_DATA_FLAG_COMPRESS) != 0); - spdyDataFrame.setData(buffer.readBytes(chunkSize)); - dataLength -= chunkSize; - if (dataLength == 0) { - spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0); + switch(state) { + case READ_COMMON_HEADER: + state = readCommonHeader(buffer); + if (state == State.FRAME_ERROR) { + if (version != SPDY_VERSION) { + fireProtocolException(ctx, "Unsupported version: " + version); + } else { + fireInvalidControlFrameException(ctx); } - frames[i] = spdyDataFrame; + } + return null; + + case READ_CONTROL_FRAME: + try { + Object frame = readControlFrame(buffer); + if (frame != null) { + state = State.READ_COMMON_HEADER; + } + return frame; + } catch (IllegalArgumentException e) { + state = State.FRAME_ERROR; + fireInvalidControlFrameException(ctx); + } + return null; + + case READ_SETTINGS_FRAME: + if (spdySettingsFrame == null) { + // Validate frame length against number of entries + if (buffer.readableBytes() < 4) { + return null; + } + int numEntries = getUnsignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(4); + length -= 4; + + // Each ID/Value entry is 8 bytes + if ((length & 0x07) != 0 || length >> 3 != numEntries) { + state = State.FRAME_ERROR; + fireInvalidControlFrameException(ctx); + return null; + } + + spdySettingsFrame = new DefaultSpdySettingsFrame(); + + boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0; + spdySettingsFrame.setClearPreviouslyPersistedSettings(clear); } - return frames; - } - } - - private Object decodeControlFrame(int type, byte flags, ChannelBuffer data) - throws Exception { - int streamID; - boolean last; - - switch (type) { - case SPDY_SYN_STREAM_FRAME: - if (data.readableBytes() < 12) { - throw new SpdyProtocolException( - "Received invalid SYN_STREAM control frame"); - } - streamID = getUnsignedInt(data, data.readerIndex()); - int associatedToStreamID = getUnsignedInt(data, data.readerIndex() + 4); - byte priority = (byte) (data.getByte(data.readerIndex() + 8) >> 6 & 0x03); - data.skipBytes(10); - - SpdySynStreamFrame spdySynStreamFrame = - new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority); - - last = (flags & SPDY_FLAG_FIN) != 0; - boolean unid = (flags & SPDY_FLAG_UNIDIRECTIONAL) != 0; - spdySynStreamFrame.setLast(last); - spdySynStreamFrame.setUnidirectional(unid); - - decodeHeaderBlock(spdySynStreamFrame, data); - - return spdySynStreamFrame; - - case SPDY_SYN_REPLY_FRAME: - if (data.readableBytes() < 8) { - throw new SpdyProtocolException( - "Received invalid SYN_REPLY control frame"); - } - streamID = getUnsignedInt(data, data.readerIndex()); - data.skipBytes(6); - - SpdySynReplyFrame spdySynReplyFrame = - new DefaultSpdySynReplyFrame(streamID); - - last = (flags & SPDY_FLAG_FIN) != 0; - spdySynReplyFrame.setLast(last); - - decodeHeaderBlock(spdySynReplyFrame, data); - - return spdySynReplyFrame; - - case SPDY_RST_STREAM_FRAME: - if (flags != 0 || data.readableBytes() != 8) { - throw new SpdyProtocolException( - "Received invalid RST_STREAM control frame"); - } - streamID = getUnsignedInt(data, data.readerIndex()); - int statusCode = getSignedInt(data, data.readerIndex() + 4); - if (statusCode == 0) { - throw new SpdyProtocolException( - "Received invalid RST_STREAM status code"); - } - - return new DefaultSpdyRstStreamFrame(streamID, statusCode); - - case SPDY_SETTINGS_FRAME: - if (data.readableBytes() < 4) { - throw new SpdyProtocolException( - "Received invalid SETTINGS control frame"); - } - // Each ID/Value entry is 8 bytes - // The number of entries cannot exceed SPDY_MAX_LENGTH / 8; - int numEntries = getUnsignedInt(data, data.readerIndex()); - if ((numEntries > (SPDY_MAX_LENGTH - 4) / 8) || - (data.readableBytes() != numEntries * 8 + 4)) { - throw new SpdyProtocolException( - "Received invalid SETTINGS control frame"); - } - data.skipBytes(4); - - SpdySettingsFrame spdySettingsFrame = new DefaultSpdySettingsFrame(); - - boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0; - spdySettingsFrame.setClearPreviouslyPersistedSettings(clear); - - for (int i = 0; i < numEntries; i ++) { + int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3); + for (int i = 0; i < readableEntries; i ++) { // Chromium Issue 79156 // SPDY setting ids are not written in network byte order // Read id assuming the architecture is little endian - int ID = (data.readByte() & 0xFF) | - (data.readByte() & 0xFF) << 8 | - (data.readByte() & 0xFF) << 16; - byte ID_flags = data.readByte(); - int value = getSignedInt(data, data.readerIndex()); - data.skipBytes(4); + int ID = buffer.readByte() & 0xFF | + (buffer.readByte() & 0xFF) << 8 | + (buffer.readByte() & 0xFF) << 16; + byte ID_flags = buffer.readByte(); + int value = getSignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(4); - if (!(spdySettingsFrame.isSet(ID))) { + // Check for invalid ID -- avoid IllegalArgumentException in setValue + if (ID == 0) { + state = State.FRAME_ERROR; + spdySettingsFrame = null; + fireInvalidControlFrameException(ctx); + return null; + } + + if (!spdySettingsFrame.isSet(ID)) { boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0; boolean persisted = (ID_flags & SPDY_SETTINGS_PERSISTED) != 0; spdySettingsFrame.setValue(ID, value, persistVal, persisted); } } - return spdySettingsFrame; - - case SPDY_NOOP_FRAME: - if (data.readableBytes() != 0) { - throw new SpdyProtocolException( - "Received invalid NOOP control frame"); + length -= 8 * readableEntries; + if (length == 0) { + state = State.READ_COMMON_HEADER; + Object frame = spdySettingsFrame; + spdySettingsFrame = null; + return frame; } - return null; - case SPDY_PING_FRAME: - if (data.readableBytes() != 4) { - throw new SpdyProtocolException( - "Received invalid PING control frame"); + case READ_HEADER_BLOCK_FRAME: + try { + spdyHeaderBlock = readHeaderBlockFrame(buffer); + if (spdyHeaderBlock != null) { + if (length == 0) { + state = State.READ_COMMON_HEADER; + Object frame = spdyHeaderBlock; + spdyHeaderBlock = null; + return frame; + } + state = State.READ_HEADER_BLOCK; + } + return null; + } catch (IllegalArgumentException e) { + state = State.FRAME_ERROR; + fireInvalidControlFrameException(ctx); + return null; } - int ID = getSignedInt(data, data.readerIndex()); + + case READ_HEADER_BLOCK: + int compressedBytes = Math.min(buffer.readableBytes(), length); + length -= compressedBytes; + + try { + decodeHeaderBlock(buffer.readSlice(compressedBytes)); + } catch (Exception e) { + state = State.FRAME_ERROR; + spdyHeaderBlock = null; + decompressed = null; + Channels.fireExceptionCaught(ctx, e); + return null; + } + + if (spdyHeaderBlock != null && spdyHeaderBlock.isInvalid()) { + Object frame = spdyHeaderBlock; + spdyHeaderBlock = null; + decompressed = null; + if (length == 0) { + state = State.READ_COMMON_HEADER; + } + return frame; + } + + if (length == 0) { + Object frame = spdyHeaderBlock; + spdyHeaderBlock = null; + state = State.READ_COMMON_HEADER; + return frame; + } + return null; + + case READ_DATA_FRAME: + if (streamID == 0) { + state = State.FRAME_ERROR; + fireProtocolException(ctx, "Received invalid data frame"); + return null; + } + + // Generate data frames that do not exceed maxChunkSize + int dataLength = Math.min(maxChunkSize, length); + + // Wait until entire frame is readable + if (buffer.readableBytes() < dataLength) { + return null; + } + + SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); + spdyDataFrame.setData(buffer.readBytes(dataLength)); + length -= dataLength; + + if (length == 0) { + spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0); + state = State.READ_COMMON_HEADER; + } + return spdyDataFrame; + + case DISCARD_FRAME: + int numBytes = Math.min(buffer.readableBytes(), length); + buffer.skipBytes(numBytes); + length -= numBytes; + if (length == 0) { + state = State.READ_COMMON_HEADER; + } + return null; + + case FRAME_ERROR: + buffer.skipBytes(buffer.readableBytes()); + return null; + + default: + throw new Error("Shouldn't reach here."); + } + } + + private State readCommonHeader(ChannelBuffer buffer) { + // Wait until entire header is readable + if (buffer.readableBytes() < SPDY_HEADER_SIZE) { + return State.READ_COMMON_HEADER; + } + + int frameOffset = buffer.readerIndex(); + int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET; + int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET; + buffer.skipBytes(SPDY_HEADER_SIZE); + + // Read common header fields + boolean control = (buffer.getByte(frameOffset) & 0x80) != 0; + flags = buffer.getByte(flagsOffset); + length = getUnsignedMedium(buffer, lengthOffset); + + if (control) { + // Decode control frame common header + version = getUnsignedShort(buffer, frameOffset) & 0x7FFF; + + int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET; + type = getUnsignedShort(buffer, typeOffset); + + // Check version first then validity + if (version != SPDY_VERSION || !isValidControlFrameHeader()) { + return State.FRAME_ERROR; + } + + // Make sure decoder will produce a frame or consume input + State nextState; + if (willGenerateControlFrame()) { + switch (type) { + case SPDY_SYN_STREAM_FRAME: + case SPDY_SYN_REPLY_FRAME: + case SPDY_HEADERS_FRAME: + nextState = State.READ_HEADER_BLOCK_FRAME; + break; + + case SPDY_SETTINGS_FRAME: + nextState = State.READ_SETTINGS_FRAME; + break; + + default: + nextState = State.READ_CONTROL_FRAME; + } + } else if (length != 0) { + nextState = State.DISCARD_FRAME; + } else { + nextState = State.READ_COMMON_HEADER; + } + return nextState; + } else { + // Decode data frame common header + streamID = getUnsignedInt(buffer, frameOffset); + + return State.READ_DATA_FRAME; + } + } + + private Object readControlFrame(ChannelBuffer buffer) { + switch (type) { + case SPDY_RST_STREAM_FRAME: + if (buffer.readableBytes() < 8) { + return null; + } + + int streamID = getUnsignedInt(buffer, buffer.readerIndex()); + int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4); + buffer.skipBytes(8); + + return new DefaultSpdyRstStreamFrame(streamID, statusCode); + + case SPDY_PING_FRAME: + if (buffer.readableBytes() < 4) { + return null; + } + + int ID = getSignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(4); return new DefaultSpdyPingFrame(ID); case SPDY_GOAWAY_FRAME: - if (data.readableBytes() != 4) { - throw new SpdyProtocolException( - "Received invalid GOAWAY control frame"); + if (buffer.readableBytes() < 4) { + return null; } - int lastGoodStreamID = getUnsignedInt(data, data.readerIndex()); + + int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(4); return new DefaultSpdyGoAwayFrame(lastGoodStreamID); - case SPDY_HEADERS_FRAME: - // Protocol allows length 4 frame when there are no name/value pairs - if (data.readableBytes() == 4) { - streamID = getUnsignedInt(data, data.readerIndex()); - return new DefaultSpdyHeadersFrame(streamID); - } - - if (data.readableBytes() < 8) { - throw new SpdyProtocolException( - "Received invalid HEADERS control frame"); - } - streamID = getUnsignedInt(data, data.readerIndex()); - data.skipBytes(6); - - SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID); - - decodeHeaderBlock(spdyHeadersFrame, data); - - return spdyHeadersFrame; - - case SPDY_WINDOW_UPDATE_FRAME: - return null; - default: - return null; + throw new Error("Shouldn't reach here."); } } - private boolean ensureBytes(ChannelBuffer decompressed, int bytes) throws Exception { + private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) { + int streamID; + switch (type) { + case SPDY_SYN_STREAM_FRAME: + if (buffer.readableBytes() < 12) { + return null; + } + + int offset = buffer.readerIndex(); + streamID = getUnsignedInt(buffer, offset); + int associatedToStreamID = getUnsignedInt(buffer, offset + 4); + byte priority = (byte) (buffer.getByte(offset + 8) >> 6 & 0x03); + buffer.skipBytes(10); + length -= 10; + + // SPDY/2 requires 16-bits of padding for empty header blocks + if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { + buffer.skipBytes(2); + length = 0; + } + + SpdySynStreamFrame spdySynStreamFrame = + new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority); + spdySynStreamFrame.setLast((flags & SPDY_FLAG_FIN) != 0); + spdySynStreamFrame.setUnidirectional((flags & SPDY_FLAG_UNIDIRECTIONAL) != 0); + + return spdySynStreamFrame; + + case SPDY_SYN_REPLY_FRAME: + if (buffer.readableBytes() < 8) { + return null; + } + + streamID = getUnsignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(6); + length -= 6; + + // SPDY/2 requires 16-bits of padding for empty header blocks + if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { + buffer.skipBytes(2); + length = 0; + } + + SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); + spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0); + + return spdySynReplyFrame; + + case SPDY_HEADERS_FRAME: + // Protocol allows length 4 frame when there are no name/value pairs + int minLength = length == 4 ? 4 : 8; + if (buffer.readableBytes() < minLength) { + return null; + } + + streamID = getUnsignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(4); + length -= 4; + + // SPDY/2 requires 16-bits of padding for empty header blocks + if (length == 4 && buffer.getShort(buffer.readerIndex() + 2) == 0) { + buffer.skipBytes(4); + length = 0; + } else if (length != 0) { + buffer.skipBytes(2); + length -= 2; + } + + return new DefaultSpdyHeadersFrame(streamID); + + default: + throw new Error("Shouldn't reach here."); + } + } + + private boolean ensureBytes(int bytes) throws Exception { if (decompressed.readableBytes() >= bytes) { return true; } - decompressed.discardReadBytes(); + // Perhaps last call to decode filled output buffer headerBlockDecompressor.decode(decompressed); return decompressed.readableBytes() >= bytes; } - private void decodeHeaderBlock(SpdyHeaderBlock headerFrame, ChannelBuffer headerBlock) - throws Exception { - if ((headerBlock.readableBytes() == 2) && - (headerBlock.getShort(headerBlock.readerIndex()) == 0)) { + private int readLengthField() { + return decompressed.readUnsignedShort(); + } + + private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception { + if (decompressed == null) { + // First time we start to decode a header block + // Initialize header block decoding fields + headerSize = 0; + numHeaders = -1; + decompressed = ChannelBuffers.dynamicBuffer(8192); + } + + // Accumulate decompressed data + headerBlockDecompressor.setInput(buffer); + headerBlockDecompressor.decode(decompressed); + + if (spdyHeaderBlock == null) { + // Only decompressing data to keep decompression context in sync + decompressed = null; return; } - headerBlockDecompressor.setInput(headerBlock); - ChannelBuffer decompressed = ChannelBuffers.dynamicBuffer(8192); - headerBlockDecompressor.decode(decompressed); + int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields - if (decompressed.readableBytes() < 2) { - throw new SpdyProtocolException( - "Received invalid header block"); - } - int headerSize = 0; - int numEntries = decompressed.readUnsignedShort(); - for (int i = 0; i < numEntries; i ++) { - if (!ensureBytes(decompressed, 2)) { - throw new SpdyProtocolException( - "Received invalid header block"); + if (numHeaders == -1) { + // Read number of Name/Value pairs + if (decompressed.readableBytes() < lengthFieldSize) { + return; } - int nameLength = decompressed.readUnsignedShort(); + numHeaders = readLengthField(); + } + + while (numHeaders > 0) { + int headerSize = this.headerSize; + decompressed.markReaderIndex(); + + // Try to read length of name + if (!ensureBytes(lengthFieldSize)) { + decompressed.resetReaderIndex(); + decompressed.discardReadBytes(); + return; + } + int nameLength = readLengthField(); + + // Recipients of a zero-length name must issue a stream error if (nameLength == 0) { - headerFrame.setInvalid(); + spdyHeaderBlock.setInvalid(); return; } headerSize += nameLength; if (headerSize > maxHeaderSize) { - throw new SpdyProtocolException( + throw new TooLongFrameException( "Header block exceeds " + maxHeaderSize); } - if (!ensureBytes(decompressed, nameLength)) { - throw new SpdyProtocolException( - "Received invalid header block"); + + // Try to read name + if (!ensureBytes(nameLength)) { + decompressed.resetReaderIndex(); + decompressed.discardReadBytes(); + return; } byte[] nameBytes = new byte[nameLength]; decompressed.readBytes(nameBytes); String name = new String(nameBytes, "UTF-8"); - if (headerFrame.containsHeader(name)) { - throw new SpdyProtocolException( - "Received duplicate header name: " + name); + + // Check for identically named headers + if (spdyHeaderBlock.containsHeader(name)) { + spdyHeaderBlock.setInvalid(); + return; } - if (!ensureBytes(decompressed, 2)) { - throw new SpdyProtocolException( - "Received invalid header block"); + + // Try to read length of value + if (!ensureBytes(lengthFieldSize)) { + decompressed.resetReaderIndex(); + decompressed.discardReadBytes(); + return; } - int valueLength = decompressed.readUnsignedShort(); + int valueLength = readLengthField(); + + // Recipients of illegal value fields must issue a stream error if (valueLength == 0) { - headerFrame.setInvalid(); + spdyHeaderBlock.setInvalid(); return; } headerSize += valueLength; if (headerSize > maxHeaderSize) { - throw new SpdyProtocolException( + throw new TooLongFrameException( "Header block exceeds " + maxHeaderSize); } - if (!ensureBytes(decompressed, valueLength)) { - throw new SpdyProtocolException( - "Received invalid header block"); + + // Try to read value + if (!ensureBytes(valueLength)) { + decompressed.resetReaderIndex(); + decompressed.discardReadBytes(); + return; } byte[] valueBytes = new byte[valueLength]; decompressed.readBytes(valueBytes); + + // Add Name/Value pair to headers int index = 0; int offset = 0; while (index < valueLength) { @@ -390,14 +573,121 @@ public class SpdyFrameDecoder extends FrameDecoder { } if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) { // Received multiple, in-sequence NULL characters - headerFrame.setInvalid(); + // Recipients of illegal value fields must issue a stream error + spdyHeaderBlock.setInvalid(); return; } String value = new String(valueBytes, offset, index - offset, "UTF-8"); - headerFrame.addHeader(name, value); + + try { + spdyHeaderBlock.addHeader(name, value); + } catch (IllegalArgumentException e) { + // Name contains NULL or non-ascii characters + spdyHeaderBlock.setInvalid(); + return; + } index ++; offset = index; } + numHeaders --; + this.headerSize = headerSize; + } + decompressed = null; + } + + private boolean isValidControlFrameHeader() { + switch (type) { + case SPDY_SYN_STREAM_FRAME: + return length >= 12; + + case SPDY_SYN_REPLY_FRAME: + return length >= 8; + + case SPDY_RST_STREAM_FRAME: + return flags == 0 && length == 8; + + case SPDY_SETTINGS_FRAME: + return length >= 4; + + case SPDY_NOOP_FRAME: + return length == 0; + + case SPDY_PING_FRAME: + return length == 4; + + case SPDY_GOAWAY_FRAME: + return length == 4; + + case SPDY_HEADERS_FRAME: + return length == 4 || length >= 8; + + case SPDY_WINDOW_UPDATE_FRAME: + default: + return true; } } + + private boolean willGenerateControlFrame() { + switch (type) { + case SPDY_SYN_STREAM_FRAME: + case SPDY_SYN_REPLY_FRAME: + case SPDY_RST_STREAM_FRAME: + case SPDY_SETTINGS_FRAME: + case SPDY_PING_FRAME: + case SPDY_GOAWAY_FRAME: + case SPDY_HEADERS_FRAME: + return true; + + case SPDY_NOOP_FRAME: + case SPDY_WINDOW_UPDATE_FRAME: + default: + return false; + } + } + + private void fireInvalidControlFrameException(ChannelHandlerContext ctx) { + String message = "Received invalid control frame"; + switch (type) { + case SPDY_SYN_STREAM_FRAME: + message = "Received invalid SYN_STREAM control frame"; + break; + + case SPDY_SYN_REPLY_FRAME: + message = "Received invalid SYN_REPLY control frame"; + break; + + case SPDY_RST_STREAM_FRAME: + message = "Received invalid RST_STREAM control frame"; + break; + + case SPDY_SETTINGS_FRAME: + message = "Received invalid SETTINGS control frame"; + break; + + case SPDY_NOOP_FRAME: + message = "Received invalid NOOP control frame"; + break; + + case SPDY_PING_FRAME: + message = "Received invalid PING control frame"; + break; + + case SPDY_GOAWAY_FRAME: + message = "Received invalid GOAWAY control frame"; + break; + + case SPDY_HEADERS_FRAME: + message = "Received invalid HEADERS control frame"; + break; + + case SPDY_WINDOW_UPDATE_FRAME: + message = "Received invalid WINDOW_UPDATE control frame"; + break; + } + fireProtocolException(ctx, message); + } + + private static void fireProtocolException(ChannelHandlerContext ctx, String message) { + Channels.fireExceptionCaught(ctx, new SpdyProtocolException(message)); + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java index 878dacf405..39619dfbb4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java @@ -82,9 +82,6 @@ public class SpdyFrameEncoder extends OneToOneEncoder { SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; ChannelBuffer data = spdyDataFrame.getData(); byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0; - if (spdyDataFrame.isCompressed()) { - flags |= SPDY_DATA_FLAG_COMPRESS; - } ChannelBuffer header = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE); header.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF); @@ -102,7 +99,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { flags |= SPDY_FLAG_UNIDIRECTIONAL; } int headerBlockLength = data.readableBytes(); - int length = (headerBlockLength == 0) ? 12 : 10 + headerBlockLength; + int length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); frame.writeShort(SPDY_VERSION | 0x8000); @@ -111,7 +108,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { frame.writeMedium(length); frame.writeInt(spdySynStreamFrame.getStreamID()); frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID()); - frame.writeShort(((short) spdySynStreamFrame.getPriority()) << 14); + frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 14); if (data.readableBytes() == 0) { frame.writeShort(0); } @@ -124,7 +121,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { encodeHeaderBlock(spdySynReplyFrame)); byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0; int headerBlockLength = data.readableBytes(); - int length = (headerBlockLength == 0) ? 8 : 6 + headerBlockLength; + int length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); frame.writeShort(SPDY_VERSION | 0x8000); @@ -178,9 +175,9 @@ public class SpdyFrameEncoder extends OneToOneEncoder { // Chromium Issue 79156 // SPDY setting ids are not written in network byte order // Write id assuming the architecture is little endian - frame.writeByte((id >> 0) & 0xFF); - frame.writeByte((id >> 8) & 0xFF); - frame.writeByte((id >> 16) & 0xFF); + frame.writeByte(id >> 0 & 0xFF); + frame.writeByte(id >> 8 & 0xFF); + frame.writeByte(id >> 16 & 0xFF); frame.writeByte(ID_flags); frame.writeInt(spdySettingsFrame.getValue(id)); } @@ -223,7 +220,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { ChannelBuffer data = compressHeaderBlock( encodeHeaderBlock(spdyHeadersFrame)); int headerBlockLength = data.readableBytes(); - int length = (headerBlockLength == 0) ? 4 : 6 + headerBlockLength; + int length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); frame.writeShort(SPDY_VERSION | 0x8000); @@ -240,7 +237,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { return msg; } - private ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame) + private static ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame) throws Exception { Set names = headerFrame.getHeaderNames(); int numHeaders = names.size(); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java index a16ab9fccd..0a6245bc9a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -220,7 +220,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { return null; } - private HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame) + private static HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame) throws Exception { // Create the first line of the request from the name/value pairs HttpMethod method = SpdyHeaders.getMethod(requestFrame); @@ -250,7 +250,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { return httpRequest; } - private HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame) + private static HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame) throws Exception { // Create the first line of the response from the name/value pairs HttpResponseStatus status = SpdyHeaders.getStatus(responseFrame); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java index 8b955ecd27..f923051d00 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyNoOpFrame.java @@ -19,4 +19,5 @@ package io.netty.handler.codec.spdy; * A SPDY Protocol NOOP Control Frame */ public interface SpdyNoOpFrame { + // Tag interface } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java index c2a77220d9..b89d136cc5 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java @@ -27,13 +27,14 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelStateEvent; import io.netty.channel.Channels; +import io.netty.channel.ExceptionEvent; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; /** * Manages streams within a SPDY session. */ -public class SpdySessionHandler extends SimpleChannelUpstreamHandler +public class SpdySessionHandler extends SimpleChannelUpstreamHandler implements ChannelDownstreamHandler { private static final SpdyProtocolException PROTOCOL_EXCEPTION = new SpdyProtocolException(); @@ -217,12 +218,12 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler */ SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg; - + if (isRemoteInitiatedID(spdyPingFrame.getID())) { Channels.write(ctx, Channels.future(e.getChannel()), spdyPingFrame, e.getRemoteAddress()); return; } - + // Note: only checks that there are outstanding pings since uniqueness is not inforced if (pings.get() == 0) { return; @@ -253,6 +254,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler super.messageReceived(ctx, e); } + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + + Throwable cause = e.getCause(); + if (cause instanceof SpdyProtocolException) { + issueSessionError(ctx, e.getChannel(), null); + } + + super.exceptionCaught(ctx, e); + } + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { if (evt instanceof ChannelStateEvent) { @@ -380,7 +393,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler private boolean isRemoteInitiatedID(int ID) { boolean serverID = SpdyCodecUtil.isServerID(ID); - return (server && !serverID) || (!server && serverID); + return server && !serverID || !server && serverID; } private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) { @@ -416,8 +429,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler if (receivedGoAwayFrame || sentGoAwayFrame) { return false; } - if ((maxConcurrentStreams != 0) && - (spdySession.numActiveStreams() >= maxConcurrentStreams)) { + if (maxConcurrentStreams != 0 && + spdySession.numActiveStreams() >= maxConcurrentStreams) { return false; } spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed); @@ -433,14 +446,14 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } else { spdySession.closeLocalSide(streamID); } - if ((closeSessionFuture != null) && spdySession.noActiveStreams()) { + if (closeSessionFuture != null && spdySession.noActiveStreams()) { closeSessionFuture.setSuccess(); } } private void removeStream(int streamID) { spdySession.removeStream(streamID); - if ((closeSessionFuture != null) && spdySession.noActiveStreams()) { + if (closeSessionFuture != null && spdySession.noActiveStreams()) { closeSessionFuture.setSuccess(); } } @@ -466,7 +479,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler if (!sentGoAwayFrame) { sentGoAwayFrame = true; ChannelFuture future = Channels.future(channel); - Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID)); + Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID), remoteAddress); return future; } return Channels.succeededFuture(channel); diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java index 83bfdb6f7b..893f0baed1 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java @@ -18,9 +18,9 @@ package io.netty.handler.codec.spdy; import java.util.List; import java.util.Map; -import io.netty.channel.Channels; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelStateEvent; +import io.netty.channel.Channels; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; import io.netty.handler.codec.embedder.DecoderEmbedder; @@ -36,7 +36,7 @@ public class SpdySessionHandlerTest { closeMessage.setValue(closeSignal, 0); } - private void assertHeaderBlock(SpdyHeaderBlock received, SpdyHeaderBlock expected) { + private static void assertHeaderBlock(SpdyHeaderBlock received, SpdyHeaderBlock expected) { for (String name: expected.getHeaderNames()) { List expectedValues = expected.getHeaders(name); List receivedValues = received.getHeaders(name); @@ -48,7 +48,7 @@ public class SpdySessionHandlerTest { Assert.assertTrue(received.getHeaders().isEmpty()); } - private void assertDataFrame(Object msg, int streamID, boolean last) { + private static void assertDataFrame(Object msg, int streamID, boolean last) { Assert.assertNotNull(msg); Assert.assertTrue(msg instanceof SpdyDataFrame); SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; @@ -56,7 +56,7 @@ public class SpdySessionHandlerTest { Assert.assertTrue(spdyDataFrame.isLast() == last); } - private void assertSynReply(Object msg, int streamID, boolean last, SpdyHeaderBlock headers) { + private static void assertSynReply(Object msg, int streamID, boolean last, SpdyHeaderBlock headers) { Assert.assertNotNull(msg); Assert.assertTrue(msg instanceof SpdySynReplyFrame); SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; @@ -65,7 +65,7 @@ public class SpdySessionHandlerTest { assertHeaderBlock(spdySynReplyFrame, headers); } - private void assertRstStream(Object msg, int streamID, SpdyStreamStatus status) { + private static void assertRstStream(Object msg, int streamID, SpdyStreamStatus status) { Assert.assertNotNull(msg); Assert.assertTrue(msg instanceof SpdyRstStreamFrame); SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; @@ -73,21 +73,21 @@ public class SpdySessionHandlerTest { Assert.assertTrue(spdyRstStreamFrame.getStatus().equals(status)); } - private void assertPing(Object msg, int ID) { + private static void assertPing(Object msg, int ID) { Assert.assertNotNull(msg); Assert.assertTrue(msg instanceof SpdyPingFrame); SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg; Assert.assertTrue(spdyPingFrame.getID() == ID); } - private void assertGoAway(Object msg, int lastGoodStreamID) { + private static void assertGoAway(Object msg, int lastGoodStreamID) { Assert.assertNotNull(msg); Assert.assertTrue(msg instanceof SpdyGoAwayFrame); SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg; Assert.assertTrue(spdyGoAwayFrame.getLastGoodStreamID() == lastGoodStreamID); } - private void assertHeaders(Object msg, int streamID, SpdyHeaderBlock headers) { + private static void assertHeaders(Object msg, int streamID, SpdyHeaderBlock headers) { Assert.assertNotNull(msg); Assert.assertTrue(msg instanceof SpdyHeadersFrame); SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; @@ -263,8 +263,8 @@ public class SpdySessionHandlerTest { // Echo Handler opens 4 half-closed streams on session connection // and then sets the number of concurrent streams to 3 private class EchoHandler extends SimpleChannelUpstreamHandler { - private int closeSignal; - private boolean server; + private final int closeSignal; + private final boolean server; EchoHandler(int closeSignal, boolean server) { super(); @@ -299,9 +299,9 @@ public class SpdySessionHandlerTest { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object msg = e.getMessage(); - if ((msg instanceof SpdyDataFrame) || - (msg instanceof SpdyPingFrame) || - (msg instanceof SpdyHeadersFrame)) { + if (msg instanceof SpdyDataFrame || + msg instanceof SpdyPingFrame || + msg instanceof SpdyHeadersFrame) { Channels.write(e.getChannel(), msg, e.getRemoteAddress()); return; From f60997686db58ab7623ddb15839d16169c59a702 Mon Sep 17 00:00:00 2001 From: Jeff Pinner Date: Wed, 23 May 2012 08:53:29 -0700 Subject: [PATCH 131/134] SPDY: fix for mozilla firefox bug 754766 --- .../spdy/DefaultSpdyWindowUpdateFrame.java | 76 +++++++++++++++++++ .../handler/codec/spdy/SpdyFrameDecoder.java | 38 +++++++++- .../handler/codec/spdy/SpdyFrameEncoder.java | 12 +++ .../codec/spdy/SpdyWindowUpdateFrame.java | 43 +++++++++++ .../spdy/AbstractSocketSpdyEchoTest.java | 20 +++-- 5 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java new file mode 100644 index 0000000000..5093213d6c --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.spdy; + +import io.netty.util.internal.StringUtil; + +/** + * The default {@link SpdyWindowUpdateFrame} implementation. + */ +public class DefaultSpdyWindowUpdateFrame implements SpdyWindowUpdateFrame { + + private int streamID; + private int deltaWindowSize; + + /** + * Creates a new instance. + * + * @param streamID the Stream-ID of this frame + * @param deltaWindowSize the Delta-Window-Size of this frame + */ + public DefaultSpdyWindowUpdateFrame(int streamID, int deltaWindowSize) { + setStreamID(streamID); + setDeltaWindowSize(deltaWindowSize); + } + + public int getStreamID() { + return streamID; + } + + public void setStreamID(int streamID) { + if (streamID <= 0) { + throw new IllegalArgumentException( + "Stream-ID must be positive: " + streamID); + } + this.streamID = streamID; + } + + public int getDeltaWindowSize() { + return deltaWindowSize; + } + + public void setDeltaWindowSize(int deltaWindowSize) { + if (deltaWindowSize <= 0) { + throw new IllegalArgumentException( + "Delta-Window-Size must be positive: " + + deltaWindowSize); + } + this.deltaWindowSize = deltaWindowSize; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append(StringUtil.NEWLINE); + buf.append("--> Stream-ID = "); + buf.append(streamID); + buf.append(StringUtil.NEWLINE); + buf.append("--> Delta-Window-Size = "); + buf.append(deltaWindowSize); + return buf.toString(); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java index d2a5a04657..016aac0cc4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java @@ -115,6 +115,26 @@ public class SpdyFrameDecoder extends FrameDecoder { fireInvalidControlFrameException(ctx); } } + + // FrameDecoders must consume data when producing frames + // All length 0 frames must be generated now + if (length == 0) { + if (state == State.READ_DATA_FRAME) { + if (streamID == 0) { + state = State.FRAME_ERROR; + fireProtocolException(ctx, "Received invalid data frame"); + return null; + } + + SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); + spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0); + state = State.READ_COMMON_HEADER; + return spdyDataFrame; + } + // There are no length 0 control frames + state = State.READ_COMMON_HEADER; + } + return null; case READ_CONTROL_FRAME: @@ -343,13 +363,14 @@ public class SpdyFrameDecoder extends FrameDecoder { } private Object readControlFrame(ChannelBuffer buffer) { + int streamID; switch (type) { case SPDY_RST_STREAM_FRAME: if (buffer.readableBytes() < 8) { return null; } - int streamID = getUnsignedInt(buffer, buffer.readerIndex()); + streamID = getUnsignedInt(buffer, buffer.readerIndex()); int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4); buffer.skipBytes(8); @@ -375,6 +396,17 @@ public class SpdyFrameDecoder extends FrameDecoder { return new DefaultSpdyGoAwayFrame(lastGoodStreamID); + case SPDY_WINDOW_UPDATE_FRAME: + if (buffer.readableBytes() < 8) { + return null; + } + + streamID = getUnsignedInt(buffer, buffer.readerIndex()); + int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4); + buffer.skipBytes(8); + + return new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize); + default: throw new Error("Shouldn't reach here."); } @@ -622,6 +654,8 @@ public class SpdyFrameDecoder extends FrameDecoder { return length == 4 || length >= 8; case SPDY_WINDOW_UPDATE_FRAME: + return length == 8; + default: return true; } @@ -636,10 +670,10 @@ public class SpdyFrameDecoder extends FrameDecoder { case SPDY_PING_FRAME: case SPDY_GOAWAY_FRAME: case SPDY_HEADERS_FRAME: + case SPDY_WINDOW_UPDATE_FRAME: return true; case SPDY_NOOP_FRAME: - case SPDY_WINDOW_UPDATE_FRAME: default: return false; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java index 39619dfbb4..309c3551e3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java @@ -231,6 +231,18 @@ public class SpdyFrameEncoder extends OneToOneEncoder { frame.writeShort(0); } return ChannelBuffers.wrappedBuffer(frame, data); + + } else if (msg instanceof SpdyWindowUpdateFrame) { + + SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg; + ChannelBuffer frame = ChannelBuffers.buffer( + ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8); + frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(SPDY_WINDOW_UPDATE_FRAME); + frame.writeInt(8); + frame.writeInt(spdyWindowUpdateFrame.getStreamID()); + frame.writeInt(spdyWindowUpdateFrame.getDeltaWindowSize()); + return frame; } // Unknown message type diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java new file mode 100644 index 0000000000..18f9712987 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.handler.codec.spdy; + +/** + * A SPDY Protocol WINDOW_UPDATE Control Frame + */ +public interface SpdyWindowUpdateFrame { + + /** + * Returns the Stream-ID of this frame. + */ + int getStreamID(); + + /** + * Sets the Stream-ID of this frame. The Stream-ID must be positive. + */ + void setStreamID(int streamID); + + /** + * Returns the Delta-Window-Size of this frame. + */ + int getDeltaWindowSize(); + + /** + * Sets the Delta-Window-Size of this frame. + * The Delta-Window-Size must be positive. + */ + void setDeltaWindowSize(int deltaWindowSize); +} diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java index 03caefc4f6..3e08e7d89f 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java @@ -47,7 +47,7 @@ import org.junit.Test; public abstract class AbstractSocketSpdyEchoTest { private static final Random random = new Random(); - static final ChannelBuffer frames = ChannelBuffers.buffer(1160); + static final ChannelBuffer frames = ChannelBuffers.buffer(1176); static final int ignoredBytes = 20; private static ExecutorService executor; @@ -68,7 +68,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeInt(0); // SPDY Data Frame - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeByte(0x01); frames.writeMedium(1024); for (int i = 0; i < 256; i ++) { @@ -81,7 +81,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeShort(1); frames.writeByte(0x03); frames.writeMedium(12); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF); frames.writeShort(0x8000); frames.writeShort(0); @@ -92,7 +92,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeShort(2); frames.writeByte(0x01); frames.writeMedium(8); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(0); // SPDY RST_STREAM Frame @@ -100,7 +100,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeByte(2); frames.writeShort(3); frames.writeInt(8); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() | 0x01); // SPDY SETTINGS Frame @@ -133,7 +133,15 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeByte(2); frames.writeShort(8); frames.writeInt(4); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + + // SPDY WINDOW_UPDATE Frame + frames.writeByte(0x80); + frames.writeByte(2); + frames.writeShort(9); + frames.writeInt(8); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); } @BeforeClass From 1a28d6119fb5d2e77caae8674540f62a6b9043e1 Mon Sep 17 00:00:00 2001 From: Jeff Pinner Date: Wed, 23 May 2012 08:54:11 -0700 Subject: [PATCH 132/134] SPDY: add SPDY/3 support --- .../codec/spdy/DefaultSpdyGoAwayFrame.java | 33 ++ .../codec/spdy/DefaultSpdyHeadersFrame.java | 12 + .../codec/spdy/DefaultSpdySynStreamFrame.java | 4 +- .../handler/codec/spdy/SpdyCodecUtil.java | 199 +++++++- .../handler/codec/spdy/SpdyFrameCodec.java | 17 +- .../handler/codec/spdy/SpdyFrameDecoder.java | 144 ++++-- .../handler/codec/spdy/SpdyFrameEncoder.java | 147 ++++-- .../handler/codec/spdy/SpdyGoAwayFrame.java | 10 + .../codec/spdy/SpdyHeaderBlockCompressor.java | 7 +- .../spdy/SpdyHeaderBlockDecompressor.java | 4 +- .../spdy/SpdyHeaderBlockJZlibCompressor.java | 13 +- .../spdy/SpdyHeaderBlockZlibCompressor.java | 12 +- .../spdy/SpdyHeaderBlockZlibDecompressor.java | 15 +- .../netty/handler/codec/spdy/SpdyHeaders.java | 213 ++++++-- .../handler/codec/spdy/SpdyHeadersFrame.java | 11 + .../handler/codec/spdy/SpdyHttpCodec.java | 7 +- .../handler/codec/spdy/SpdyHttpDecoder.java | 98 ++-- .../handler/codec/spdy/SpdyHttpEncoder.java | 83 ++- .../handler/codec/spdy/SpdyHttpHeaders.java | 25 + .../netty/handler/codec/spdy/SpdySession.java | 155 +++++- .../codec/spdy/SpdySessionHandler.java | 481 +++++++++++++++--- .../handler/codec/spdy/SpdySessionStatus.java | 113 ++++ .../handler/codec/spdy/SpdySettingsFrame.java | 17 +- .../handler/codec/spdy/SpdyStreamStatus.java | 32 ++ .../codec/spdy/SpdySynStreamFrame.java | 2 +- .../codec/spdy/SpdyWindowUpdateFrame.java | 2 +- .../spdy/AbstractSocketSpdyEchoTest.java | 91 +++- .../codec/spdy/SpdySessionHandlerTest.java | 10 +- 28 files changed, 1623 insertions(+), 334 deletions(-) create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionStatus.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java index 1185e2f929..27d4a010ce 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java @@ -23,6 +23,7 @@ import io.netty.util.internal.StringUtil; public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame { private int lastGoodStreamID; + private SpdySessionStatus status; /** * Creates a new instance. @@ -30,7 +31,28 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame { * @param lastGoodStreamID the Last-good-stream-ID of this frame */ public DefaultSpdyGoAwayFrame(int lastGoodStreamID) { + this(lastGoodStreamID, 0); + } + + /** + * Creates a new instance. + * + * @param lastGoodStreamID the Last-good-stream-ID of this frame + * @param statusCode the Status code of this frame + */ + public DefaultSpdyGoAwayFrame(int lastGoodStreamID, int statusCode) { + this(lastGoodStreamID, SpdySessionStatus.valueOf(statusCode)); + } + + /** + * Creates a new instance. + * + * @param lastGoodStreamID the Last-good-stream-ID of this frame + * @param status the status of this frame + */ + public DefaultSpdyGoAwayFrame(int lastGoodStreamID, SpdySessionStatus status) { setLastGoodStreamID(lastGoodStreamID); + setStatus(status); } public int getLastGoodStreamID() { @@ -45,6 +67,14 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame { this.lastGoodStreamID = lastGoodStreamID; } + public SpdySessionStatus getStatus() { + return status; + } + + public void setStatus(SpdySessionStatus status) { + this.status = status; + } + @Override public String toString() { StringBuilder buf = new StringBuilder(); @@ -52,6 +82,9 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame { buf.append(StringUtil.NEWLINE); buf.append("--> Last-good-stream-ID = "); buf.append(lastGoodStreamID); + buf.append(StringUtil.NEWLINE); + buf.append("--> Status: "); + buf.append(status.toString()); return buf.toString(); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java index c779f9baf2..6ee8207402 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java @@ -24,6 +24,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock implements SpdyHeadersFrame { private int streamID; + private boolean last; /** * Creates a new instance. @@ -47,10 +48,21 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock this.streamID = streamID; } + public boolean isLast() { + return last; + } + + public void setLast(boolean last) { + this.last = last; + } + @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName()); + buf.append("(last: "); + buf.append(isLast()); + buf.append(')'); buf.append(StringUtil.NEWLINE); buf.append("--> Stream-ID = "); buf.append(streamID); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java index 548d8a7e72..cea7867b67 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java @@ -74,9 +74,9 @@ public class DefaultSpdySynStreamFrame extends DefaultSpdyHeaderBlock } public void setPriority(byte priority) { - if (priority < 0 || priority > 3) { + if (priority < 0 || priority > 7) { throw new IllegalArgumentException( - "Priortiy must be between 0 and 3 inclusive: " + priority); + "Priority must be between 0 and 7 inclusive: " + priority); } this.priority = priority; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java index 342fd36e51..7f323eed37 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java @@ -19,7 +19,8 @@ import io.netty.buffer.ChannelBuffer; final class SpdyCodecUtil { - static final int SPDY_VERSION = 2; + static final int SPDY_MIN_VERSION = 2; + static final int SPDY_MAX_VERSION = 3; static final int SPDY_HEADER_TYPE_OFFSET = 2; static final int SPDY_HEADER_FLAGS_OFFSET = 4; @@ -39,6 +40,7 @@ final class SpdyCodecUtil { static final int SPDY_GOAWAY_FRAME = 7; static final int SPDY_HEADERS_FRAME = 8; static final int SPDY_WINDOW_UPDATE_FRAME = 9; + static final int SPDY_CREDENTIAL_FRAME = 10; static final byte SPDY_FLAG_FIN = 0x01; static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02; @@ -52,7 +54,188 @@ final class SpdyCodecUtil { static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field // Zlib Dictionary - private static final String SPDY_DICT_S = + static final byte[] SPDY_DICT = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - - + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - - + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t - + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - - + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e - + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e - + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - - + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t - + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - - + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - - + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - - + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - - + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - - + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - - + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h - + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - - + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e - + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - - + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f - + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - - + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - - + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a - + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - - + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - - + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - - + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r - + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e - + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g - + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a - + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - - + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t - + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - - + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e - + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 - + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 - + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e - + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t - + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v - + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u - + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - - + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t - + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 - + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - - + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 - + }; + + private static final String SPDY2_DICT_S = "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + @@ -66,19 +249,19 @@ final class SpdyCodecUtil { "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + ".1statusversionurl "; - static final byte[] SPDY_DICT; + static final byte[] SPDY2_DICT; static { - byte[] SPDY_DICT_ = null; + byte[] SPDY2_DICT_ = null; try { - SPDY_DICT_ = SPDY_DICT_S.getBytes("US-ASCII"); + SPDY2_DICT_ = SPDY2_DICT_S.getBytes("US-ASCII"); // dictionary is null terminated - SPDY_DICT_[SPDY_DICT_.length - 1] = (byte) 0; + SPDY2_DICT_[SPDY2_DICT_.length - 1] = (byte) 0; } catch (Exception e) { - SPDY_DICT_ = new byte[1]; + SPDY2_DICT_ = new byte[1]; } - SPDY_DICT = SPDY_DICT_; + SPDY2_DICT = SPDY2_DICT_; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java index 35350bfafb..0c54e89e79 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java @@ -32,23 +32,24 @@ public class SpdyFrameCodec implements ChannelUpstreamHandler, private final SpdyFrameEncoder encoder; /** - * Creates a new instance with the default decoder and encoder options + * Creates a new instance with the specified {@code version} and + * the default decoder and encoder options * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)}, - * {@code compressionLevel (6)}, {@code windowBits (15)}, and - * {@code memLevel (8)}). + * {@code compressionLevel (6)}, {@code windowBits (15)}, + * and {@code memLevel (8)}). */ - public SpdyFrameCodec() { - this(8192, 16384, 6, 15, 8); + public SpdyFrameCodec(int version) { + this(version, 8192, 16384, 6, 15, 8); } /** * Creates a new instance with the specified decoder and encoder options. */ public SpdyFrameCodec( - int maxChunkSize, int maxHeaderSize, + int version, int maxChunkSize, int maxHeaderSize, int compressionLevel, int windowBits, int memLevel) { - decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize); - encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel); + decoder = new SpdyFrameDecoder(version, maxChunkSize, maxHeaderSize); + encoder = new SpdyFrameEncoder(version, compressionLevel, windowBits, memLevel); } public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java index 016aac0cc4..104e531862 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java @@ -30,11 +30,11 @@ import io.netty.handler.codec.frame.TooLongFrameException; */ public class SpdyFrameDecoder extends FrameDecoder { + private final int spdyVersion; private final int maxChunkSize; private final int maxHeaderSize; - private final SpdyHeaderBlockDecompressor headerBlockDecompressor = - SpdyHeaderBlockDecompressor.newInstance(); + private final SpdyHeaderBlockDecompressor headerBlockDecompressor; private State state; private SpdySettingsFrame spdySettingsFrame; @@ -64,18 +64,22 @@ public class SpdyFrameDecoder extends FrameDecoder { } /** - * Creates a new instance with the default {@code maxChunkSize (8192)} - * and {@code maxHeaderSize (16384)}. + * Creates a new instance with the specified {@code version} and the default + * {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}. */ - public SpdyFrameDecoder() { - this(8192, 16384); + public SpdyFrameDecoder(int version) { + this(version, 8192, 16384); } /** * Creates a new instance with the specified parameters. */ - public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) { + public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) { super(false); + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } if (maxChunkSize <= 0) { throw new IllegalArgumentException( "maxChunkSize must be a positive integer: " + maxChunkSize); @@ -84,8 +88,10 @@ public class SpdyFrameDecoder extends FrameDecoder { throw new IllegalArgumentException( "maxHeaderSize must be a positive integer: " + maxHeaderSize); } + spdyVersion = version; this.maxChunkSize = maxChunkSize; this.maxHeaderSize = maxHeaderSize; + headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version); state = State.READ_COMMON_HEADER; } @@ -109,7 +115,7 @@ public class SpdyFrameDecoder extends FrameDecoder { case READ_COMMON_HEADER: state = readCommonHeader(buffer); if (state == State.FRAME_ERROR) { - if (version != SPDY_VERSION) { + if (version != spdyVersion) { fireProtocolException(ctx, "Unsupported version: " + version); } else { fireInvalidControlFrameException(ctx); @@ -175,13 +181,21 @@ public class SpdyFrameDecoder extends FrameDecoder { int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3); for (int i = 0; i < readableEntries; i ++) { - // Chromium Issue 79156 - // SPDY setting ids are not written in network byte order - // Read id assuming the architecture is little endian - int ID = buffer.readByte() & 0xFF | + int ID; + byte ID_flags; + if (version < 3) { + // Chromium Issue 79156 + // SPDY setting ids are not written in network byte order + // Read id assuming the architecture is little endian + ID = buffer.readByte() & 0xFF | (buffer.readByte() & 0xFF) << 8 | (buffer.readByte() & 0xFF) << 16; - byte ID_flags = buffer.readByte(); + ID_flags = buffer.readByte(); + } else { + ID_flags = buffer.readByte(); + ID = getUnsignedMedium(buffer, buffer.readerIndex()); + buffer.skipBytes(3); + } int value = getSignedInt(buffer, buffer.readerIndex()); buffer.skipBytes(4); @@ -327,7 +341,7 @@ public class SpdyFrameDecoder extends FrameDecoder { type = getUnsignedShort(buffer, typeOffset); // Check version first then validity - if (version != SPDY_VERSION || !isValidControlFrameHeader()) { + if (version != spdyVersion || !isValidControlFrameHeader()) { return State.FRAME_ERROR; } @@ -364,6 +378,7 @@ public class SpdyFrameDecoder extends FrameDecoder { private Object readControlFrame(ChannelBuffer buffer) { int streamID; + int statusCode; switch (type) { case SPDY_RST_STREAM_FRAME: if (buffer.readableBytes() < 8) { @@ -371,7 +386,7 @@ public class SpdyFrameDecoder extends FrameDecoder { } streamID = getUnsignedInt(buffer, buffer.readerIndex()); - int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4); + statusCode = getSignedInt(buffer, buffer.readerIndex() + 4); buffer.skipBytes(8); return new DefaultSpdyRstStreamFrame(streamID, statusCode); @@ -387,14 +402,22 @@ public class SpdyFrameDecoder extends FrameDecoder { return new DefaultSpdyPingFrame(ID); case SPDY_GOAWAY_FRAME: - if (buffer.readableBytes() < 4) { + int minLength = version < 3 ? 4 : 8; + if (buffer.readableBytes() < minLength) { return null; } int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex()); buffer.skipBytes(4); - return new DefaultSpdyGoAwayFrame(lastGoodStreamID); + if (version < 3) { + return new DefaultSpdyGoAwayFrame(lastGoodStreamID); + } + + statusCode = getSignedInt(buffer, buffer.readerIndex()); + buffer.skipBytes(4); + + return new DefaultSpdyGoAwayFrame(lastGoodStreamID, statusCode); case SPDY_WINDOW_UPDATE_FRAME: if (buffer.readableBytes() < 8) { @@ -413,22 +436,27 @@ public class SpdyFrameDecoder extends FrameDecoder { } private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) { + int minLength; int streamID; switch (type) { case SPDY_SYN_STREAM_FRAME: - if (buffer.readableBytes() < 12) { + minLength = version < 3 ? 12 : 10; + if (buffer.readableBytes() < minLength) { return null; } int offset = buffer.readerIndex(); streamID = getUnsignedInt(buffer, offset); int associatedToStreamID = getUnsignedInt(buffer, offset + 4); - byte priority = (byte) (buffer.getByte(offset + 8) >> 6 & 0x03); + byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07); + if (version < 3) { + priority >>= 1; + } buffer.skipBytes(10); length -= 10; // SPDY/2 requires 16-bits of padding for empty header blocks - if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { + if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { buffer.skipBytes(2); length = 0; } @@ -441,16 +469,23 @@ public class SpdyFrameDecoder extends FrameDecoder { return spdySynStreamFrame; case SPDY_SYN_REPLY_FRAME: - if (buffer.readableBytes() < 8) { + minLength = version < 3 ? 8 : 4; + if (buffer.readableBytes() < minLength) { return null; } streamID = getUnsignedInt(buffer, buffer.readerIndex()); - buffer.skipBytes(6); - length -= 6; + buffer.skipBytes(4); + length -= 4; + + // SPDY/2 has 16-bits of unused space + if (version < 3) { + buffer.skipBytes(2); + length -= 2; + } // SPDY/2 requires 16-bits of padding for empty header blocks - if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { + if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { buffer.skipBytes(2); length = 0; } @@ -461,9 +496,12 @@ public class SpdyFrameDecoder extends FrameDecoder { return spdySynReplyFrame; case SPDY_HEADERS_FRAME: - // Protocol allows length 4 frame when there are no name/value pairs - int minLength = length == 4 ? 4 : 8; - if (buffer.readableBytes() < minLength) { + if (buffer.readableBytes() < 4) { + return null; + } + + // SPDY/2 allows length 4 frame when there are no name/value pairs + if (version < 3 && length > 4 && buffer.readableBytes() < 8) { return null; } @@ -471,16 +509,22 @@ public class SpdyFrameDecoder extends FrameDecoder { buffer.skipBytes(4); length -= 4; - // SPDY/2 requires 16-bits of padding for empty header blocks - if (length == 4 && buffer.getShort(buffer.readerIndex() + 2) == 0) { - buffer.skipBytes(4); - length = 0; - } else if (length != 0) { + // SPDY/2 has 16-bits of unused space + if (version < 3 && length != 0) { buffer.skipBytes(2); length -= 2; } - return new DefaultSpdyHeadersFrame(streamID); + // SPDY/2 requires 16-bits of padding for empty header blocks + if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) { + buffer.skipBytes(2); + length = 0; + } + + SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID); + spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0); + + return spdyHeadersFrame; default: throw new Error("Shouldn't reach here."); @@ -497,7 +541,11 @@ public class SpdyFrameDecoder extends FrameDecoder { } private int readLengthField() { - return decompressed.readUnsignedShort(); + if (version < 3) { + return decompressed.readUnsignedShort(); + } else { + return decompressed.readInt(); + } } private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception { @@ -519,7 +567,7 @@ public class SpdyFrameDecoder extends FrameDecoder { return; } - int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields + int lengthFieldSize = version < 3 ? 2 : 4; if (numHeaders == -1) { // Read number of Name/Value pairs @@ -527,6 +575,10 @@ public class SpdyFrameDecoder extends FrameDecoder { return; } numHeaders = readLengthField(); + if (numHeaders < 0) { + spdyHeaderBlock.setInvalid(); + return; + } } while (numHeaders > 0) { @@ -542,7 +594,7 @@ public class SpdyFrameDecoder extends FrameDecoder { int nameLength = readLengthField(); // Recipients of a zero-length name must issue a stream error - if (nameLength == 0) { + if (nameLength <= 0) { spdyHeaderBlock.setInvalid(); return; } @@ -577,7 +629,7 @@ public class SpdyFrameDecoder extends FrameDecoder { int valueLength = readLengthField(); // Recipients of illegal value fields must issue a stream error - if (valueLength == 0) { + if (valueLength <= 0) { spdyHeaderBlock.setInvalid(); return; } @@ -630,10 +682,10 @@ public class SpdyFrameDecoder extends FrameDecoder { private boolean isValidControlFrameHeader() { switch (type) { case SPDY_SYN_STREAM_FRAME: - return length >= 12; + return version < 3 ? length >= 12 : length >= 10; case SPDY_SYN_REPLY_FRAME: - return length >= 8; + return version < 3 ? length >= 8 : length >= 4; case SPDY_RST_STREAM_FRAME: return flags == 0 && length == 8; @@ -648,14 +700,19 @@ public class SpdyFrameDecoder extends FrameDecoder { return length == 4; case SPDY_GOAWAY_FRAME: - return length == 4; + return version < 3 ? length == 4 : length == 8; case SPDY_HEADERS_FRAME: - return length == 4 || length >= 8; + if (version < 3) { + return length == 4 || length >= 8; + } else { + return length >= 4; + } case SPDY_WINDOW_UPDATE_FRAME: return length == 8; + case SPDY_CREDENTIAL_FRAME: default: return true; } @@ -674,6 +731,7 @@ public class SpdyFrameDecoder extends FrameDecoder { return true; case SPDY_NOOP_FRAME: + case SPDY_CREDENTIAL_FRAME: default: return false; } @@ -717,6 +775,10 @@ public class SpdyFrameDecoder extends FrameDecoder { case SPDY_WINDOW_UPDATE_FRAME: message = "Received invalid WINDOW_UPDATE control frame"; break; + + case SPDY_CREDENTIAL_FRAME: + message = "Received invalid CREDENTIAL control frame"; + break; } fireProtocolException(ctx, message); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java index 309c3551e3..88dfa4a70d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java @@ -33,23 +33,31 @@ import io.netty.handler.codec.oneone.OneToOneEncoder; */ public class SpdyFrameEncoder extends OneToOneEncoder { + private final int version; private volatile boolean finished; private final SpdyHeaderBlockCompressor headerBlockCompressor; /** - * Creates a new instance with the default {@code compressionLevel (6)}, - * {@code windowBits (15)}, and {@code memLevel (8)}. + * Creates a new instance with the specified {@code version} and the + * default {@code compressionLevel (6)}, {@code windowBits (15)}, + * and {@code memLevel (8)}. */ - public SpdyFrameEncoder() { - this(6, 15, 8); + public SpdyFrameEncoder(int version) { + this(version, 6, 15, 8); } /** * Creates a new instance with the specified parameters. */ - public SpdyFrameEncoder(int compressionLevel, int windowBits, int memLevel) { + public SpdyFrameEncoder(int version, int compressionLevel, int windowBits, int memLevel) { super(); - headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(compressionLevel, windowBits, memLevel); + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unknown version: " + version); + } + this.version = version; + headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance( + version, compressionLevel, windowBits, memLevel); } @Override @@ -93,23 +101,37 @@ public class SpdyFrameEncoder extends OneToOneEncoder { SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; ChannelBuffer data = compressHeaderBlock( - encodeHeaderBlock(spdySynStreamFrame)); + encodeHeaderBlock(version, spdySynStreamFrame)); byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0; if (spdySynStreamFrame.isUnidirectional()) { flags |= SPDY_FLAG_UNIDIRECTIONAL; } int headerBlockLength = data.readableBytes(); - int length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength; + int length; + if (version < 3) { + length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength; + } else { + length = 10 + headerBlockLength; + } ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_SYN_STREAM_FRAME); frame.writeByte(flags); frame.writeMedium(length); frame.writeInt(spdySynStreamFrame.getStreamID()); frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID()); - frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 14); - if (data.readableBytes() == 0) { + if (version < 3) { + // Restrict priorities for SPDY/2 to between 0 and 3 + byte priority = spdySynStreamFrame.getPriority(); + if (priority > 3) { + priority = 3; + } + frame.writeShort((priority & 0xFF) << 14); + } else { + frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 13); + } + if (version < 3 && data.readableBytes() == 0) { frame.writeShort(0); } return ChannelBuffers.wrappedBuffer(frame, data); @@ -118,21 +140,28 @@ public class SpdyFrameEncoder extends OneToOneEncoder { SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; ChannelBuffer data = compressHeaderBlock( - encodeHeaderBlock(spdySynReplyFrame)); + encodeHeaderBlock(version, spdySynReplyFrame)); byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0; int headerBlockLength = data.readableBytes(); - int length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength; + int length; + if (version < 3) { + length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength; + } else { + length = 4 + headerBlockLength; + } ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_SYN_REPLY_FRAME); frame.writeByte(flags); frame.writeMedium(length); frame.writeInt(spdySynReplyFrame.getStreamID()); - if (data.readableBytes() == 0) { - frame.writeInt(0); - } else { - frame.writeShort(0); + if (version < 3) { + if (data.readableBytes() == 0) { + frame.writeInt(0); + } else { + frame.writeShort(0); + } } return ChannelBuffers.wrappedBuffer(frame, data); @@ -141,7 +170,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_RST_STREAM_FRAME); frame.writeInt(8); frame.writeInt(spdyRstStreamFrame.getStreamID()); @@ -158,7 +187,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { int length = 4 + numEntries * 8; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_SETTINGS_FRAME); frame.writeByte(flags); frame.writeMedium(length); @@ -172,13 +201,18 @@ public class SpdyFrameEncoder extends OneToOneEncoder { if (spdySettingsFrame.isPersisted(id)) { ID_flags |= SPDY_SETTINGS_PERSISTED; } - // Chromium Issue 79156 - // SPDY setting ids are not written in network byte order - // Write id assuming the architecture is little endian - frame.writeByte(id >> 0 & 0xFF); - frame.writeByte(id >> 8 & 0xFF); - frame.writeByte(id >> 16 & 0xFF); - frame.writeByte(ID_flags); + if (version < 3) { + // Chromium Issue 79156 + // SPDY setting ids are not written in network byte order + // Write id assuming the architecture is little endian + frame.writeByte(id >> 0 & 0xFF); + frame.writeByte(id >> 8 & 0xFF); + frame.writeByte(id >> 16 & 0xFF); + frame.writeByte(ID_flags); + } else { + frame.writeByte(ID_flags); + frame.writeMedium(id); + } frame.writeInt(spdySettingsFrame.getValue(id)); } return frame; @@ -187,7 +221,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_NOOP_FRAME); frame.writeInt(0); return frame; @@ -197,7 +231,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_PING_FRAME); frame.writeInt(4); frame.writeInt(spdyPingFrame.getID()); @@ -206,28 +240,39 @@ public class SpdyFrameEncoder extends OneToOneEncoder { } else if (msg instanceof SpdyGoAwayFrame) { SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg; + int length = version < 3 ? 4 : 8; ChannelBuffer frame = ChannelBuffers.buffer( - ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4); - frame.writeShort(SPDY_VERSION | 0x8000); + ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_GOAWAY_FRAME); - frame.writeInt(4); + frame.writeInt(length); frame.writeInt(spdyGoAwayFrame.getLastGoodStreamID()); + if (version >= 3) { + frame.writeInt(spdyGoAwayFrame.getStatus().getCode()); + } return frame; } else if (msg instanceof SpdyHeadersFrame) { SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; ChannelBuffer data = compressHeaderBlock( - encodeHeaderBlock(spdyHeadersFrame)); + encodeHeaderBlock(version, spdyHeadersFrame)); + byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0; int headerBlockLength = data.readableBytes(); - int length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength; + int length; + if (version < 3) { + length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength; + } else { + length = 4 + headerBlockLength; + } ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_HEADERS_FRAME); - frame.writeInt(length); + frame.writeByte(flags); + frame.writeMedium(length); frame.writeInt(spdyHeadersFrame.getStreamID()); - if (data.readableBytes() != 0) { + if (version < 3 && data.readableBytes() != 0) { frame.writeShort(0); } return ChannelBuffers.wrappedBuffer(frame, data); @@ -237,7 +282,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg; ChannelBuffer frame = ChannelBuffers.buffer( ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8); - frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(version | 0x8000); frame.writeShort(SPDY_WINDOW_UPDATE_FRAME); frame.writeInt(8); frame.writeInt(spdyWindowUpdateFrame.getStreamID()); @@ -249,7 +294,23 @@ public class SpdyFrameEncoder extends OneToOneEncoder { return msg; } - private static ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame) + private static void writeLengthField(int version, ChannelBuffer buffer, int length) { + if (version < 3) { + buffer.writeShort(length); + } else { + buffer.writeInt(length); + } + } + + private static void setLengthField(int version, ChannelBuffer buffer, int writerIndex, int length) { + if (version < 3) { + buffer.setShort(writerIndex, length); + } else { + buffer.setInt(writerIndex, length); + } + } + + private static ChannelBuffer encodeHeaderBlock(int version, SpdyHeaderBlock headerFrame) throws Exception { Set names = headerFrame.getHeaderNames(); int numHeaders = names.size(); @@ -262,14 +323,14 @@ public class SpdyFrameEncoder extends OneToOneEncoder { } ChannelBuffer headerBlock = ChannelBuffers.dynamicBuffer( ByteOrder.BIG_ENDIAN, 256); - headerBlock.writeShort(numHeaders); + writeLengthField(version, headerBlock, numHeaders); for (String name: names) { byte[] nameBytes = name.getBytes("UTF-8"); - headerBlock.writeShort(nameBytes.length); + writeLengthField(version, headerBlock, nameBytes.length); headerBlock.writeBytes(nameBytes); int savedIndex = headerBlock.writerIndex(); int valueLength = 0; - headerBlock.writeShort(valueLength); + writeLengthField(version, headerBlock, valueLength); for (String value: headerFrame.getHeaders(name)) { byte[] valueBytes = value.getBytes("UTF-8"); headerBlock.writeBytes(valueBytes); @@ -281,7 +342,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder { throw new IllegalArgumentException( "header exceeds allowable length: " + name); } - headerBlock.setShort(savedIndex, valueLength); + setLengthField(version, headerBlock, savedIndex, valueLength); headerBlock.writerIndex(headerBlock.writerIndex() - 1); } return headerBlock; diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java index 890e464406..6abb5080a8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyGoAwayFrame.java @@ -30,4 +30,14 @@ public interface SpdyGoAwayFrame { * cannot be negative. */ void setLastGoodStreamID(int lastGoodStreamID); + + /** + * Returns the status of this frame. + */ + SpdySessionStatus getStatus(); + + /** + * Sets the status of this frame. + */ + void setStatus(SpdySessionStatus status); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java index af51caf6b1..57f8060304 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java @@ -21,13 +21,14 @@ import io.netty.util.internal.DetectionUtil; abstract class SpdyHeaderBlockCompressor { static SpdyHeaderBlockCompressor newInstance( - int compressionLevel, int windowBits, int memLevel) { + int version, int compressionLevel, int windowBits, int memLevel) { if (DetectionUtil.javaVersion() >= 7) { - return new SpdyHeaderBlockZlibCompressor(compressionLevel); + return new SpdyHeaderBlockZlibCompressor( + version, compressionLevel); } else { return new SpdyHeaderBlockJZlibCompressor( - compressionLevel, windowBits, memLevel); + version, compressionLevel, windowBits, memLevel); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java index 23b9f4a04b..b861249d83 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java @@ -19,8 +19,8 @@ import io.netty.buffer.ChannelBuffer; abstract class SpdyHeaderBlockDecompressor { - static SpdyHeaderBlockDecompressor newInstance() { - return new SpdyHeaderBlockZlibDecompressor(); + static SpdyHeaderBlockDecompressor newInstance(int version) { + return new SpdyHeaderBlockZlibDecompressor(version); } abstract void setInput(ChannelBuffer compressed); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java index dc6e7db1ff..b7236fab0a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java @@ -26,7 +26,12 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor { private final ZStream z = new ZStream(); - public SpdyHeaderBlockJZlibCompressor(int compressionLevel, int windowBits, int memLevel) { + public SpdyHeaderBlockJZlibCompressor( + int version, int compressionLevel, int windowBits, int memLevel) { + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } if (compressionLevel < 0 || compressionLevel > 9) { throw new IllegalArgumentException( "compressionLevel: " + compressionLevel + " (expected: 0-9)"); @@ -46,7 +51,11 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor { throw new CompressionException( "failed to initialize an SPDY header block deflater: " + resultCode); } else { - resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length); + if (version < 3) { + resultCode = z.deflateSetDictionary(SPDY2_DICT, SPDY2_DICT.length); + } else { + resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length); + } if (resultCode != JZlib.Z_OK) { throw new CompressionException( "failed to set the SPDY dictionary: " + resultCode); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java index d00434b120..dad8ee647f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java @@ -26,13 +26,21 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor { private final byte[] out = new byte[8192]; private final Deflater compressor; - public SpdyHeaderBlockZlibCompressor(int compressionLevel) { + public SpdyHeaderBlockZlibCompressor(int version, int compressionLevel) { + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } if (compressionLevel < 0 || compressionLevel > 9) { throw new IllegalArgumentException( "compressionLevel: " + compressionLevel + " (expected: 0-9)"); } compressor = new Deflater(compressionLevel); - compressor.setDictionary(SPDY_DICT); + if (version < 3) { + compressor.setDictionary(SPDY2_DICT); + } else { + compressor.setDictionary(SPDY_DICT); + } } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibDecompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibDecompressor.java index 87f5ba09e9..1fa8e7b038 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibDecompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibDecompressor.java @@ -24,9 +24,18 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor { + private final int version; private final byte[] out = new byte[8192]; private final Inflater decompressor = new Inflater(); + public SpdyHeaderBlockZlibDecompressor(int version) { + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } + this.version = version; + } + @Override public void setInput(ChannelBuffer compressed) { byte[] in = new byte[compressed.readableBytes()]; @@ -39,7 +48,11 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor { try { int numBytes = decompressor.inflate(out); if (numBytes == 0 && decompressor.needsDictionary()) { - decompressor.setDictionary(SPDY_DICT); + if (version < 3) { + decompressor.setDictionary(SPDY2_DICT); + } else { + decompressor.setDictionary(SPDY_DICT); + } numBytes = decompressor.inflate(out); } decompressed.writeBytes(out, 0, numBytes); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java index 6236931c0c..b5001688a7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java @@ -27,7 +27,7 @@ import io.netty.handler.codec.http.HttpVersion; /** * Provides the constants for the standard SPDY HTTP header names and commonly - * used utility methods that access an {@link SpdyHeaderBlock}. + * used utility methods that access a {@link SpdyHeaderBlock}. * @apiviz.stereotype static */ public class SpdyHeaders { @@ -37,6 +37,41 @@ public class SpdyHeaders { * @apiviz.stereotype static */ public static final class HttpNames { + /** + * {@code ":host"} + */ + public static final String HOST = ":host"; + /** + * {@code ":method"} + */ + public static final String METHOD = ":method"; + /** + * {@code ":path"} + */ + public static final String PATH = ":path"; + /** + * {@code ":scheme"} + */ + public static final String SCHEME = ":scheme"; + /** + * {@code ":status"} + */ + public static final String STATUS = ":status"; + /** + * {@code ":version"} + */ + public static final String VERSION = ":version"; + + private HttpNames() { + super(); + } + } + + /** + * SPDY/2 HTTP header names + * @apiviz.stereotype static + */ + public static final class Spdy2HttpNames { /** * {@code "method"} */ @@ -58,7 +93,7 @@ public class SpdyHeaders { */ public static final String VERSION = "version"; - private HttpNames() { + private Spdy2HttpNames() { super(); } } @@ -115,64 +150,118 @@ public class SpdyHeaders { } /** - * Removes the {@code "method"} header. + * Removes the SPDY host header. */ - public static void removeMethod(SpdyHeaderBlock block) { - block.removeHeader(HttpNames.METHOD); + public static void removeHost(SpdyHeaderBlock block) { + block.removeHeader(HttpNames.HOST); } /** - * Returns the {@link HttpMethod} represented by the {@code "method"} header. + * Returns the SPDY host header. */ - public static HttpMethod getMethod(SpdyHeaderBlock block) { + public static String getHost(SpdyHeaderBlock block) { + return block.getHeader(HttpNames.HOST); + } + + /** + * Set the SPDY host header. + */ + public static void setHost(SpdyHeaderBlock block, String host) { + block.setHeader(HttpNames.HOST, host); + } + + /** + * Removes the HTTP method header. + */ + public static void removeMethod(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 3) { + block.removeHeader(Spdy2HttpNames.METHOD); + } else { + block.removeHeader(HttpNames.METHOD); + } + } + + /** + * Returns the {@link HttpMethod} represented by the HTTP method header. + */ + public static HttpMethod getMethod(int spdyVersion, SpdyHeaderBlock block) { try { - return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD)); + if (spdyVersion < 3) { + return HttpMethod.valueOf(block.getHeader(Spdy2HttpNames.METHOD)); + } else { + return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD)); + } } catch (Exception e) { return null; } } /** - * Sets the {@code "method"} header. + * Sets the HTTP method header. */ - public static void setMethod(SpdyHeaderBlock block, HttpMethod method) { - block.setHeader(HttpNames.METHOD, method.getName()); + public static void setMethod(int spdyVersion, SpdyHeaderBlock block, HttpMethod method) { + if (spdyVersion < 3) { + block.setHeader(Spdy2HttpNames.METHOD, method.getName()); + } else { + block.setHeader(HttpNames.METHOD, method.getName()); + } } /** - * Removes the {@code "scheme"} header. + * Removes the URL scheme header. */ - public static void removeScheme(SpdyHeaderBlock block) { - block.removeHeader(HttpNames.SCHEME); + public static void removeScheme(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 2) { + block.removeHeader(Spdy2HttpNames.SCHEME); + } else { + block.removeHeader(HttpNames.SCHEME); + } } /** - * Returns the value of the {@code "scheme"} header. + * Returns the value of the URL scheme header. */ - public static String getScheme(SpdyHeaderBlock block) { - return block.getHeader(HttpNames.SCHEME); + public static String getScheme(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 3) { + return block.getHeader(Spdy2HttpNames.SCHEME); + } else { + return block.getHeader(HttpNames.SCHEME); + } } /** - * Sets the {@code "scheme"} header. + * Sets the URL scheme header. */ - public static void setScheme(SpdyHeaderBlock block, String value) { - block.setHeader(HttpNames.SCHEME, value); + public static void setScheme(int spdyVersion, SpdyHeaderBlock block, String scheme) { + if (spdyVersion < 3) { + block.setHeader(Spdy2HttpNames.SCHEME, scheme); + } else { + block.setHeader(HttpNames.SCHEME, scheme); + } } /** - * Removes the {@code "status"} header. + * Removes the HTTP response status header. */ - public static void removeStatus(SpdyHeaderBlock block) { - block.removeHeader(HttpNames.STATUS); + public static void removeStatus(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 3) { + block.removeHeader(Spdy2HttpNames.STATUS); + } else { + block.removeHeader(HttpNames.STATUS); + } } /** - * Returns the {@link HttpResponseStatus} represented by the {@code "status"} header. + * Returns the {@link HttpResponseStatus} represented by the HTTP response status header. */ - public static HttpResponseStatus getStatus(SpdyHeaderBlock block) { + public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeaderBlock block) { try { - String status = block.getHeader(HttpNames.STATUS); + String status; + if (spdyVersion < 3) { + status = block.getHeader(Spdy2HttpNames.STATUS); + } else { + status = block.getHeader(HttpNames.STATUS); + } int space = status.indexOf(' '); if (space == -1) { return HttpResponseStatus.valueOf(Integer.parseInt(status)); @@ -180,7 +269,7 @@ public class SpdyHeaders { int code = Integer.parseInt(status.substring(0, space)); String reasonPhrase = status.substring(space + 1); HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code); - if (responseStatus.getReasonPhrase().equals(responseStatus)) { + if (responseStatus.getReasonPhrase().equals(reasonPhrase)) { return responseStatus; } else { return new HttpResponseStatus(code, reasonPhrase); @@ -192,56 +281,84 @@ public class SpdyHeaders { } /** - * Sets the {@code "status"} header. + * Sets the HTTP response status header. */ - public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) { - block.setHeader(HttpNames.STATUS, status.toString()); + public static void setStatus(int spdyVersion, SpdyHeaderBlock block, HttpResponseStatus status) { + if (spdyVersion < 3) { + block.setHeader(Spdy2HttpNames.STATUS, status.toString()); + } else { + block.setHeader(HttpNames.STATUS, status.toString()); + } } /** - * Removes the {@code "url"} header. + * Removes the URL path header. */ - public static void removeUrl(SpdyHeaderBlock block) { - block.removeHeader(HttpNames.URL); + public static void removeUrl(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 3) { + block.removeHeader(Spdy2HttpNames.URL); + } else { + block.removeHeader(HttpNames.PATH); + } } /** - * Returns the value of the {@code "url"} header. + * Returns the value of the URL path header. */ - public static String getUrl(SpdyHeaderBlock block) { - return block.getHeader(HttpNames.URL); + public static String getUrl(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 3) { + return block.getHeader(Spdy2HttpNames.URL); + } else { + return block.getHeader(HttpNames.PATH); + } } /** - * Sets the {@code "url"} header. + * Sets the URL path header. */ - public static void setUrl(SpdyHeaderBlock block, String value) { - block.setHeader(HttpNames.URL, value); + public static void setUrl(int spdyVersion, SpdyHeaderBlock block, String path) { + if (spdyVersion < 3) { + block.setHeader(Spdy2HttpNames.URL, path); + } else { + block.setHeader(HttpNames.PATH, path); + } } /** - * Removes the {@code "version"} header. + * Removes the HTTP version header. */ - public static void removeVersion(SpdyHeaderBlock block) { - block.removeHeader(HttpNames.VERSION); + public static void removeVersion(int spdyVersion, SpdyHeaderBlock block) { + if (spdyVersion < 3) { + block.removeHeader(Spdy2HttpNames.VERSION); + } else { + block.removeHeader(HttpNames.VERSION); + } } /** - * Returns the {@link HttpVersion} represented by the {@code "version"} header. + * Returns the {@link HttpVersion} represented by the HTTP version header. */ - public static HttpVersion getVersion(SpdyHeaderBlock block) { + public static HttpVersion getVersion(int spdyVersion, SpdyHeaderBlock block) { try { - return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION)); + if (spdyVersion < 3) { + return HttpVersion.valueOf(block.getHeader(Spdy2HttpNames.VERSION)); + } else { + return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION)); + } } catch (Exception e) { return null; } } /** - * Sets the {@code "version"} header. + * Sets the HTTP version header. */ - public static void setVersion(SpdyHeaderBlock block, HttpVersion version) { - block.setHeader(HttpNames.VERSION, version.getText()); + public static void setVersion(int spdyVersion, SpdyHeaderBlock block, HttpVersion httpVersion) { + if (spdyVersion < 3) { + block.setHeader(Spdy2HttpNames.VERSION, httpVersion.getText()); + } else { + block.setHeader(HttpNames.VERSION, httpVersion.getText()); + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java index 4d1dac9d11..f136f378d7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeadersFrame.java @@ -29,4 +29,15 @@ public interface SpdyHeadersFrame extends SpdyHeaderBlock { * Sets the Stream-ID of this frame. The Stream-ID must be positive. */ void setStreamID(int streamID); + + /** + * Returns {@code true} if this frame is the last frame to be transmitted + * on the stream. + */ + boolean isLast(); + + /** + * Sets if this frame is the last frame to be transmitted on the stream. + */ + void setLast(boolean last); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java index 1e56429002..536b881a27 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java @@ -28,13 +28,14 @@ import io.netty.channel.ChannelUpstreamHandler; public class SpdyHttpCodec implements ChannelUpstreamHandler, ChannelDownstreamHandler { private final SpdyHttpDecoder decoder; - private final SpdyHttpEncoder encoder = new SpdyHttpEncoder(); + private final SpdyHttpEncoder encoder; /** * Creates a new instance with the specified decoder options. */ - public SpdyHttpCodec(int maxContentLength) { - decoder = new SpdyHttpDecoder(maxContentLength); + public SpdyHttpCodec(int version, int maxContentLength) { + decoder = new SpdyHttpDecoder(version, maxContentLength); + encoder = new SpdyHttpEncoder(version); } public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java index 0a6245bc9a..af13da2161 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.spdy; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,22 +44,29 @@ import io.netty.handler.codec.oneone.OneToOneDecoder; */ public class SpdyHttpDecoder extends OneToOneDecoder { + private final int spdyVersion; private final int maxContentLength; private final Map messageMap = new HashMap(); /** * Creates a new instance. * + * @param version the protocol version * @param maxContentLength the maximum length of the message content. * If the length of the message content exceeds this value, * a {@link TooLongFrameException} will be raised. */ - public SpdyHttpDecoder(int maxContentLength) { + public SpdyHttpDecoder(int version, int maxContentLength) { super(); + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } if (maxContentLength <= 0) { throw new IllegalArgumentException( "maxContentLength must be a positive integer: " + maxContentLength); } + spdyVersion = version; this.maxContentLength = maxContentLength; } @@ -72,7 +81,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { int streamID = spdySynStreamFrame.getStreamID(); if (SpdyCodecUtil.isServerID(streamID)) { - // SYN_STREAM frames inititated by the server are pushed resources + // SYN_STREAM frames initiated by the server are pushed resources int associatedToStreamID = spdySynStreamFrame.getAssociatedToStreamID(); // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0 @@ -83,7 +92,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame); } - String URL = SpdyHeaders.getUrl(spdySynStreamFrame); + String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame); // If a client receives a SYN_STREAM without a 'url' header // it must reply with a RST_STREAM with error code PROTOCOL_ERROR @@ -94,7 +103,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { } try { - HttpResponse httpResponse = createHttpResponse(spdySynStreamFrame); + HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame); // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers SpdyHttpHeaders.setStreamID(httpResponse, streamID); @@ -118,7 +127,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { } else { // SYN_STREAM frames initiated by the client are HTTP requests try { - HttpRequest httpRequest = createHttpRequest(spdySynStreamFrame); + HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame); // Set the Stream-ID as a header SpdyHttpHeaders.setStreamID(httpRequest, streamID); @@ -130,13 +139,13 @@ public class SpdyHttpDecoder extends OneToOneDecoder { messageMap.put(new Integer(streamID), httpRequest); } } catch (Exception e) { - // If a client sends a SYN_STREAM without method, url, and version headers - // the server must reply with a HTTP 400 BAD REQUEST reply + // If a client sends a SYN_STREAM without all of the method, url (host and path), + // scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply. // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); spdySynReplyFrame.setLast(true); - SpdyHeaders.setStatus(spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST); - SpdyHeaders.setVersion(spdySynReplyFrame, HttpVersion.HTTP_1_0); + SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST); + SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0); Channels.write(ctx, Channels.future(channel), spdySynReplyFrame); } } @@ -147,7 +156,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder { int streamID = spdySynReplyFrame.getStreamID(); try { - HttpResponse httpResponse = createHttpResponse(spdySynReplyFrame); + HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame); // Set the Stream-ID as a header SpdyHttpHeaders.setStreamID(httpResponse, streamID); @@ -215,67 +224,72 @@ public class SpdyHttpDecoder extends OneToOneDecoder { messageMap.remove(streamID); return httpMessage; } + + } else if (msg instanceof SpdyRstStreamFrame) { + + SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; + Integer streamID = new Integer(spdyRstStreamFrame.getStreamID()); + messageMap.remove(streamID); } return null; } - private static HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame) + private static HttpRequest createHttpRequest(int spdyVersion, SpdyHeaderBlock requestFrame) throws Exception { // Create the first line of the request from the name/value pairs - HttpMethod method = SpdyHeaders.getMethod(requestFrame); - String url = SpdyHeaders.getUrl(requestFrame); - HttpVersion version = SpdyHeaders.getVersion(requestFrame); - SpdyHeaders.removeMethod(requestFrame); - SpdyHeaders.removeUrl(requestFrame); - SpdyHeaders.removeVersion(requestFrame); + HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame); + String url = SpdyHeaders.getUrl(spdyVersion, requestFrame); + HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame); + SpdyHeaders.removeMethod(spdyVersion, requestFrame); + SpdyHeaders.removeUrl(spdyVersion, requestFrame); + SpdyHeaders.removeVersion(spdyVersion, requestFrame); - HttpRequest httpRequest = new DefaultHttpRequest(version, method, url); - for (Map.Entry e: requestFrame.getHeaders()) { - httpRequest.addHeader(e.getKey(), e.getValue()); + HttpRequest httpRequest = new DefaultHttpRequest(httpVersion, method, url); + + // Remove the scheme header + SpdyHeaders.removeScheme(spdyVersion, requestFrame); + + if (spdyVersion >= 3) { + // Replace the SPDY host header with the HTTP host header + String host = SpdyHeaders.getHost(requestFrame); + SpdyHeaders.removeHost(requestFrame); + HttpHeaders.setHost(httpRequest, host); } - // Chunked encoding is no longer valid - List encodings = httpRequest.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); - encodings.remove(HttpHeaders.Values.CHUNKED); - if (encodings.isEmpty()) { - httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); - } else { - httpRequest.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings); + for (Map.Entry e: requestFrame.getHeaders()) { + httpRequest.addHeader(e.getKey(), e.getValue()); } // The Connection and Keep-Alive headers are no longer valid HttpHeaders.setKeepAlive(httpRequest, true); + // Transfer-Encoding header is not valid + httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + return httpRequest; } - private static HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame) + private static HttpResponse createHttpResponse(int spdyVersion, SpdyHeaderBlock responseFrame) throws Exception { // Create the first line of the response from the name/value pairs - HttpResponseStatus status = SpdyHeaders.getStatus(responseFrame); - HttpVersion version = SpdyHeaders.getVersion(responseFrame); - SpdyHeaders.removeStatus(responseFrame); - SpdyHeaders.removeVersion(responseFrame); + HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame); + HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame); + SpdyHeaders.removeStatus(spdyVersion, responseFrame); + SpdyHeaders.removeVersion(spdyVersion, responseFrame); HttpResponse httpResponse = new DefaultHttpResponse(version, status); for (Map.Entry e: responseFrame.getHeaders()) { httpResponse.addHeader(e.getKey(), e.getValue()); } - // Chunked encoding is no longer valid - List encodings = httpResponse.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); - encodings.remove(HttpHeaders.Values.CHUNKED); - if (encodings.isEmpty()) { - httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); - } else { - httpResponse.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings); - } - httpResponse.removeHeader(HttpHeaders.Names.TRAILER); - // The Connection and Keep-Alive headers are no longer valid HttpHeaders.setKeepAlive(httpResponse, true); + // Transfer-Encoding header is not valid + httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + httpResponse.removeHeader(HttpHeaders.Names.TRAILER); + return httpResponse; } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java index f235482990..420cd1e410 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.spdy; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; + import java.util.List; import java.util.Map; @@ -51,8 +53,8 @@ import io.netty.handler.codec.http.HttpResponse; * * {@code "X-SPDY-Priority"} * The priority value for this request. - * The priority should be between 0 and 3 inclusive. - * 0 represents the highest priority and 3 represents the lowest. + * The priority should be between 0 and 7 inclusive. + * 0 represents the highest priority and 7 represents the lowest. * This header is optional and defaults to 0. * * @@ -84,21 +86,32 @@ import io.netty.handler.codec.http.HttpResponse; * * * {@code "X-SPDY-Associated-To-Stream-ID"} - * The Stream-ID of the request that inititated this pushed resource. + * The Stream-ID of the request that initiated this pushed resource. * * * {@code "X-SPDY-Priority"} * The priority value for this resource. - * The priority should be between 0 and 3 inclusive. - * 0 represents the highest priority and 3 represents the lowest. + * The priority should be between 0 and 7 inclusive. + * 0 represents the highest priority and 7 represents the lowest. * This header is optional and defaults to 0. * * * {@code "X-SPDY-URL"} - * The full URL for the resource being pushed. + * The absolute path for the resource being pushed. * * * + *

        Required Annotations

        + * + * SPDY requires that all Requests and Pushed Resources contain + * an HTTP "Host" header. + * + *

        Optional Annotations

        + * + * Requests and Pushed Resources must contain a SPDY scheme header. + * This can be set via the {@code "X-SPDY-Scheme"} header but otherwise + * defaults to "https" as that is the most common SPDY deployment. + * *

        Chunked Content

        * * This encoder associates all {@link HttpChunk}s that it receives @@ -112,9 +125,20 @@ import io.netty.handler.codec.http.HttpResponse; */ public class SpdyHttpEncoder implements ChannelDownstreamHandler { + private final int spdyVersion; private volatile int currentStreamID; - public SpdyHttpEncoder() { + /** + * Creates a new instance. + * + * @param version the protocol version + */ + public SpdyHttpEncoder(int version) { + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } + spdyVersion = version; } public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt) @@ -228,15 +252,17 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler { throws Exception { boolean chunked = httpMessage.isChunked(); - // Get the Stream-ID, Associated-To-Stream-ID, Priority, and URL from the headers + // Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers int streamID = SpdyHttpHeaders.getStreamID(httpMessage); int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage); byte priority = SpdyHttpHeaders.getPriority(httpMessage); String URL = SpdyHttpHeaders.getUrl(httpMessage); + String scheme = SpdyHttpHeaders.getScheme(httpMessage); SpdyHttpHeaders.removeStreamID(httpMessage); SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage); SpdyHttpHeaders.removePriority(httpMessage); SpdyHttpHeaders.removeUrl(httpMessage); + SpdyHttpHeaders.removeScheme(httpMessage); // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // headers are not valid and MUST not be sent. @@ -246,24 +272,39 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler { httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority); - for (Map.Entry entry: httpMessage.getHeaders()) { - spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue()); - } // Unfold the first line of the message into name/value pairs - SpdyHeaders.setVersion(spdySynStreamFrame, httpMessage.getProtocolVersion()); if (httpMessage instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest) httpMessage; - SpdyHeaders.setMethod(spdySynStreamFrame, httpRequest.getMethod()); - SpdyHeaders.setUrl(spdySynStreamFrame, httpRequest.getUri()); + SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod()); + SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri()); + SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); } if (httpMessage instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) httpMessage; - SpdyHeaders.setStatus(spdySynStreamFrame, httpResponse.getStatus()); - SpdyHeaders.setUrl(spdySynStreamFrame, URL); + SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus()); + SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL); spdySynStreamFrame.setUnidirectional(true); } + // Replace the HTTP host header with the SPDY host header + if (spdyVersion >= 3) { + String host = HttpHeaders.getHost(httpMessage); + httpMessage.removeHeader(HttpHeaders.Names.HOST); + SpdyHeaders.setHost(spdySynStreamFrame, host); + } + + // Set the SPDY scheme header + if (scheme == null) { + scheme = "https"; + } + SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme); + + // Transfer the remaining HTTP headers + for (Map.Entry entry: httpMessage.getHeaders()) { + spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue()); + } + if (chunked) { currentStreamID = streamID; spdySynStreamFrame.setLast(false); @@ -290,14 +331,16 @@ public class SpdyHttpEncoder implements ChannelDownstreamHandler { httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); + + // Unfold the first line of the response into name/value pairs + SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus()); + SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion()); + + // Transfer the remaining HTTP headers for (Map.Entry entry: httpResponse.getHeaders()) { spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue()); } - // Unfold the first line of the repsonse into name/value pairs - SpdyHeaders.setStatus(spdySynReplyFrame, httpResponse.getStatus()); - SpdyHeaders.setVersion(spdySynReplyFrame, httpResponse.getProtocolVersion()); - if (chunked) { currentStreamID = streamID; spdySynReplyFrame.setLast(false); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java index ceb1af3d53..005a2e4c90 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java @@ -46,6 +46,10 @@ public final class SpdyHttpHeaders { * {@code "X-SPDY-URL"} */ public static final String URL = "X-SPDY-URL"; + /** + * {@code "X-SPDY-Scheme"} + */ + public static final String SCHEME = "X-SPDY-Scheme"; private Names() { super(); @@ -144,4 +148,25 @@ public final class SpdyHttpHeaders { public static void setUrl(HttpMessage message, String url) { message.setHeader(Names.URL, url); } + + /** + * Removes the {@code "X-SPDY-Scheme"} header. + */ + public static void removeScheme(HttpMessage message) { + message.removeHeader(Names.SCHEME); + } + + /** + * Returns the value of the {@code "X-SPDY-Scheme"} header. + */ + public static String getScheme(HttpMessage message) { + return message.getHeader(Names.SCHEME); + } + + /** + * Sets the {@code "X-SPDY-Scheme"} header. + */ + public static void setScheme(HttpMessage message, String scheme) { + message.setHeader(Names.URL, scheme); + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java index 4c68061ca0..c7457b6e7d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java @@ -15,11 +15,20 @@ */ package io.netty.handler.codec.spdy; +import java.util.Comparator; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +import io.netty.channel.MessageEvent; final class SpdySession { + private static final SpdyProtocolException STREAM_CLOSED = new SpdyProtocolException("Stream closed"); + private final Map activeStreams = new ConcurrentHashMap(); @@ -38,17 +47,35 @@ final class SpdySession { return activeStreams.containsKey(new Integer(streamID)); } - public void acceptStream(int streamID, boolean remoteSideClosed, boolean localSideClosed) { + // Stream-IDs should be iterated in priority order + public Set getActiveStreams() { + TreeSet StreamIDs = new TreeSet(new PriorityComparator()); + StreamIDs.addAll(activeStreams.keySet()); + return StreamIDs; + } + + public void acceptStream( + int streamID, byte priority, boolean remoteSideClosed, boolean localSideClosed, + int sendWindowSize, int receiveWindowSize) { if (!remoteSideClosed || !localSideClosed) { - activeStreams.put(new Integer(streamID), - new StreamState(remoteSideClosed, localSideClosed)); + activeStreams.put( + new Integer(streamID), + new StreamState(priority, remoteSideClosed, localSideClosed, sendWindowSize, receiveWindowSize)); } return; } public void removeStream(int streamID) { - activeStreams.remove(new Integer(streamID)); - return; + Integer StreamID = new Integer(streamID); + StreamState state = activeStreams.get(StreamID); + activeStreams.remove(StreamID); + if (state != null) { + MessageEvent e = state.removePendingWrite(); + while (e != null) { + e.getFuture().setFailure(STREAM_CLOSED); + e = state.removePendingWrite(); + } + } } public boolean isRemoteSideClosed(int streamID) { @@ -83,6 +110,11 @@ final class SpdySession { } } + /* + * hasReceivedReply and receivedReply are only called from messageReceived + * no need to synchronize access to the StreamState + */ + public boolean hasReceivedReply(int streamID) { StreamState state = activeStreams.get(new Integer(streamID)); return (state != null) && state.hasReceivedReply(); @@ -95,15 +127,77 @@ final class SpdySession { } } + public int getSendWindowSize(int streamID) { + StreamState state = activeStreams.get(new Integer(streamID)); + return state != null ? state.getSendWindowSize() : -1; + } + + public int updateSendWindowSize(int streamID, int deltaWindowSize) { + StreamState state = activeStreams.get(new Integer(streamID)); + return state != null ? state.updateSendWindowSize(deltaWindowSize) : -1; + } + + public int updateReceiveWindowSize(int streamID, int deltaWindowSize) { + StreamState state = activeStreams.get(new Integer(streamID)); + if (deltaWindowSize > 0) { + state.setReceiveWindowSizeLowerBound(0); + } + return state != null ? state.updateReceiveWindowSize(deltaWindowSize) : -1; + } + + public int getReceiveWindowSizeLowerBound(int streamID) { + StreamState state = activeStreams.get(new Integer(streamID)); + return state != null ? state.getReceiveWindowSizeLowerBound() : 0; + } + + public void updateAllReceiveWindowSizes(int deltaWindowSize) { + for (StreamState state: activeStreams.values()) { + state.updateReceiveWindowSize(deltaWindowSize); + if (deltaWindowSize < 0) { + state.setReceiveWindowSizeLowerBound(deltaWindowSize); + } + } + } + + public boolean putPendingWrite(int streamID, MessageEvent evt) { + StreamState state = activeStreams.get(new Integer(streamID)); + return state != null && state.putPendingWrite(evt); + } + + public MessageEvent getPendingWrite(int streamID) { + StreamState state = activeStreams.get(new Integer(streamID)); + return state != null ? state.getPendingWrite() : null; + } + + public MessageEvent removePendingWrite(int streamID) { + StreamState state = activeStreams.get(new Integer(streamID)); + return state != null ? state.removePendingWrite() : null; + } + private static final class StreamState { - private boolean remoteSideClosed; - private boolean localSideClosed; + private final byte priority; + private volatile boolean remoteSideClosed; + private volatile boolean localSideClosed; private boolean receivedReply; + private AtomicInteger sendWindowSize; + private AtomicInteger receiveWindowSize; + private volatile int receiveWindowSizeLowerBound; + private ConcurrentLinkedQueue pendingWriteQueue = + new ConcurrentLinkedQueue(); - public StreamState(boolean remoteSideClosed, boolean localSideClosed) { + public StreamState( + byte priority, boolean remoteSideClosed, boolean localSideClosed, + int sendWindowSize, int receiveWindowSize) { + this.priority = priority; this.remoteSideClosed = remoteSideClosed; this.localSideClosed = localSideClosed; + this.sendWindowSize = new AtomicInteger(sendWindowSize); + this.receiveWindowSize = new AtomicInteger(receiveWindowSize); + } + + public byte getPriority() { + return priority; } public boolean isRemoteSideClosed() { @@ -129,5 +223,50 @@ final class SpdySession { public void receivedReply() { receivedReply = true; } + + public int getSendWindowSize() { + return sendWindowSize.get(); + } + + public int updateSendWindowSize(int deltaWindowSize) { + return sendWindowSize.addAndGet(deltaWindowSize); + } + + public int updateReceiveWindowSize(int deltaWindowSize) { + return receiveWindowSize.addAndGet(deltaWindowSize); + } + + public int getReceiveWindowSizeLowerBound() { + return receiveWindowSizeLowerBound; + } + + public void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) { + this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound; + } + + public boolean putPendingWrite(MessageEvent evt) { + return pendingWriteQueue.offer(evt); + } + + public MessageEvent getPendingWrite() { + return pendingWriteQueue.peek(); + } + + public MessageEvent removePendingWrite() { + return pendingWriteQueue.poll(); + } + } + + private final class PriorityComparator implements Comparator { + + public PriorityComparator() { + super(); + } + + public int compare(Integer id1, Integer id2) { + StreamState state1 = activeStreams.get(id1); + StreamState state2 = activeStreams.get(id2); + return state1.getPriority() - state2.getPriority(); + } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java index b89d136cc5..c4850c0f04 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.spdy; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; + import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.concurrent.atomic.AtomicInteger; @@ -46,6 +48,12 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler private volatile int localConcurrentStreams; private volatile int maxConcurrentStreams; + private static final int DEFAULT_WINDOW_SIZE = 64 * 1024; // 64 KB default initial window size + private volatile int initialSendWindowSize = DEFAULT_WINDOW_SIZE; + private volatile int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE; + + private final Object flowControlLock = new Object(); + private final AtomicInteger pings = new AtomicInteger(); private volatile boolean sentGoAwayFrame; @@ -54,18 +62,25 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler private volatile ChannelFuture closeSessionFuture; private final boolean server; + private final boolean flowControl; /** * Creates a new session handler. * - * @param server {@code true} if and only if this session handler should - * handle the server endpoint of the connection. - * {@code false} if and only if this session handler should - * handle the client endpoint of the connection. + * @param version the protocol version + * @param server {@code true} if and only if this session handler should + * handle the server endpoint of the connection. + * {@code false} if and only if this session handler should + * handle the client endpoint of the connection. */ - public SpdySessionHandler(boolean server) { + public SpdySessionHandler(int version, boolean server) { super(); + if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) { + throw new IllegalArgumentException( + "unsupported version: " + version); + } this.server = server; + this.flowControl = version >= 3; } @Override @@ -78,41 +93,95 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler /* * SPDY Data frame processing requirements: * - * If an endpoint receives a data frame for a Stream-ID which does not exist, - * it must return a RST_STREAM with error code INVALID_STREAM for the Stream-ID. + * If an endpoint receives a data frame for a Stream-ID which is not open + * and the endpoint has not sent a GOAWAY frame, it must issue a stream error + * with the error code INVALID_STREAM for the Stream-ID. * * If an endpoint which created the stream receives a data frame before receiving - * a SYN_REPLY on that stream, it is a protocol error, and the receiver should - * close the connection immediately. + * a SYN_REPLY on that stream, it is a protocol error, and the recipient must + * issue a stream error with the status code PROTOCOL_ERROR for the Stream-ID. * * If an endpoint receives multiple data frames for invalid Stream-IDs, - * it may terminate the session. + * it may close the session. * * If an endpoint refuses a stream it must ignore any data frames for that stream. * - * If an endpoint receives data on a stream which has already been torn down, - * it must ignore the data received after the teardown. + * If an endpoint receives a data frame after the stream is half-closed from the + * sender, it must send a RST_STREAM frame with the status STREAM_ALREADY_CLOSED. + * + * If an endpoint receives a data frame after the stream is closed, it must send + * a RST_STREAM frame with the status PROTOCOL_ERROR. */ SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; int streamID = spdyDataFrame.getStreamID(); // Check if we received a data frame for a Stream-ID which is not open - if (spdySession.isRemoteSideClosed(streamID)) { - if (!sentGoAwayFrame) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM); + if (!spdySession.isActiveStream(streamID)) { + if (streamID <= lastGoodStreamID) { + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR); + } else if (!sentGoAwayFrame) { + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM); } return; } + // Check if we received a data frame for a stream which is half-closed + if (spdySession.isRemoteSideClosed(streamID)) { + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_ALREADY_CLOSED); + return; + } + // Check if we received a data frame before receiving a SYN_REPLY if (!isRemoteInitiatedID(streamID) && !spdySession.hasReceivedReply(streamID)) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR); return; } + /* + * SPDY Data frame flow control processing requirements: + * + * Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame. + */ + + if (flowControl) { + // Update receive window size + int deltaWindowSize = -1 * spdyDataFrame.getData().readableBytes(); + int newWindowSize = spdySession.updateReceiveWindowSize(streamID, deltaWindowSize); + + // Window size can become negative if we sent a SETTINGS frame that reduces the + // size of the transfer window after the peer has written data frames. + // The value is bounded by the length that SETTINGS frame decrease the window. + // This difference is stored for the session when writing the SETTINGS frame + // and is cleared once we send a WINDOW_UPDATE frame. + if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamID)) { + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.FLOW_CONTROL_ERROR); + return; + } + + // Window size became negative due to sender writing frame before receiving SETTINGS + // Send data frames upstream in initialReceiveWindowSize chunks + if (newWindowSize < 0) { + while (spdyDataFrame.getData().readableBytes() > initialReceiveWindowSize) { + SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID); + partialDataFrame.setData(spdyDataFrame.getData().readSlice(initialReceiveWindowSize)); + Channels.fireMessageReceived(ctx, partialDataFrame, e.getRemoteAddress()); + } + } + + // Send a WINDOW_UPDATE frame if less than half the window size remains + if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) { + deltaWindowSize = initialReceiveWindowSize - newWindowSize; + spdySession.updateReceiveWindowSize(streamID, deltaWindowSize); + SpdyWindowUpdateFrame spdyWindowUpdateFrame = + new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize); + Channels.write( + ctx, Channels.future(e.getChannel()), spdyWindowUpdateFrame, e.getRemoteAddress()); + } + } + + // Close the remote side of the stream if this is the last frame if (spdyDataFrame.isLast()) { - // Close remote side of stream halfCloseStream(streamID, true); } @@ -121,11 +190,15 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler /* * SPDY SYN_STREAM frame processing requirements: * - * If an endpoint receives a SYN_STREAM with a Stream-ID that is not monotonically - * increasing, it must issue a session error with the status PROTOCOL_ERROR. + * If an endpoint receives a SYN_STREAM with a Stream-ID that is less than + * any previously received SYN_STREAM, it must issue a session error with + * the status PROTOCOL_ERROR. * * If an endpoint receives multiple SYN_STREAM frames with the same active * Stream-ID, it must issue a stream error with the status code PROTOCOL_ERROR. + * + * The recipient can reject a stream by sending a stream error with the + * status code REFUSED_STREAM. */ SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; @@ -135,21 +208,22 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler if (spdySynStreamFrame.isInvalid() || !isRemoteInitiatedID(streamID) || spdySession.isActiveStream(streamID)) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR); return; } - // Stream-IDs must be monotonically increassing - if (streamID < lastGoodStreamID) { - issueSessionError(ctx, e.getChannel(), e.getRemoteAddress()); + // Stream-IDs must be monotonically increasing + if (streamID <= lastGoodStreamID) { + issueSessionError(ctx, e.getChannel(), e.getRemoteAddress(), SpdySessionStatus.PROTOCOL_ERROR); return; } // Try to accept the stream + byte priority = spdySynStreamFrame.getPriority(); boolean remoteSideClosed = spdySynStreamFrame.isLast(); boolean localSideClosed = spdySynStreamFrame.isUnidirectional(); - if (!acceptStream(streamID, remoteSideClosed, localSideClosed)) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.REFUSED_STREAM); + if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) { + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.REFUSED_STREAM); return; } @@ -159,7 +233,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler * SPDY SYN_REPLY frame processing requirements: * * If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID - * it must issue a stream error with the status code PROTOCOL_ERROR. + * it must issue a stream error with the status code STREAM_IN_USE. */ SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; @@ -169,19 +243,20 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler if (spdySynReplyFrame.isInvalid() || isRemoteInitiatedID(streamID) || spdySession.isRemoteSideClosed(streamID)) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM); + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM); return; } // Check if we have received multiple frames for the same Stream-ID if (spdySession.hasReceivedReply(streamID)) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.STREAM_IN_USE); return; } spdySession.receivedReply(streamID); + + // Close the remote side of the stream if this is the last frame if (spdySynReplyFrame.isLast()) { - // Close remote side of stream halfCloseStream(streamID, true); } @@ -190,8 +265,10 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler /* * SPDY RST_STREAM frame processing requirements: * - * After receiving a RST_STREAM on a stream, the receiver must not send additional - * frames on that stream. + * After receiving a RST_STREAM on a stream, the receiver must not send + * additional frames on that stream. + * + * An endpoint must not send a RST_STREAM in response to a RST_STREAM. */ SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; @@ -199,12 +276,29 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } else if (msg instanceof SpdySettingsFrame) { - /* - * Only concerned with MAX_CONCURRENT_STREAMS - */ - SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg; - updateConcurrentStreams(spdySettingsFrame, true); + + int newConcurrentStreams = + spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS); + if (newConcurrentStreams >= 0) { + updateConcurrentStreams(newConcurrentStreams, true); + } + + // Persistence flag are inconsistent with the use of SETTINGS to communicate + // the initial window size. Remove flags from the sender requesting that the + // value be persisted. Remove values that the sender indicates are persisted. + if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) { + spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE); + } + spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false); + + if (flowControl) { + int newInitialWindowSize = + spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE); + if (newInitialWindowSize >= 0) { + updateInitialSendWindowSize(newInitialWindowSize); + } + } } else if (msg instanceof SpdyPingFrame) { @@ -224,7 +318,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler return; } - // Note: only checks that there are outstanding pings since uniqueness is not inforced + // Note: only checks that there are outstanding pings since uniqueness is not enforced if (pings.get() == 0) { return; } @@ -241,14 +335,51 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler // Check if we received a valid HEADERS frame if (spdyHeadersFrame.isInvalid()) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR); + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.PROTOCOL_ERROR); return; } if (spdySession.isRemoteSideClosed(streamID)) { - issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM); + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.INVALID_STREAM); return; } + + // Close the remote side of the stream if this is the last frame + if (spdyHeadersFrame.isLast()) { + halfCloseStream(streamID, true); + } + + } else if (msg instanceof SpdyWindowUpdateFrame) { + + /* + * SPDY WINDOW_UPDATE frame processing requirements: + * + * Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31 + * must send a RST_STREAM with the status code FLOW_CONTROL_ERROR. + * + * Sender should ignore all WINDOW_UPDATE frames associated with a stream + * after sending the last frame for the stream. + */ + + if (flowControl) { + SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg; + int streamID = spdyWindowUpdateFrame.getStreamID(); + int deltaWindowSize = spdyWindowUpdateFrame.getDeltaWindowSize(); + + // Ignore frames for half-closed streams + if (spdySession.isLocalSideClosed(streamID)) { + return; + } + + // Check for numerical overflow + if (spdySession.getSendWindowSize(streamID) > Integer.MAX_VALUE - deltaWindowSize) { + issueStreamError(ctx, e.getRemoteAddress(), streamID, SpdyStreamStatus.FLOW_CONTROL_ERROR); + return; + } + + updateSendWindowSize(ctx, streamID, deltaWindowSize); + } + return; } super.messageReceived(ctx, e); @@ -260,7 +391,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler Throwable cause = e.getCause(); if (cause instanceof SpdyProtocolException) { - issueSessionError(ctx, e.getChannel(), null); + issueSessionError(ctx, e.getChannel(), null, SpdySessionStatus.PROTOCOL_ERROR); } super.exceptionCaught(ctx, e); @@ -274,6 +405,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler case OPEN: case CONNECTED: case BOUND: + + /* + * SPDY connection requirements: + * + * When either endpoint closes the transport-level connection, + * it must first send a GOAWAY frame. + */ if (Boolean.FALSE.equals(e.getValue()) || e.getValue() == null) { sendGoAwayFrame(ctx, e); return; @@ -291,13 +429,85 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler if (msg instanceof SpdyDataFrame) { SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; - int streamID = spdyDataFrame.getStreamID(); + final int streamID = spdyDataFrame.getStreamID(); + // Frames must not be sent on half-closed streams if (spdySession.isLocalSideClosed(streamID)) { e.getFuture().setFailure(PROTOCOL_EXCEPTION); return; } + /* + * SPDY Data frame flow control processing requirements: + * + * Sender must not send a data frame with data length greater + * than the transfer window size. + * + * After sending each data frame, the sender decrements its + * transfer window size by the amount of data transmitted. + * + * When the window size becomes less than or equal to 0, the + * sender must pause transmitting data frames. + */ + + if (flowControl) { + synchronized (flowControlLock) { + int dataLength = spdyDataFrame.getData().readableBytes(); + int sendWindowSize = spdySession.getSendWindowSize(streamID); + + if (sendWindowSize >= dataLength) { + // Window size is large enough to send entire data frame + spdySession.updateSendWindowSize(streamID, -1 * dataLength); + + // The transfer window size is pre-decremented when sending a data frame downstream. + // Close the stream on write failures that leaves the transfer window in a corrupt state. + final SocketAddress remoteAddress = e.getRemoteAddress(); + final ChannelHandlerContext context = ctx; + e.getFuture().addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR); + } + } + }); + + } else if (sendWindowSize > 0) { + // Stream is not stalled but we cannot send the entire frame + spdySession.updateSendWindowSize(streamID, -1 * sendWindowSize); + + // Create a partial data frame whose length is the current window size + SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID); + partialDataFrame.setData(spdyDataFrame.getData().readSlice(sendWindowSize)); + + // Enqueue the remaining data (will be the first frame queued) + spdySession.putPendingWrite(streamID, e); + + ChannelFuture writeFuture = Channels.future(e.getChannel()); + + // The transfer window size is pre-decremented when sending a data frame downstream. + // Close the stream on write failures that leaves the transfer window in a corrupt state. + final SocketAddress remoteAddress = e.getRemoteAddress(); + final ChannelHandlerContext context = ctx; + e.getFuture().addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR); + } + } + }); + + Channels.write(ctx, writeFuture, partialDataFrame, remoteAddress); + return; + + } else { + // Stream is stalled -- enqueue Data frame and return + spdySession.putPendingWrite(streamID, e); + return; + } + } + } + + // Close the local side of the stream if this is the last frame if (spdyDataFrame.isLast()) { halfCloseStream(streamID, false); } @@ -305,9 +515,17 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } else if (msg instanceof SpdySynStreamFrame) { SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; + int streamID = spdySynStreamFrame.getStreamID(); + + if (isRemoteInitiatedID(streamID)) { + e.getFuture().setFailure(PROTOCOL_EXCEPTION); + return; + } + + byte priority = spdySynStreamFrame.getPriority(); boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional(); boolean localSideClosed = spdySynStreamFrame.isLast(); - if (!acceptStream(spdySynStreamFrame.getStreamID(), remoteSideClosed, localSideClosed)) { + if (!acceptStream(streamID, priority, remoteSideClosed, localSideClosed)) { e.getFuture().setFailure(PROTOCOL_EXCEPTION); return; } @@ -317,11 +535,13 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; int streamID = spdySynReplyFrame.getStreamID(); + // Frames must not be sent on half-closed streams if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) { e.getFuture().setFailure(PROTOCOL_EXCEPTION); return; } + // Close the local side of the stream if this is the last frame if (spdySynReplyFrame.isLast()) { halfCloseStream(streamID, false); } @@ -334,7 +554,28 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } else if (msg instanceof SpdySettingsFrame) { SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg; - updateConcurrentStreams(spdySettingsFrame, false); + + int newConcurrentStreams = + spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS); + if (newConcurrentStreams >= 0) { + updateConcurrentStreams(newConcurrentStreams, false); + } + + // Persistence flag are inconsistent with the use of SETTINGS to communicate + // the initial window size. Remove flags from the sender requesting that the + // value be persisted. Remove values that the sender indicates are persisted. + if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) { + spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE); + } + spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false); + + if (flowControl) { + int newInitialWindowSize = + spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE); + if (newInitialWindowSize >= 0) { + updateInitialReceiveWindowSize(newInitialWindowSize); + } + } } else if (msg instanceof SpdyPingFrame) { @@ -348,7 +589,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } else if (msg instanceof SpdyGoAwayFrame) { - // Should send a CLOSE ChannelStateEvent + // Why is this being sent? Intercept it and fail the write. + // Should have sent a CLOSE ChannelStateEvent e.getFuture().setFailure(PROTOCOL_EXCEPTION); return; @@ -357,34 +599,65 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; int streamID = spdyHeadersFrame.getStreamID(); + // Frames must not be sent on half-closed streams if (spdySession.isLocalSideClosed(streamID)) { e.getFuture().setFailure(PROTOCOL_EXCEPTION); return; } + + // Close the local side of the stream if this is the last frame + if (spdyHeadersFrame.isLast()) { + halfCloseStream(streamID, false); + } + + } else if (msg instanceof SpdyWindowUpdateFrame) { + + // Why is this being sent? Intercept it and fail the write. + e.getFuture().setFailure(PROTOCOL_EXCEPTION); + return; } ctx.sendDownstream(evt); } /* - * Error Handling + * SPDY Session Error Handling: + * + * When a session error occurs, the endpoint encountering the error must first + * send a GOAWAY frame with the Stream-ID of the most recently received stream + * from the remote endpoint, and the error code for why the session is terminating. + * + * After sending the GOAWAY frame, the endpoint must close the TCP connection. */ - private void issueSessionError( - ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) { + ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) { - ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress); + ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress, status); future.addListener(ChannelFutureListener.CLOSE); } - // Send a RST_STREAM frame in response to an incoming MessageEvent - // Only called in the upstream direction + /* + * SPDY Stream Error Handling: + * + * Upon a stream error, the endpoint must send a RST_STREAM frame which contains + * the Stream-ID for the stream where the error occurred and the error status which + * caused the error. + * + * After sending the RST_STREAM, the stream is closed to the sending endpoint. + * + * Note: this is only called by the worker thread + */ private void issueStreamError( - ChannelHandlerContext ctx, MessageEvent e, int streamID, SpdyStreamStatus status) { + ChannelHandlerContext ctx, SocketAddress remoteAddress, int streamID, SpdyStreamStatus status) { + boolean fireMessageReceived = !spdySession.isRemoteSideClosed(streamID); removeStream(streamID); + SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamID, status); - Channels.write(ctx, Channels.future(e.getChannel()), spdyRstStreamFrame, e.getRemoteAddress()); + Channels.write(ctx, Channels.future(ctx.getChannel()), spdyRstStreamFrame, remoteAddress); + if (fireMessageReceived) { + Channels.fireMessageReceived(ctx, spdyRstStreamFrame, remoteAddress); + } } /* @@ -396,8 +669,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler return server && !serverID || !server && serverID; } - private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) { - int newConcurrentStreams = settings.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS); + private void updateConcurrentStreams(int newConcurrentStreams, boolean remote) { if (remote) { remoteConcurrentStreams = newConcurrentStreams; } else { @@ -422,18 +694,37 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } } - // need to synchronize accesses to sentGoAwayFrame and lastGoodStreamID + // need to synchronize to prevent new streams from being created while updating active streams + private synchronized void updateInitialSendWindowSize(int newInitialWindowSize) { + int deltaWindowSize = newInitialWindowSize - initialSendWindowSize; + initialSendWindowSize = newInitialWindowSize; + for (Integer StreamID: spdySession.getActiveStreams()) { + spdySession.updateSendWindowSize(StreamID.intValue(), deltaWindowSize); + } + } + + // need to synchronize to prevent new streams from being created while updating active streams + private synchronized void updateInitialReceiveWindowSize(int newInitialWindowSize) { + int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize; + initialReceiveWindowSize = newInitialWindowSize; + spdySession.updateAllReceiveWindowSizes(deltaWindowSize); + } + + // need to synchronize accesses to sentGoAwayFrame, lastGoodStreamID, and initial window sizes private synchronized boolean acceptStream( - int streamID, boolean remoteSideClosed, boolean localSideClosed) { + int streamID, byte priority, boolean remoteSideClosed, boolean localSideClosed) { // Cannot initiate any new streams after receiving or sending GOAWAY if (receivedGoAwayFrame || sentGoAwayFrame) { return false; } + + int maxConcurrentStreams = this.maxConcurrentStreams; // read volatile once if (maxConcurrentStreams != 0 && spdySession.numActiveStreams() >= maxConcurrentStreams) { return false; } - spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed); + spdySession.acceptStream( + streamID, priority, remoteSideClosed, localSideClosed, initialSendWindowSize, initialReceiveWindowSize); if (isRemoteInitiatedID(streamID)) { lastGoodStreamID = streamID; } @@ -458,6 +749,74 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } } + private void updateSendWindowSize(ChannelHandlerContext ctx, final int streamID, int deltaWindowSize) { + synchronized (flowControlLock) { + int newWindowSize = spdySession.updateSendWindowSize(streamID, deltaWindowSize); + + while (newWindowSize > 0) { + // Check if we have unblocked a stalled stream + MessageEvent e = spdySession.getPendingWrite(streamID); + if (e == null) { + break; + } + + SpdyDataFrame spdyDataFrame = (SpdyDataFrame) e.getMessage(); + int dataFrameSize = spdyDataFrame.getData().readableBytes(); + + if (newWindowSize >= dataFrameSize) { + // Window size is large enough to send entire data frame + spdySession.removePendingWrite(streamID); + newWindowSize = spdySession.updateSendWindowSize(streamID, -1 * dataFrameSize); + + // The transfer window size is pre-decremented when sending a data frame downstream. + // Close the stream on write failures that leaves the transfer window in a corrupt state. + final SocketAddress remoteAddress = e.getRemoteAddress(); + final ChannelHandlerContext context = ctx; + e.getFuture().addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR); + } + } + }); + + // Close the local side of the stream if this is the last frame + if (spdyDataFrame.isLast()) { + halfCloseStream(streamID, false); + } + + Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress()); + + } else { + // We can send a partial frame + spdySession.updateSendWindowSize(streamID, -1 * newWindowSize); + + // Create a partial data frame whose length is the current window size + SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(streamID); + partialDataFrame.setData(spdyDataFrame.getData().readSlice(newWindowSize)); + + ChannelFuture writeFuture = Channels.future(e.getChannel()); + + // The transfer window size is pre-decremented when sending a data frame downstream. + // Close the stream on write failures that leaves the transfer window in a corrupt state. + final SocketAddress remoteAddress = e.getRemoteAddress(); + final ChannelHandlerContext context = ctx; + e.getFuture().addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + issueStreamError(context, remoteAddress, streamID, SpdyStreamStatus.INTERNAL_ERROR); + } + } + }); + + Channels.write(ctx, writeFuture, partialDataFrame, remoteAddress); + + newWindowSize = 0; + } + } + } + } + private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelStateEvent e) { // Avoid NotYetConnectedException if (!e.getChannel().isConnected()) { @@ -465,7 +824,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler return; } - ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null); + ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null, SpdySessionStatus.OK); if (spdySession.noActiveStreams()) { future.addListener(new ClosingChannelFutureListener(ctx, e)); } else { @@ -475,18 +834,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler } private synchronized ChannelFuture sendGoAwayFrame( - ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) { + ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress, SpdySessionStatus status) { if (!sentGoAwayFrame) { sentGoAwayFrame = true; + SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamID, status); ChannelFuture future = Channels.future(channel); - Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID), remoteAddress); + Channels.write(ctx, future, spdyGoAwayFrame, remoteAddress); return future; } return Channels.succeededFuture(channel); } private static final class ClosingChannelFutureListener implements ChannelFutureListener { - private final ChannelHandlerContext ctx; private final ChannelStateEvent e; diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionStatus.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionStatus.java new file mode 100644 index 0000000000..c929c0d5e7 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionStatus.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012 The Netty Project + * + * 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.spdy; + +/** + * The SPDY session status code and its description. + * @apiviz.exclude + */ +public class SpdySessionStatus implements Comparable { + + /** + * 0 OK + */ + public static final SpdySessionStatus OK = + new SpdySessionStatus(0, "OK"); + + /** + * 1 Protocol Error + */ + public static final SpdySessionStatus PROTOCOL_ERROR = + new SpdySessionStatus(1, "PROTOCOL_ERROR"); + + /** + * 11 Internal Error + */ + public static final SpdySessionStatus INTERNAL_ERROR = + new SpdySessionStatus(11, "INTERNAL_ERROR"); + + /** + * Returns the {@link SpdySessionStatus} represented by the specified code. + * If the specified code is a defined SPDY status code, a cached instance + * will be returned. Otherwise, a new instance will be returned. + */ + public static SpdySessionStatus valueOf(int code) { + switch (code) { + case 0: + return OK; + case 1: + return PROTOCOL_ERROR; + case 11: + return INTERNAL_ERROR; + } + + return new SpdySessionStatus(code, "UNKNOWN (" + code + ')'); + } + + private final int code; + + private final String statusPhrase; + + /** + * Creates a new instance with the specified {@code code} and its + * {@code statusPhrase}. + */ + public SpdySessionStatus(int code, String statusPhrase) { + if (statusPhrase == null) { + throw new NullPointerException("statusPhrase"); + } + + this.code = code; + this.statusPhrase = statusPhrase; + } + + /** + * Returns the code of this status. + */ + public int getCode() { + return code; + } + + /** + * Returns the status phrase of this status. + */ + public String getStatusPhrase() { + return statusPhrase; + } + + @Override + public int hashCode() { + return getCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SpdySessionStatus)) { + return false; + } + + return getCode() == ((SpdySessionStatus) o).getCode(); + } + + @Override + public String toString() { + return getStatusPhrase(); + } + + public int compareTo(SpdySessionStatus o) { + return getCode() - o.getCode(); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java index 986db39542..96d237ceb4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySettingsFrame.java @@ -22,13 +22,14 @@ import java.util.Set; */ public interface SpdySettingsFrame { - int SETTINGS_UPLOAD_BANDWIDTH = 1; - int SETTINGS_DOWNLOAD_BANDWIDTH = 2; - int SETTINGS_ROUND_TRIP_TIME = 3; - int SETTINGS_MAX_CONCURRENT_STREAMS = 4; - int SETTINGS_CURRENT_CWND = 5; - int SETTINGS_DOWNLOAD_RETRANS_RATE = 6; - int SETTINGS_INITIAL_WINDOW_SIZE = 7; + int SETTINGS_UPLOAD_BANDWIDTH = 1; + int SETTINGS_DOWNLOAD_BANDWIDTH = 2; + int SETTINGS_ROUND_TRIP_TIME = 3; + int SETTINGS_MAX_CONCURRENT_STREAMS = 4; + int SETTINGS_CURRENT_CWND = 5; + int SETTINGS_DOWNLOAD_RETRANS_RATE = 6; + int SETTINGS_INITIAL_WINDOW_SIZE = 7; + int SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8; /** * Returns a {@code Set} of the setting IDs. @@ -49,7 +50,7 @@ public interface SpdySettingsFrame { /** * Sets the value of the setting ID. - * The ID must be positive and cannot exceeed 16777215. + * The ID must be positive and cannot exceed 16777215. */ void setValue(int ID, int value); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java index 57689f0098..feeb816022 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyStreamStatus.java @@ -63,6 +63,30 @@ public class SpdyStreamStatus implements Comparable { public static final SpdyStreamStatus FLOW_CONTROL_ERROR = new SpdyStreamStatus(7, "FLOW_CONTROL_ERROR"); + /** + * 8 Stream In Use + */ + public static final SpdyStreamStatus STREAM_IN_USE = + new SpdyStreamStatus(8, "STREAM_IN_USE"); + + /** + * 9 Stream Already Closed + */ + public static final SpdyStreamStatus STREAM_ALREADY_CLOSED = + new SpdyStreamStatus(9, "STREAM_ALREADY_CLOSED"); + + /** + * 10 Invalid Credentials + */ + public static final SpdyStreamStatus INVALID_CREDENTIALS = + new SpdyStreamStatus(10, "INVALID_CREDENTIALS"); + + /** + * 11 Frame Too Large + */ + public static final SpdyStreamStatus FRAME_TOO_LARGE = + new SpdyStreamStatus(11, "FRAME_TOO_LARGE"); + /** * Returns the {@link SpdyStreamStatus} represented by the specified code. * If the specified code is a defined SPDY status code, a cached instance @@ -89,6 +113,14 @@ public class SpdyStreamStatus implements Comparable { return INTERNAL_ERROR; case 7: return FLOW_CONTROL_ERROR; + case 8: + return STREAM_IN_USE; + case 9: + return STREAM_ALREADY_CLOSED; + case 10: + return INVALID_CREDENTIALS; + case 11: + return FRAME_TOO_LARGE; } return new SpdyStreamStatus(code, "UNKNOWN (" + code + ')'); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java index b69bcf3b16..4e2e069a91 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySynStreamFrame.java @@ -48,7 +48,7 @@ public interface SpdySynStreamFrame extends SpdyHeaderBlock { /** * Sets the priority of the stream. - * The priority must be between 0 and 3 inclusive. + * The priority must be between 0 and 7 inclusive. */ void setPriority(byte priority); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java index 18f9712987..4935cf2ada 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.handler.codec.spdy; +package io.netty.handler.codec.spdy; /** * A SPDY Protocol WINDOW_UPDATE Control Frame diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java index 3e08e7d89f..8c6e67b723 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java @@ -17,6 +17,8 @@ package io.netty.handler.codec.spdy; import static org.junit.Assert.*; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; + import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -47,15 +49,17 @@ import org.junit.Test; public abstract class AbstractSocketSpdyEchoTest { private static final Random random = new Random(); - static final ChannelBuffer frames = ChannelBuffers.buffer(1176); static final int ignoredBytes = 20; private static ExecutorService executor; - static { + private static ChannelBuffer createFrames(int version) { + int length = version < 3 ? 1176 : 1174; + ChannelBuffer frames = ChannelBuffers.buffer(length); + // SPDY UNKNOWN Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(0xFFFF); frames.writeByte(0xFF); frames.writeMedium(4); @@ -63,7 +67,7 @@ public abstract class AbstractSocketSpdyEchoTest { // SPDY NOOP Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(5); frames.writeInt(0); @@ -77,27 +81,39 @@ public abstract class AbstractSocketSpdyEchoTest { // SPDY SYN_STREAM Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(1); frames.writeByte(0x03); - frames.writeMedium(12); + if (version < 3) { + frames.writeMedium(12); + } else { + frames.writeMedium(10); + } frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF); frames.writeShort(0x8000); - frames.writeShort(0); + if (version < 3) { + frames.writeShort(0); + } // SPDY SYN_REPLY Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(2); frames.writeByte(0x01); - frames.writeMedium(8); + if (version < 3) { + frames.writeMedium(8); + } else { + frames.writeMedium(4); + } frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); - frames.writeInt(0); + if (version < 3) { + frames.writeInt(0); + } // SPDY RST_STREAM Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(3); frames.writeInt(8); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); @@ -105,43 +121,58 @@ public abstract class AbstractSocketSpdyEchoTest { // SPDY SETTINGS Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(4); frames.writeByte(0x01); frames.writeMedium(12); frames.writeInt(1); - frames.writeMedium(random.nextInt()); - frames.writeByte(0x03); + if (version < 3) { + frames.writeMedium(random.nextInt()); + frames.writeByte(0x03); + } else { + frames.writeByte(0x03); + frames.writeMedium(random.nextInt()); + } frames.writeInt(random.nextInt()); // SPDY PING Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(6); frames.writeInt(4); frames.writeInt(random.nextInt()); // SPDY GOAWAY Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(7); - frames.writeInt(4); + if (version < 3) { + frames.writeInt(4); + } else { + frames.writeInt(8); + } frames.writeInt(random.nextInt() & 0x7FFFFFFF); + if (version >= 3) { + frames.writeInt(random.nextInt() | 0x01); + } // SPDY HEADERS Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(8); - frames.writeInt(4); + frames.writeByte(0x01); + frames.writeMedium(4); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); // SPDY WINDOW_UPDATE Frame frames.writeByte(0x80); - frames.writeByte(2); + frames.writeByte(version); frames.writeShort(9); frames.writeInt(8); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + + return frames; } @BeforeClass @@ -159,14 +190,22 @@ public abstract class AbstractSocketSpdyEchoTest { @Test(timeout = 10000) public void testSpdyEcho() throws Throwable { + for (int version = SPDY_MIN_VERSION; version <= SPDY_MAX_VERSION; version ++) { + testSpdyEcho(version); + } + } + + private void testSpdyEcho(int version) throws Throwable { ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); - EchoHandler sh = new EchoHandler(true); - EchoHandler ch = new EchoHandler(false); + ChannelBuffer frames = createFrames(version); - sb.getPipeline().addLast("decoder", new SpdyFrameDecoder()); - sb.getPipeline().addLast("encoder", new SpdyFrameEncoder()); + EchoHandler sh = new EchoHandler(frames, true); + EchoHandler ch = new EchoHandler(frames, false); + + sb.getPipeline().addLast("decoder", new SpdyFrameDecoder(version)); + sb.getPipeline().addLast("encoder", new SpdyFrameEncoder(version)); sb.getPipeline().addLast("handler", sh); cb.getPipeline().addLast("handler", ch); @@ -216,11 +255,13 @@ public abstract class AbstractSocketSpdyEchoTest { private class EchoHandler extends SimpleChannelUpstreamHandler { volatile Channel channel; final AtomicReference exception = new AtomicReference(); + final ChannelBuffer frames; volatile int counter; final boolean server; - EchoHandler(boolean server) { + EchoHandler(ChannelBuffer frames, boolean server) { super(); + this.frames = frames; this.server = server; } diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java index 893f0baed1..c4277f6f79 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java @@ -95,10 +95,10 @@ public class SpdySessionHandlerTest { assertHeaderBlock(spdyHeadersFrame, headers); } - private void testSpdySessionHandler(boolean server) { + private void testSpdySessionHandler(int version, boolean server) { DecoderEmbedder sessionHandler = new DecoderEmbedder( - new SpdySessionHandler(server), new EchoHandler(closeSignal, server)); + new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server)); sessionHandler.pollAll(); int localStreamID = server ? 1 : 2; @@ -132,7 +132,7 @@ public class SpdySessionHandlerTest { sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID)); Assert.assertNull(sessionHandler.peek()); sessionHandler.offer(new DefaultSpdySynReplyFrame(remoteStreamID)); - assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.PROTOCOL_ERROR); + assertRstStream(sessionHandler.poll(), remoteStreamID, SpdyStreamStatus.STREAM_IN_USE); Assert.assertNull(sessionHandler.peek()); remoteStreamID += 2; @@ -252,12 +252,12 @@ public class SpdySessionHandlerTest { @Test public void testSpdyClientSessionHandler() { - testSpdySessionHandler(false); + testSpdySessionHandler(2, false); } @Test public void testSpdyServerSessionHandler() { - testSpdySessionHandler(true); + testSpdySessionHandler(2, true); } // Echo Handler opens 4 half-closed streams on session connection From a6d7105761a083230bb4d2aac7e58ca5fb07a196 Mon Sep 17 00:00:00 2001 From: Sun Ning Date: Thu, 24 May 2012 14:33:19 +0800 Subject: [PATCH 133/134] fix #360, add check for empty buffer; also add unit test for this scenario --- .../io/netty/buffer/ChannelBufferInputStream.java | 3 ++- .../java/io/netty/buffer/ChannelBufferStreamTest.java | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java b/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java index 63c14f1109..0fd6d52d97 100644 --- a/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java +++ b/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java @@ -195,7 +195,8 @@ public class ChannelBufferInputStream extends InputStream implements DataInput { lineBuf.append((char) b); } - while (lineBuf.charAt(lineBuf.length() - 1) == '\r') { + while ( lineBuf.length() > 0 && + lineBuf.charAt(lineBuf.length() - 1) == '\r') { lineBuf.setLength(lineBuf.length() - 1); } diff --git a/buffer/src/test/java/io/netty/buffer/ChannelBufferStreamTest.java b/buffer/src/test/java/io/netty/buffer/ChannelBufferStreamTest.java index 5ddd657ec3..2474c630e4 100644 --- a/buffer/src/test/java/io/netty/buffer/ChannelBufferStreamTest.java +++ b/buffer/src/test/java/io/netty/buffer/ChannelBufferStreamTest.java @@ -168,4 +168,15 @@ public class ChannelBufferStreamTest { assertEquals(buf.readerIndex(), in.readBytes()); } + + @Test + public void testEmptyReadLine() throws Exception { + ChannelBuffer buf = ChannelBuffers.buffer(0); + ChannelBufferInputStream in = new ChannelBufferInputStream(buf); + + String s = in.readLine(); + assertEquals(0, s.length()); + + in.close(); + } } From 326b88c43005f9b295f305b59d8ae43fc7f09410 Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 24 May 2012 08:47:35 +0200 Subject: [PATCH 134/134] A small optimization for the fix of #360 --- .../java/io/netty/buffer/ChannelBufferInputStream.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java b/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java index 0fd6d52d97..0d5d4f163c 100644 --- a/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java +++ b/buffer/src/main/java/io/netty/buffer/ChannelBufferInputStream.java @@ -195,9 +195,10 @@ public class ChannelBufferInputStream extends InputStream implements DataInput { lineBuf.append((char) b); } - while ( lineBuf.length() > 0 && - lineBuf.charAt(lineBuf.length() - 1) == '\r') { - lineBuf.setLength(lineBuf.length() - 1); + if (lineBuf.length() > 0 ) { + while (lineBuf.charAt(lineBuf.length() - 1) == '\r') { + lineBuf.setLength(lineBuf.length() - 1); + } } return lineBuf.toString();