From af4109b579fddcd2d7a15555e2644121bb3cf87b Mon Sep 17 00:00:00 2001 From: iainmcgin Date: Fri, 10 Dec 2010 11:23:40 +0000 Subject: [PATCH 1/6] Fix for NETTY-370 - documentation updated to reflect the correct behaviour of high / low water marks on the interest ops of nio channels. --- .../socket/nio/NioSocketChannelConfig.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java index 94e552f47c2..7a43427af09 100644 --- a/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java @@ -59,36 +59,36 @@ */ public interface NioSocketChannelConfig extends SocketChannelConfig { - /** - * Returns the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code true}. - */ + /** + * Returns the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code false}. + */ int getWriteBufferHighWaterMark(); - /** - * Sets the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code true}. - */ + /** + * Sets the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code false}. + */ void setWriteBufferHighWaterMark(int writeBufferHighWaterMark); - /** - * Returns the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code false} again. - */ + /** + * Returns the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will return + * {@code true} again. + */ int getWriteBufferLowWaterMark(); - /** - * Sets the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code false} again. - */ + /** + * Sets the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will return + * {@code true} again. + */ void setWriteBufferLowWaterMark(int writeBufferLowWaterMark); /** From 44c5dac556eedb9427365f40aaeac7a98b4dcbc6 Mon Sep 17 00:00:00 2001 From: iainmcgin Date: Fri, 10 Dec 2010 17:43:48 +0000 Subject: [PATCH 2/6] NETTY-246: adding implementation to netty trunk for the 4.0.0 release. In order to get the tests to pass, I had to change the surefire fork policy from "never" to "once", as it seems the classloader settings used by the maven process were preventing jmock from mocking package private interfaces. --- pom.xml | 10 +- .../AcceptedServerChannelPipelineFactory.java | 56 ++ .../AcceptedServerChannelRequestDispatch.java | 194 +++++++ .../httptunnel/ChannelFutureAggregator.java | 78 +++ .../httptunnel/DefaultTunnelIdGenerator.java | 53 ++ .../httptunnel/HttpTunnelAcceptedChannel.java | 117 +++++ .../HttpTunnelAcceptedChannelConfig.java | 130 +++++ .../HttpTunnelAcceptedChannelFactory.java | 34 ++ .../HttpTunnelAcceptedChannelReceiver.java | 37 ++ .../HttpTunnelAcceptedChannelSink.java | 133 +++++ .../httptunnel/HttpTunnelChannelConfig.java | 164 ++++++ .../httptunnel/HttpTunnelClientChannel.java | 385 ++++++++++++++ .../HttpTunnelClientChannelConfig.java | 180 +++++++ .../HttpTunnelClientChannelFactory.java | 57 ++ .../HttpTunnelClientChannelSink.java | 89 ++++ .../HttpTunnelClientPollHandler.java | 104 ++++ .../HttpTunnelClientSendHandler.java | 267 ++++++++++ .../HttpTunnelClientWorkerOwner.java | 68 +++ .../httptunnel/HttpTunnelMessageUtils.java | 363 +++++++++++++ .../httptunnel/HttpTunnelServerChannel.java | 113 ++++ .../HttpTunnelServerChannelConfig.java | 150 ++++++ .../HttpTunnelServerChannelFactory.java | 65 +++ .../HttpTunnelServerChannelSink.java | 99 ++++ .../socket/httptunnel/SaturationManager.java | 68 +++ .../httptunnel/SaturationStateChange.java | 31 ++ .../httptunnel/ServerMessageSwitch.java | 268 ++++++++++ ...erverMessageSwitchDownstreamInterface.java | 36 ++ .../ServerMessageSwitchUpstreamInterface.java | 57 ++ .../socket/httptunnel/TunnelIdGenerator.java | 38 ++ .../TunnelWrappedServerChannelHandler.java | 84 +++ .../socket/httptunnel/WriteFragmenter.java | 78 +++ .../socket/httptunnel/WriteSplitter.java | 59 +++ ...eptedServerChannelRequestDispatchTest.java | 247 +++++++++ .../socket/httptunnel/FakeChannelConfig.java | 237 +++++++++ .../socket/httptunnel/FakeChannelSink.java | 40 ++ .../FakeClientSocketChannelFactory.java | 52 ++ .../httptunnel/FakeServerSocketChannel.java | 92 ++++ .../FakeServerSocketChannelConfig.java | 80 +++ .../FakeServerSocketChannelFactory.java | 46 ++ .../socket/httptunnel/FakeSocketChannel.java | 115 +++++ .../HttpTunnelAcceptedChannelSinkTest.java | 94 ++++ .../HttpTunnelClientChannelConfigTest.java | 329 ++++++++++++ .../HttpTunnelClientChannelTest.java | 255 +++++++++ .../HttpTunnelClientPollHandlerTest.java | 126 +++++ .../HttpTunnelClientSendHandlerTest.java | 217 ++++++++ .../HttpTunnelServerChannelFactoryTest.java | 111 ++++ .../HttpTunnelServerChannelSinkTest.java | 173 +++++++ .../HttpTunnelServerChannelTest.java | 242 +++++++++ .../httptunnel/HttpTunnelSoakTester.java | 487 ++++++++++++++++++ .../socket/httptunnel/HttpTunnelTest.java | 228 ++++++++ .../httptunnel/MockChannelStateListener.java | 66 +++ .../socket/httptunnel/NettyTestUtils.java | 200 +++++++ .../socket/httptunnel/NettyTestUtilsTest.java | 144 ++++++ .../socket/httptunnel/NullChannelHandler.java | 41 ++ .../httptunnel/SaturationManagerTest.java | 34 ++ .../httptunnel/ServerMessageSwitchTest.java | 181 +++++++ .../httptunnel/UpstreamEventCatcher.java | 42 ++ .../httptunnel/WriteFragmenterTest.java | 156 ++++++ .../socket/httptunnel/WriteSplitterTest.java | 104 ++++ 59 files changed, 7802 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java create mode 100644 src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java create mode 100644 src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java diff --git a/pom.xml b/pom.xml index 75d0f62942b..a54a46d1c33 100644 --- a/pom.xml +++ b/pom.xml @@ -167,6 +167,12 @@ 2.5.2 test + + org.jmock + jmock-junit4 + 2.5.1 + test + org.slf4j slf4j-simple @@ -253,9 +259,9 @@ maven-surefire-plugin - 2.5 + 2.6 - never + once **/Abstract* **/TestUtil* diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java new file mode 100644 index 00000000000..e5155eea0bd --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.handler.codec.http.HttpChunkAggregator; +import org.jboss.netty.handler.codec.http.HttpRequestDecoder; +import org.jboss.netty.handler.codec.http.HttpResponseEncoder; + +/** + * Creates pipelines for incoming http tunnel connections, capable of decoding the incoming HTTP + * requests, determining their type (client sending data, client polling data, or unknown) and + * handling them appropriately. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class AcceptedServerChannelPipelineFactory implements ChannelPipelineFactory +{ + + private final ServerMessageSwitch messageSwitch; + + public AcceptedServerChannelPipelineFactory(ServerMessageSwitch messageSwitch) + { + this.messageSwitch = messageSwitch; + } + + public ChannelPipeline getPipeline() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder()); + pipeline.addLast("httpChunkAggregator", new HttpChunkAggregator(HttpTunnelMessageUtils.MAX_BODY_SIZE)); + pipeline.addLast("messageSwitchClient", new AcceptedServerChannelRequestDispatch(messageSwitch)); + + return pipeline; + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java new file mode 100644 index 00000000000..3dd5db36036 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java @@ -0,0 +1,194 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +/** + * Upstream handler which is responsible for determining whether a received HTTP request is a legal + * tunnel request, and if so, invoking the appropriate request method on the + * {@link ServerMessageSwitch} to service the request. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class AcceptedServerChannelRequestDispatch extends SimpleChannelUpstreamHandler +{ + + public static final String NAME = "AcceptedServerChannelRequestDispatch"; + + private static final InternalLogger LOG = InternalLoggerFactory + .getInstance(AcceptedServerChannelRequestDispatch.class); + + private final ServerMessageSwitchUpstreamInterface messageSwitch; + + public AcceptedServerChannelRequestDispatch(ServerMessageSwitchUpstreamInterface messageSwitch) + { + this.messageSwitch = messageSwitch; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + HttpRequest request = (HttpRequest) e.getMessage(); + + if (HttpTunnelMessageUtils.isOpenTunnelRequest(request)) + { + handleOpenTunnel(ctx); + } + else if (HttpTunnelMessageUtils.isSendDataRequest(request)) + { + handleSendData(ctx, request); + } + else if (HttpTunnelMessageUtils.isReceiveDataRequest(request)) + { + handleReceiveData(ctx, request); + } + else if (HttpTunnelMessageUtils.isCloseTunnelRequest(request)) + { + handleCloseTunnel(ctx, request); + } + else + { + respondWithRejection(ctx, request, "invalid request to netty HTTP tunnel gateway"); + } + } + + private void handleOpenTunnel(ChannelHandlerContext ctx) + { + String tunnelId = messageSwitch.createTunnel((InetSocketAddress) ctx.getChannel().getRemoteAddress()); + if (LOG.isDebugEnabled()) + { + LOG.debug("open tunnel request received from " + ctx.getChannel().getRemoteAddress() + " - allocated ID " + + tunnelId); + } + respondWith(ctx, HttpTunnelMessageUtils.createTunnelOpenResponse(tunnelId)); + } + + private void handleCloseTunnel(ChannelHandlerContext ctx, HttpRequest request) + { + String tunnelId = checkTunnelId(request, ctx); + if (tunnelId == null) + { + return; + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("close tunnel request received for tunnel " + tunnelId); + } + messageSwitch.clientCloseTunnel(tunnelId); + respondWith(ctx, HttpTunnelMessageUtils.createTunnelCloseResponse()).addListener(ChannelFutureListener.CLOSE); + } + + private void handleSendData(ChannelHandlerContext ctx, HttpRequest request) + { + String tunnelId = checkTunnelId(request, ctx); + if (tunnelId == null) + { + return; + } + if (LOG.isDebugEnabled()) + { + LOG.debug("send data request received for tunnel " + tunnelId); + } + + if (HttpHeaders.getContentLength(request) == 0 || request.getContent() == null + || request.getContent().readableBytes() == 0) + { + respondWithRejection(ctx, request, "Send data requests must contain data"); + return; + } + + messageSwitch.routeInboundData(tunnelId, request.getContent()); + respondWith(ctx, HttpTunnelMessageUtils.createSendDataResponse()); + } + + private void handleReceiveData(ChannelHandlerContext ctx, HttpRequest request) + { + String tunnelId = checkTunnelId(request, ctx); + if (tunnelId == null) + { + return; + } + if (LOG.isDebugEnabled()) + { + LOG.debug("poll data request received for tunnel " + tunnelId); + } + messageSwitch.pollOutboundData(tunnelId, ctx.getChannel()); + } + + private String checkTunnelId(HttpRequest request, ChannelHandlerContext ctx) + { + String tunnelId = HttpTunnelMessageUtils.extractTunnelId(request); + if (tunnelId == null) + { + respondWithRejection(ctx, request, "no tunnel id specified in request"); + } + else if (!messageSwitch.isOpenTunnel(tunnelId)) + { + respondWithRejection(ctx, request, "specified tunnel is either closed or does not exist"); + return null; + } + + return tunnelId; + } + + /** + * Sends the provided response back on the channel, returning the created ChannelFuture + * for this operation. + */ + private ChannelFuture respondWith(ChannelHandlerContext ctx, HttpResponse response) + { + ChannelFuture writeFuture = Channels.future(ctx.getChannel()); + Channels.write(ctx, writeFuture, response); + return writeFuture; + } + + /** + * Sends an HTTP 400 message back to on the channel with the specified error message, and asynchronously + * closes the channel after this is successfully sent. + */ + private void respondWithRejection(ChannelHandlerContext ctx, HttpRequest rejectedRequest, String errorMessage) + { + if (LOG.isWarnEnabled()) + { + SocketAddress remoteAddress = ctx.getChannel().getRemoteAddress(); + String tunnelId = HttpTunnelMessageUtils.extractTunnelId(rejectedRequest); + if (tunnelId == null) + { + tunnelId = ""; + } + LOG.warn("Rejecting request from " + remoteAddress + " representing tunnel " + tunnelId + ": " + errorMessage); + } + HttpResponse rejection = HttpTunnelMessageUtils.createRejection(rejectedRequest, errorMessage); + respondWith(ctx, rejection).addListener(ChannelFutureListener.CLOSE); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java new file mode 100644 index 00000000000..bada6091cd2 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java @@ -0,0 +1,78 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.HashSet; +import java.util.Set; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; + +/** + * Class which is used to consolidate multiple channel futures into one, by + * listening to the individual futures and producing an aggregated result + * (success/failure) when all futures have completed. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class ChannelFutureAggregator implements ChannelFutureListener +{ + + private final ChannelFuture aggregateFuture; + + private final Set pendingFutures; + + public ChannelFutureAggregator(ChannelFuture aggregateFuture) + { + this.aggregateFuture = aggregateFuture; + pendingFutures = new HashSet(); + } + + public void addFuture(ChannelFuture future) + { + pendingFutures.add(future); + future.addListener(this); + } + + public synchronized void operationComplete(ChannelFuture future) throws Exception + { + if (future.isCancelled()) + { + // TODO: what should the correct behaviour be when a fragment is cancelled? + // cancel all outstanding fragments and cancel the aggregate? + return; + } + + pendingFutures.remove(future); + if (!future.isSuccess()) + { + aggregateFuture.setFailure(future.getCause()); + for (ChannelFuture pendingFuture : pendingFutures) + { + pendingFuture.cancel(); + } + return; + } + + if (pendingFutures.isEmpty()) + { + aggregateFuture.setSuccess(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java new file mode 100644 index 00000000000..041aaac744a --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.security.SecureRandom; + +/** + * Default implementation of TunnelIdGenerator, which uses a + * {@link java.security.SecureRandom SecureRandom} generator + * to produce 32-bit tunnel identifiers. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class DefaultTunnelIdGenerator implements TunnelIdGenerator +{ + + private SecureRandom generator; + + public DefaultTunnelIdGenerator() + { + this(new SecureRandom()); + } + + public DefaultTunnelIdGenerator(SecureRandom generator) + { + this.generator = generator; + } + + public synchronized String generateId() + { + // synchronized to ensure that this code is thread safe. The Sun + // standard implementations seem to be synchronized or lock free + // but are not documented as guaranteeing this + return Integer.toHexString(generator.nextInt()); + } + +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java new file mode 100644 index 00000000000..1d10936f5eb --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java @@ -0,0 +1,117 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.jboss.netty.channel.Channels.fireChannelBound; +import static org.jboss.netty.channel.Channels.fireChannelConnected; +import static org.jboss.netty.channel.Channels.fireChannelOpen; + +import java.net.InetSocketAddress; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.AbstractChannel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.SocketChannel; +import org.jboss.netty.channel.socket.SocketChannelConfig; + +/** + * Represents the server end of an HTTP tunnel, created after a legal tunnel creation + * request is received from a client. The server end of a tunnel does not have any + * directly related TCP connections - the connections used by a client are likely + * to change over the lifecycle of a tunnel, especially when an HTTP proxy is in + * use. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class HttpTunnelAcceptedChannel extends AbstractChannel implements SocketChannel, HttpTunnelAcceptedChannelReceiver +{ + private final HttpTunnelAcceptedChannelConfig config; + + private final HttpTunnelAcceptedChannelSink sink; + + private final InetSocketAddress remoteAddress; + + protected HttpTunnelAcceptedChannel(HttpTunnelServerChannel parent, ChannelFactory factory, + ChannelPipeline pipeline, HttpTunnelAcceptedChannelSink sink, InetSocketAddress remoteAddress, HttpTunnelAcceptedChannelConfig config) + { + super(parent, factory, pipeline, sink); + this.config = config; + this.sink = sink; + this.remoteAddress = remoteAddress; + fireChannelOpen(this); + fireChannelBound(this, getLocalAddress()); + fireChannelConnected(this, getRemoteAddress()); + } + + public SocketChannelConfig getConfig() + { + return config; + } + + public InetSocketAddress getLocalAddress() + { + + return ((HttpTunnelServerChannel) getParent()).getLocalAddress(); + } + + public InetSocketAddress getRemoteAddress() + { + return remoteAddress; + } + + public boolean isBound() + { + return sink.isActive(); + } + + public boolean isConnected() + { + return sink.isActive(); + } + + public void clientClosed() + { + this.setClosed(); + Channels.fireChannelClosed(this); + } + + public void dataReceived(ChannelBuffer data) + { + Channels.fireMessageReceived(this, data); + } + + public void updateInterestOps(SaturationStateChange transition) { + switch(transition) { + case SATURATED: fireWriteEnabled(false); break; + case DESATURATED: fireWriteEnabled(true); break; + case NO_CHANGE: break; + } + } + + private void fireWriteEnabled(boolean enabled) { + int ops = OP_READ; + if(!enabled) { + ops |= OP_WRITE; + } + + setInterestOpsNow(ops); + Channels.fireChannelInterestChanged(this); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java new file mode 100644 index 00000000000..bdfa60109ec --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java @@ -0,0 +1,130 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +/** + * Configuration the server end of an http tunnel. + * + * These properties largely have no effect in the current implementation, and exist + * for API compatibility with TCP channels. With the exception of high / low water + * marks, any changes in the values will not be honoured. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelAcceptedChannelConfig extends HttpTunnelChannelConfig +{ + + private static final int SO_LINGER_DISABLED = -1; + + private static final int FAKE_SEND_BUFFER_SIZE = 16 * 1024; + + private static final int FAKE_RECEIVE_BUFFER_SIZE = 16 * 1024; + + // based on the values in RFC 791 + private static final int DEFAULT_TRAFFIC_CLASS = 0; + + @Override + public boolean isTcpNoDelay() + { + return true; + } + + @Override + public void setTcpNoDelay(boolean tcpNoDelay) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getSoLinger() + { + return SO_LINGER_DISABLED; + } + + @Override + public void setSoLinger(int soLinger) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getSendBufferSize() + { + return FAKE_SEND_BUFFER_SIZE; + } + + @Override + public void setSendBufferSize(int sendBufferSize) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getReceiveBufferSize() + { + return FAKE_RECEIVE_BUFFER_SIZE; + } + + @Override + public void setReceiveBufferSize(int receiveBufferSize) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public boolean isKeepAlive() + { + return true; + } + + @Override + public void setKeepAlive(boolean keepAlive) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getTrafficClass() + { + return DEFAULT_TRAFFIC_CLASS; + } + + @Override + public void setTrafficClass(int trafficClass) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public boolean isReuseAddress() + { + return false; + } + + @Override + public void setReuseAddress(boolean reuseAddress) + { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) + { + // we do not allow the value to be changed, as it will not be honoured + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java new file mode 100644 index 00000000000..600ac398511 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; + +/** + * Simple interface provided to a {@link ServerMessageSwitch}, allowing it to + * create the server end of tunnels in response to legal tunnel creation + * requests from clients. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +interface HttpTunnelAcceptedChannelFactory +{ + public HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId, InetSocketAddress remoteAddress); + + public String generateTunnelId(); +} \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java new file mode 100644 index 00000000000..ab9852273a2 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java @@ -0,0 +1,37 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * Interface from the server message switch and channel sink to an + * accepted channel. Exists primarily for mock testing purposes. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +interface HttpTunnelAcceptedChannelReceiver +{ + + public void updateInterestOps(SaturationStateChange transition); + + public void dataReceived(ChannelBuffer data); + + public void clientClosed(); + +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java new file mode 100644 index 00000000000..29e631576fa --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java @@ -0,0 +1,133 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.AbstractChannelSink; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; + +/** + * Sink for the server end of an http tunnel. Data sent down through the server end is dispatched + * from here to the ServerMessageSwitch, which queues the data awaiting a poll request from the + * client end of the tunnel. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class HttpTunnelAcceptedChannelSink extends AbstractChannelSink +{ + + private final SaturationManager saturationManager; + private final ServerMessageSwitchDownstreamInterface messageSwitch; + + private final String tunnelId; + + private AtomicBoolean active = new AtomicBoolean(false); + private HttpTunnelAcceptedChannelConfig config; + + public HttpTunnelAcceptedChannelSink(ServerMessageSwitchDownstreamInterface messageSwitch, String tunnelId, HttpTunnelAcceptedChannelConfig config) + { + this.messageSwitch = messageSwitch; + this.tunnelId = tunnelId; + this.config = config; + this.saturationManager = new SaturationManager(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark()); + } + + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception + { + if (e instanceof MessageEvent) + { + handleMessageEvent((MessageEvent) e); + } + else if (e instanceof ChannelStateEvent) + { + handleStateEvent((ChannelStateEvent) e); + } + } + + private void handleMessageEvent(MessageEvent ev) + { + if (!(ev.getMessage() instanceof ChannelBuffer)) + { + throw new IllegalArgumentException("Attempt to send data which is not a ChannelBuffer:" + ev.getMessage()); + } + + final HttpTunnelAcceptedChannelReceiver channel = (HttpTunnelAcceptedChannelReceiver) ev.getChannel(); + final ChannelBuffer message = (ChannelBuffer) ev.getMessage(); + final int messageSize = message.readableBytes(); + final ChannelFuture future = ev.getFuture(); + + saturationManager.updateThresholds(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark()); + channel.updateInterestOps(saturationManager.queueSizeChanged(messageSize)); + future.addListener(new ChannelFutureListener() + { + + @Override + public void operationComplete(ChannelFuture future) throws Exception + { + channel.updateInterestOps(saturationManager.queueSizeChanged(-messageSize)); + } + }); + messageSwitch.routeOutboundData(tunnelId, message, future); + } + + private void handleStateEvent(ChannelStateEvent ev) + { + /* TODO: as any of disconnect, unbind or close destroys a server + channel, should we fire all three events always? */ + Channel owner = ev.getChannel(); + switch (ev.getState()) + { + case OPEN : + if (Boolean.FALSE.equals(ev.getValue())) + { + messageSwitch.serverCloseTunnel(tunnelId); + active.set(false); + Channels.fireChannelClosed(owner); + } + break; + case BOUND : + if (ev.getValue() == null) + { + messageSwitch.serverCloseTunnel(tunnelId); + active.set(false); + Channels.fireChannelUnbound(owner); + } + case CONNECTED : + if (ev.getValue() == null) + { + messageSwitch.serverCloseTunnel(tunnelId); + active.set(false); + Channels.fireChannelDisconnected(owner); + } + } + } + + public boolean isActive() + { + return active.get(); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java new file mode 100644 index 00000000000..d8821b37938 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java @@ -0,0 +1,164 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.DefaultChannelConfig; +import org.jboss.netty.channel.socket.SocketChannelConfig; + +/** + * Configuration for HTTP tunnels. Where possible, properties set on this configuration will + * be applied to the two channels that service sending and receiving data on this end of the + * tunnel. + *

+ * HTTP tunnel clients have the following additional options: + * + * + * + * + * + * + * + *
NameAssociated setter method
{@code "writeBufferHighWaterMark"}{@link #setWriteBufferHighWaterMark(int)}
{@code "writeBufferLowWaterMark"}{@link #setWriteBufferLowWaterMark(int)}
+ * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public abstract class HttpTunnelChannelConfig extends DefaultChannelConfig implements SocketChannelConfig +{ + + /** + * The minimum value that the high water mark may be set to, in addition to the + * constraint that the high water mark must be strictly greater than the low + * water mark. + */ + public static final int MIN_HIGH_WATER_MARK = 1; + + /** + * The minimum value that the low water mark may be set to. + */ + public static final int MIN_LOW_WATER_MARK = 0; + + /** + * The default level for the write buffer's high water mark, presently set to + * 64KByte. + */ + public static final int DEFAULT_HIGH_WATER_MARK = 64 * 1024; + + /** + * The default level for the write buffer's low water mark, presently set to + * 32KByte. + */ + public static final int DEFAULT_LOW_WATER_MARK = 32 * 1024; + + static final String HIGH_WATER_MARK_OPTION = "writeBufferhHighWaterMark"; + + static final String LOW_WATER_MARK_OPTION = "writeBufferLowWaterMark"; + + + protected volatile int writeBufferLowWaterMark = DEFAULT_LOW_WATER_MARK; + + protected volatile int writeBufferHighWaterMark = DEFAULT_HIGH_WATER_MARK; + + /** + * @return the current value (in bytes) of the high water mark. + */ + public int getWriteBufferHighWaterMark() + { + return writeBufferHighWaterMark; + } + + /** + * Similarly to {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferHighWaterMark(int) + * NioSocketChannelConfig.setWriteBufferHighWaterMark()}, + * the high water mark refers to the buffer size at which a user of the channel should stop writing. When the + * number of queued bytes exceeds the high water mark, {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWritable()} will + * return false. Once the number of queued bytes falls below the {@link #setWriteBufferLowWaterMark(int) low water mark}, + * {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWritable()} will return true again, indicating that the client + * can begin to send more data. + * + * @param level the number of queued bytes required to flip {@link org.jboss.netty.channel.Channel#isWritable()} to + * false. + * + * @see {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferHighWaterMark(int) NioSocketChannelConfig.setWriteBufferHighWaterMark()} + */ + public void setWriteBufferHighWaterMark(int level) + { + if (level <= writeBufferLowWaterMark) + { + throw new IllegalArgumentException( + "Write buffer high water mark must be strictly greater than the low water mark"); + } + + if (level < MIN_HIGH_WATER_MARK) + { + throw new IllegalArgumentException("Cannot set write buffer high water mark lower than " + MIN_HIGH_WATER_MARK); + } + + writeBufferHighWaterMark = level; + } + + /** + * @return the current value (in bytes) of the low water mark. + */ + public int getWriteBufferLowWaterMark() + { + return writeBufferLowWaterMark; + } + + /** + * The low water mark refers to the "safe" size of the queued byte buffer at which more data can be enqueued. When + * the {@link #setWriteBufferHighWaterMark(int) high water mark} is exceeded, {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWriteable()} + * will return false until the buffer drops below this level. By creating a sufficient gap between the high and low + * water marks, rapid oscillation between "write enabled" and "write disabled" can be avoided. + * + * @see {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferLowWaterMark(int) NioSocketChannelConfig.setWriteBufferLowWaterMark()} + */ + public void setWriteBufferLowWaterMark(int level) + { + if (level >= writeBufferHighWaterMark) + { + throw new IllegalArgumentException( + "Write buffer low water mark must be strictly less than the high water mark"); + } + + if (level < MIN_LOW_WATER_MARK) + { + throw new IllegalArgumentException("Cannot set write buffer low water mark lower than " + MIN_LOW_WATER_MARK); + } + + writeBufferLowWaterMark = level; + } + + @Override + public boolean setOption(String key, Object value) + { + if (HIGH_WATER_MARK_OPTION.equals(key)) + { + setWriteBufferHighWaterMark((Integer) value); + } + else if (LOW_WATER_MARK_OPTION.equals(key)) + { + setWriteBufferLowWaterMark((Integer) value); + } + else + { + return super.setOption(key, value); + } + + return true; + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java new file mode 100644 index 00000000000..70bb38d4f54 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java @@ -0,0 +1,385 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.AbstractChannel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.socket.ClientSocketChannelFactory; +import org.jboss.netty.channel.socket.SocketChannel; +import org.jboss.netty.handler.codec.http.HttpChunkAggregator; +import org.jboss.netty.handler.codec.http.HttpRequestEncoder; +import org.jboss.netty.handler.codec.http.HttpResponseDecoder; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +/** + * The client end of an HTTP tunnel, created by an {@link HttpTunnelClientChannelFactory}. Channels of + * this type are designed to emulate a normal TCP based socket channel as far as is feasible within the limitations + * of the HTTP 1.1 protocol, and the usage patterns permitted by commonly used HTTP proxies and firewalls. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelClientChannel extends AbstractChannel implements SocketChannel +{ + + private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HttpTunnelClientChannel.class); + + private final HttpTunnelClientChannelConfig config; + + private final SocketChannel sendChannel; + + private final SocketChannel pollChannel; + + private volatile String tunnelId; + + private volatile ChannelFuture connectFuture; + + private volatile boolean connected; + + private volatile boolean bound; + + volatile InetSocketAddress serverAddress; + + private volatile String serverHostName; + + private final WorkerCallbacks callbackProxy; + + private final SaturationManager saturationManager; + + /** + * @see {@link HttpTunnelClientChannelFactory#newChannel(ChannelPipeline)} + */ + protected HttpTunnelClientChannel(ChannelFactory factory, ChannelPipeline pipeline, + HttpTunnelClientChannelSink sink, ClientSocketChannelFactory outboundFactory, ChannelGroup realConnections) + { + super(null, factory, pipeline, sink); + + this.callbackProxy = new WorkerCallbacks(); + + sendChannel = outboundFactory.newChannel(createSendPipeline()); + pollChannel = outboundFactory.newChannel(createPollPipeline()); + config = new HttpTunnelClientChannelConfig(sendChannel.getConfig(), pollChannel.getConfig()); + saturationManager = new SaturationManager(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark()); + serverAddress = null; + + realConnections.add(sendChannel); + realConnections.add(pollChannel); + + Channels.fireChannelOpen(this); + } + + public HttpTunnelClientChannelConfig getConfig() + { + return config; + } + + public boolean isBound() + { + return bound; + } + + public boolean isConnected() + { + return connected; + } + + public InetSocketAddress getLocalAddress() + { + return sendChannel.getLocalAddress(); + } + + public InetSocketAddress getRemoteAddress() + { + return serverAddress; + } + + void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress) + { + this.connectFuture = connectFuture; + /* if we are using a proxy, the remoteAddress is swapped here for the address of the proxy. + * The send and poll channels can later ask for the correct server address using + * getServerHostName(). + */ + serverAddress = remoteAddress; + + SocketAddress connectTarget; + if (config.getProxyAddress() != null) + { + connectTarget = config.getProxyAddress(); + } + else + { + connectTarget = remoteAddress; + } + + Channels.connect(sendChannel, connectTarget); + } + + void onDisconnectRequest(final ChannelFuture disconnectFuture) + { + ChannelFutureListener disconnectListener = new ConsolidatingFutureListener(disconnectFuture, 2); + sendChannel.disconnect().addListener(disconnectListener); + pollChannel.disconnect().addListener(disconnectListener); + + disconnectFuture.addListener(new ChannelFutureListener() + { + public void operationComplete(ChannelFuture future) throws Exception + { + serverAddress = null; + } + }); + } + + void onBindRequest(InetSocketAddress localAddress, final ChannelFuture bindFuture) + { + ChannelFutureListener bindListener = new ConsolidatingFutureListener(bindFuture, 2); + // bind the send channel to the specified local address, and the poll channel to + // an ephemeral port on the same interface as the send channel + sendChannel.bind(localAddress).addListener(bindListener); + InetSocketAddress pollBindAddress; + if (localAddress.isUnresolved()) + { + pollBindAddress = InetSocketAddress.createUnresolved(localAddress.getHostName(), 0); + } + else + { + pollBindAddress = new InetSocketAddress(localAddress.getAddress(), 0); + } + pollChannel.bind(pollBindAddress).addListener(bindListener); + } + + void onUnbindRequest(final ChannelFuture unbindFuture) + { + ChannelFutureListener unbindListener = new ConsolidatingFutureListener(unbindFuture, 2); + sendChannel.unbind().addListener(unbindListener); + pollChannel.unbind().addListener(unbindListener); + } + + void onCloseRequest(final ChannelFuture closeFuture) + { + ChannelFutureListener closeListener = new CloseConsolidatingFutureListener(closeFuture, 2); + sendChannel.close().addListener(closeListener); + pollChannel.close().addListener(closeListener); + } + + private ChannelPipeline createSendPipeline() + { + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("reqencoder", new HttpRequestEncoder()); // downstream + pipeline.addLast("respdecoder", new HttpResponseDecoder()); // upstream + pipeline.addLast("aggregator", new HttpChunkAggregator(HttpTunnelMessageUtils.MAX_BODY_SIZE)); // upstream + pipeline.addLast("sendHandler", new HttpTunnelClientSendHandler(callbackProxy)); // both + pipeline.addLast("writeFragmenter", new WriteFragmenter(HttpTunnelMessageUtils.MAX_BODY_SIZE)); + + return pipeline; + } + + private ChannelPipeline createPollPipeline() + { + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("reqencoder", new HttpRequestEncoder()); // downstream + pipeline.addLast("respdecoder", new HttpResponseDecoder()); // upstream + pipeline.addLast("aggregator", new HttpChunkAggregator(HttpTunnelMessageUtils.MAX_BODY_SIZE)); // upstream + pipeline.addLast(HttpTunnelClientPollHandler.NAME, new HttpTunnelClientPollHandler(callbackProxy)); // both + + return pipeline; + } + + private void setTunnelIdForPollChannel() + { + HttpTunnelClientPollHandler pollHandler = pollChannel.getPipeline().get(HttpTunnelClientPollHandler.class); + pollHandler.setTunnelId(tunnelId); + } + + void sendData(final MessageEvent e) + { + saturationManager.updateThresholds(config.getWriteBufferLowWaterMark(), + config.getWriteBufferHighWaterMark()); + final ChannelFuture originalFuture = e.getFuture(); + final ChannelBuffer message = (ChannelBuffer) e.getMessage(); + final int messageSize = message.readableBytes(); + updateSaturationStatus(messageSize); + Channels.write(sendChannel, e.getMessage()).addListener(new ChannelFutureListener() + { + public void operationComplete(ChannelFuture future) throws Exception + { + if (future.isSuccess()) + { + originalFuture.setSuccess(); + } + else + { + originalFuture.setFailure(future.getCause()); + } + updateSaturationStatus(-messageSize); + } + }); + } + + private void updateSaturationStatus(int queueSizeDelta) { + SaturationStateChange transition = saturationManager.queueSizeChanged(queueSizeDelta); + switch(transition) { + case SATURATED: fireWriteEnabled(false); break; + case DESATURATED: fireWriteEnabled(true); break; + case NO_CHANGE: break; + } + } + + private void fireWriteEnabled(boolean enabled) { + int ops = OP_READ; + if(!enabled) { + ops |= OP_WRITE; + } + + setInterestOpsNow(ops); + Channels.fireChannelInterestChanged(this); + } + + private class ConsolidatingFutureListener implements ChannelFutureListener + { + + private final ChannelFuture completionFuture; + + private final AtomicInteger eventsLeft; + + public ConsolidatingFutureListener(ChannelFuture completionFuture, int numToConsolidate) + { + this.completionFuture = completionFuture; + eventsLeft = new AtomicInteger(numToConsolidate); + } + + public void operationComplete(ChannelFuture future) throws Exception + { + if (!future.isSuccess()) + { + futureFailed(future); + } + else + { + if (eventsLeft.decrementAndGet() == 0) + { + allFuturesComplete(); + } + } + } + + protected void allFuturesComplete() + { + completionFuture.setSuccess(); + } + + protected void futureFailed(ChannelFuture future) + { + completionFuture.setFailure(future.getCause()); + } + } + + /** + * Close futures are a special case, as marking them as successful or failed has no effect. + * Instead, we must call setClosed() on the channel itself, once all the child channels are + * closed or if we fail to close them for whatever reason. + */ + private final class CloseConsolidatingFutureListener extends ConsolidatingFutureListener + { + + public CloseConsolidatingFutureListener(ChannelFuture completionFuture, int numToConsolidate) + { + super(completionFuture, numToConsolidate); + } + + @Override + protected void futureFailed(ChannelFuture future) + { + LOG.warn("Failed to close one of the child channels of tunnel " + tunnelId); + HttpTunnelClientChannel.this.setClosed(); + } + + @Override + protected void allFuturesComplete() + { + if (LOG.isDebugEnabled()) + { + LOG.debug("Tunnel " + tunnelId + " closed"); + } + HttpTunnelClientChannel.this.setClosed(); + } + + } + + /** + * Contains the implementing methods of HttpTunnelClientWorkerOwner, so that these are hidden + * from the public API. + */ + private class WorkerCallbacks implements HttpTunnelClientWorkerOwner + { + + public void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress) + { + HttpTunnelClientChannel.this.onConnectRequest(connectFuture, remoteAddress); + } + + public void onTunnelOpened(String tunnelId) + { + HttpTunnelClientChannel.this.tunnelId = tunnelId; + setTunnelIdForPollChannel(); + Channels.connect(pollChannel, sendChannel.getRemoteAddress()); + } + + public void fullyEstablished() + { + if (!bound) + { + bound = true; + Channels.fireChannelBound(HttpTunnelClientChannel.this, getLocalAddress()); + } + + connected = true; + connectFuture.setSuccess(); + Channels.fireChannelConnected(HttpTunnelClientChannel.this, getRemoteAddress()); + } + + public void onMessageReceived(ChannelBuffer content) + { + Channels.fireMessageReceived(HttpTunnelClientChannel.this, content); + } + + public String getServerHostName() + { + if (serverHostName == null) + { + serverHostName = HttpTunnelMessageUtils.convertToHostString(serverAddress); + } + + return serverHostName; + } + + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java new file mode 100644 index 00000000000..12667632ed4 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java @@ -0,0 +1,180 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.SocketAddress; + +import org.jboss.netty.channel.socket.SocketChannelConfig; + +/** + * Configuration for the client end of an HTTP tunnel. Any socket channel properties set here + * will be applied uniformly to the underlying send and poll channels, created from the channel + * factory provided to the {@link HttpTunnelClientChannelFactory}. + *

+ * HTTP tunnel clients have the following additional options: + * + * + * + * + * + * + * + * + *
NameAssociated setter method
{@code "proxyAddress"}{@link #setProxyAddress(SocketAddress)}
{@code "writeBufferHighWaterMark"}{@link #setWriteBufferHighWaterMark(long)}
{@code "writeBufferLowWaterMark"}{@link #setWriteBufferLowWaterMark(long)}
+ * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelClientChannelConfig extends HttpTunnelChannelConfig +{ + + static final String PROXY_ADDRESS_OPTION = "proxyAddress"; + + private final SocketChannelConfig sendChannelConfig; + + private final SocketChannelConfig pollChannelConfig; + + private volatile SocketAddress proxyAddress; + + HttpTunnelClientChannelConfig(SocketChannelConfig sendChannelConfig, SocketChannelConfig pollChannelConfig) + { + this.sendChannelConfig = sendChannelConfig; + this.pollChannelConfig = pollChannelConfig; + } + + /* HTTP TUNNEL SPECIFIC CONFIGURATION */ + // TODO Support all options in the old tunnel (see HttpTunnelingSocketChannelConfig) + // Mostly SSL, virtual host, and URL prefix + @Override + public boolean setOption(String key, Object value) + { + if (PROXY_ADDRESS_OPTION.equals(key)) + { + setProxyAddress((SocketAddress) value); + } + else + { + return super.setOption(key, value); + } + + return true; + } + + /** + * @return the address of the http proxy. If this is null, then no proxy + * should be used. + */ + public SocketAddress getProxyAddress() + { + return proxyAddress; + } + + /** + * Specify a proxy to be used for the http tunnel. If this is null, then + * no proxy should be used, otherwise this should be a directly accessible IPv4/IPv6 + * address and port. + */ + public void setProxyAddress(SocketAddress proxyAddress) + { + this.proxyAddress = proxyAddress; + } + + /* GENERIC SOCKET CHANNEL CONFIGURATION */ + + public int getReceiveBufferSize() + { + return pollChannelConfig.getReceiveBufferSize(); + } + + public int getSendBufferSize() + { + return pollChannelConfig.getSendBufferSize(); + } + + public int getSoLinger() + { + return pollChannelConfig.getSoLinger(); + } + + public int getTrafficClass() + { + return pollChannelConfig.getTrafficClass(); + } + + public boolean isKeepAlive() + { + return pollChannelConfig.isKeepAlive(); + } + + public boolean isReuseAddress() + { + return pollChannelConfig.isReuseAddress(); + } + + public boolean isTcpNoDelay() + { + return pollChannelConfig.isTcpNoDelay(); + } + + public void setKeepAlive(boolean keepAlive) + { + pollChannelConfig.setKeepAlive(keepAlive); + sendChannelConfig.setKeepAlive(keepAlive); + } + + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) + { + pollChannelConfig.setPerformancePreferences(connectionTime, latency, bandwidth); + sendChannelConfig.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + public void setReceiveBufferSize(int receiveBufferSize) + { + pollChannelConfig.setReceiveBufferSize(receiveBufferSize); + sendChannelConfig.setReceiveBufferSize(receiveBufferSize); + } + + public void setReuseAddress(boolean reuseAddress) + { + pollChannelConfig.setReuseAddress(reuseAddress); + sendChannelConfig.setReuseAddress(reuseAddress); + } + + public void setSendBufferSize(int sendBufferSize) + { + pollChannelConfig.setSendBufferSize(sendBufferSize); + sendChannelConfig.setSendBufferSize(sendBufferSize); + } + + public void setSoLinger(int soLinger) + { + pollChannelConfig.setSoLinger(soLinger); + sendChannelConfig.setSoLinger(soLinger); + } + + public void setTcpNoDelay(boolean tcpNoDelay) + { + pollChannelConfig.setTcpNoDelay(true); + sendChannelConfig.setTcpNoDelay(true); + } + + public void setTrafficClass(int trafficClass) + { + pollChannelConfig.setTrafficClass(1); + sendChannelConfig.setTrafficClass(1); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java new file mode 100644 index 00000000000..5842ec03125 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.DefaultChannelGroup; +import org.jboss.netty.channel.socket.ClientSocketChannelFactory; + +/** + * Factory used to create new client channels. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelClientChannelFactory implements ClientSocketChannelFactory +{ + + private final ClientSocketChannelFactory factory; + + private final ChannelGroup realConnections = new DefaultChannelGroup(); + + public HttpTunnelClientChannelFactory(ClientSocketChannelFactory factory) + { + if (factory == null) + { + throw new NullPointerException("factory"); + } + this.factory = factory; + } + + public HttpTunnelClientChannel newChannel(ChannelPipeline pipeline) + { + return new HttpTunnelClientChannel(this, pipeline, new HttpTunnelClientChannelSink(), factory, realConnections); + } + + public void releaseExternalResources() + { + realConnections.close().awaitUninterruptibly(); + factory.releaseExternalResources(); + } + +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java new file mode 100644 index 00000000000..6c5fa3121aa --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java @@ -0,0 +1,89 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; + +import org.jboss.netty.channel.AbstractChannelSink; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.MessageEvent; + +/** + * Sink of a client channel, deals with sunk events and then makes appropriate calls + * on the channel itself to push data. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class HttpTunnelClientChannelSink extends AbstractChannelSink +{ + + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception + { + if (e instanceof ChannelStateEvent) + { + handleChannelStateEvent((ChannelStateEvent) e); + } + else if (e instanceof MessageEvent) + { + handleMessageEvent((MessageEvent) e); + } + } + + private void handleMessageEvent(MessageEvent e) + { + HttpTunnelClientChannel channel = (HttpTunnelClientChannel) e.getChannel(); + channel.sendData(e); + } + + private void handleChannelStateEvent(ChannelStateEvent e) + { + HttpTunnelClientChannel channel = (HttpTunnelClientChannel) e.getChannel(); + + switch (e.getState()) + { + case CONNECTED : + if (e.getValue() != null) + { + channel.onConnectRequest(e.getFuture(), (InetSocketAddress) e.getValue()); + } + else + { + channel.onDisconnectRequest(e.getFuture()); + } + break; + case BOUND : + if (e.getValue() != null) + { + channel.onBindRequest((InetSocketAddress) e.getValue(), e.getFuture()); + } + else + { + channel.onUnbindRequest(e.getFuture()); + } + break; + case OPEN : + if (Boolean.FALSE.equals(e.getValue())) + { + channel.onCloseRequest(e.getFuture()); + } + break; + } + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java new file mode 100644 index 00000000000..ce8cfa9bad2 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java @@ -0,0 +1,104 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +/** + * Pipeline component which controls the client poll loop to the server. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class HttpTunnelClientPollHandler extends SimpleChannelHandler +{ + + public static final String NAME = "server2client"; + + private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HttpTunnelClientPollHandler.class); + + private String tunnelId; + + private final HttpTunnelClientWorkerOwner tunnelChannel; + + private long pollTime; + + public HttpTunnelClientPollHandler(HttpTunnelClientWorkerOwner tunnelChannel) + { + this.tunnelChannel = tunnelChannel; + } + + public void setTunnelId(String tunnelId) + { + this.tunnelId = tunnelId; + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + if (LOG.isDebugEnabled()) + { + LOG.debug("Poll channel for tunnel " + tunnelId + " established"); + } + tunnelChannel.fullyEstablished(); + sendPoll(ctx); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + HttpResponse response = (HttpResponse) e.getMessage(); + + if (HttpTunnelMessageUtils.isOKResponse(response)) + { + long rtTime = System.nanoTime() - pollTime; + if (LOG.isDebugEnabled()) + { + LOG.debug("OK response received for poll on tunnel " + tunnelId + " after " + rtTime + " ns"); + } + tunnelChannel.onMessageReceived(response.getContent()); + sendPoll(ctx); + } + else + { + if (LOG.isWarnEnabled()) + { + LOG.warn("non-OK response received for poll on tunnel " + tunnelId); + } + } + } + + private void sendPoll(ChannelHandlerContext ctx) + { + pollTime = System.nanoTime(); + if (LOG.isDebugEnabled()) + { + LOG.debug("sending poll request for tunnel " + tunnelId); + } + HttpRequest request = HttpTunnelMessageUtils + .createReceiveDataRequest(tunnelChannel.getServerHostName(), tunnelId); + Channels.write(ctx, Channels.future(ctx.getChannel()), request); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java new file mode 100644 index 00000000000..4ef997fd54c --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java @@ -0,0 +1,267 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.DownstreamMessageEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +/** + * Pipeline component which deals with sending data from the client to server. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class HttpTunnelClientSendHandler extends SimpleChannelHandler +{ + + public static final String NAME = "client2server"; + + private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HttpTunnelClientSendHandler.class); + + private final HttpTunnelClientWorkerOwner tunnelChannel; + + private String tunnelId = null; + + private final AtomicBoolean disconnecting; + + private ChannelStateEvent postShutdownEvent; + + private final ConcurrentLinkedQueue queuedWrites; + + private final AtomicInteger pendingRequestCount; + + private long sendRequestTime; + + public HttpTunnelClientSendHandler(HttpTunnelClientWorkerOwner tunnelChannel) + { + this.tunnelChannel = tunnelChannel; + queuedWrites = new ConcurrentLinkedQueue(); + pendingRequestCount = new AtomicInteger(0); + disconnecting = new AtomicBoolean(false); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + if (tunnelId == null) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("connection to " + e.getValue() + " succeeded - sending open tunnel request"); + } + HttpRequest request = HttpTunnelMessageUtils.createOpenTunnelRequest(tunnelChannel.getServerHostName()); + Channel thisChannel = ctx.getChannel(); + DownstreamMessageEvent event = new DownstreamMessageEvent(thisChannel, Channels.future(thisChannel), request, + thisChannel.getRemoteAddress()); + queuedWrites.offer(event); + pendingRequestCount.incrementAndGet(); + sendQueuedData(ctx); + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + HttpResponse response = (HttpResponse) e.getMessage(); + + if (HttpTunnelMessageUtils.isOKResponse(response)) + { + long roundTripTime = System.nanoTime() - sendRequestTime; + if (LOG.isDebugEnabled()) + { + LOG.debug("OK response received for tunnel " + tunnelId + ", after " + roundTripTime + " ns"); + } + sendNextAfterResponse(ctx); + } + else if (HttpTunnelMessageUtils.isTunnelOpenResponse(response)) + { + tunnelId = HttpTunnelMessageUtils.extractCookie(response); + if (LOG.isDebugEnabled()) + { + LOG.debug("tunnel open request accepted - id " + tunnelId); + } + tunnelChannel.onTunnelOpened(tunnelId); + sendNextAfterResponse(ctx); + } + else if (HttpTunnelMessageUtils.isTunnelCloseResponse(response)) + { + if (LOG.isDebugEnabled()) + { + if (disconnecting.get()) + { + LOG.debug("server acknowledged disconnect for tunnel " + tunnelId); + } + else + { + LOG.debug("server closed tunnel " + tunnelId); + } + } + ctx.sendDownstream(postShutdownEvent); + } + else + { + // TODO: kill connection + if (LOG.isWarnEnabled()) + { + LOG.warn("unknown response received for tunnel " + tunnelId + ", closing connection"); + } + Channels.close(ctx, ctx.getChannel().getCloseFuture()); + } + } + + private void sendNextAfterResponse(ChannelHandlerContext ctx) + { + if (pendingRequestCount.decrementAndGet() > 0) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("Immediately sending next send request for tunnel " + tunnelId); + } + sendQueuedData(ctx); + } + } + + private synchronized void sendQueuedData(ChannelHandlerContext ctx) + { + if (disconnecting.get()) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("sending close request for tunnel " + tunnelId); + } + HttpRequest closeRequest = HttpTunnelMessageUtils.createCloseTunnelRequest(tunnelChannel.getServerHostName(), + tunnelId); + Channels.write(ctx, Channels.future(ctx.getChannel()), closeRequest); + } + else + { + if (LOG.isDebugEnabled()) + { + LOG.debug("sending next request for tunnel " + tunnelId); + } + MessageEvent nextWrite = queuedWrites.poll(); + sendRequestTime = System.nanoTime(); + ctx.sendDownstream(nextWrite); + } + } + + @Override + public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + if (LOG.isDebugEnabled()) + { + LOG.debug("request to send data for tunnel " + tunnelId); + } + if (disconnecting.get()) + { + if (LOG.isWarnEnabled()) + { + LOG.warn("rejecting write request for tunnel " + tunnelId + " received after disconnect requested"); + } + e.getFuture().setFailure(new IllegalStateException("tunnel is closing")); + return; + } + ChannelBuffer data = (ChannelBuffer) e.getMessage(); + HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(tunnelChannel.getServerHostName(), tunnelId, + data); + DownstreamMessageEvent translatedEvent = new DownstreamMessageEvent(ctx.getChannel(), e.getFuture(), request, ctx + .getChannel().getRemoteAddress()); + queuedWrites.offer(translatedEvent); + if (pendingRequestCount.incrementAndGet() == 1) + { + sendQueuedData(ctx); + } + else + { + if (LOG.isDebugEnabled()) + { + LOG.debug("write request for tunnel " + tunnelId + " queued"); + } + } + } + + @Override + public void closeRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + shutdownTunnel(ctx, e); + } + + @Override + public void disconnectRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + shutdownTunnel(ctx, e); + } + + @Override + public void unbindRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + shutdownTunnel(ctx, e); + } + + private void shutdownTunnel(ChannelHandlerContext ctx, ChannelStateEvent postShutdownEvent) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("tunnel shutdown requested for send channel of tunnel " + tunnelId); + } + if (!ctx.getChannel().isConnected()) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("send channel of tunnel " + tunnelId + " is already disconnected"); + } + ctx.sendDownstream(postShutdownEvent); + return; + } + + if (!disconnecting.compareAndSet(false, true)) + { + if (LOG.isWarnEnabled()) + { + LOG.warn("tunnel shutdown process already initiated for tunnel " + tunnelId); + } + return; + } + + this.postShutdownEvent = postShutdownEvent; + + // if the channel is idle, send a close request immediately + if (pendingRequestCount.incrementAndGet() == 1) + { + sendQueuedData(ctx); + } + } + + public String getTunnelId() + { + return tunnelId; + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java new file mode 100644 index 00000000000..c34ee3e2ee9 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java @@ -0,0 +1,68 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelFuture; + +/** + * Interface which is used by the send and poll "worker" channels + * to notify the virtual tunnel channel of key events, and to get + * access to higher level information required for correct + * operation. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +interface HttpTunnelClientWorkerOwner +{ + /** + * The HTTP tunnel client sink invokes this when the application code requests the connection + * of an HTTP tunnel to the specified remote address. + */ + public void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress); + + /** + * The send channel handler calls this method when the server accepts the open tunnel request, + * returning a unique tunnel ID. + * + * @param tunnelId the server allocated tunnel ID + */ + public void onTunnelOpened(String tunnelId); + + /** + * The poll channel handler calls this method when the poll channel is connected, indicating + * that full duplex communications are now possible. + */ + public void fullyEstablished(); + + /** + * The poll handler calls this method when some data is received and decoded from the server. + * @param content the data received from the server + */ + public void onMessageReceived(ChannelBuffer content); + + /** + * @return the name of the server with whom we are communicating with - this is used within + * the HOST HTTP header for all requests. This is particularly important for operation behind + * a proxy, where the HOST string is used to route the request. + */ + public String getServerHostName(); + +} \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java new file mode 100644 index 00000000000..e5e28a96302 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java @@ -0,0 +1,363 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.handler.codec.http.DefaultHttpRequest; +import org.jboss.netty.handler.codec.http.DefaultHttpResponse; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.jboss.netty.handler.codec.http.HttpVersion; + +/** + * Utility class for creating http requests for the operation of the full duplex + * http tunnel, and verifying that received requests are of the correct types. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelMessageUtils +{ + + private static final String HTTP_URL_PREFIX = "http://"; + + /** + * An upper bound is enforced on the size of message bodies, so as + * to ensure we do not dump large chunks of data on either peer. + */ + public static final int MAX_BODY_SIZE = 16 * 1024; + + /** + * The tunnel will only accept connections from this specific user agent. This + * allows us to distinguish a legitimate tunnel connection from someone pointing + * a web browser or robot at the tunnel URL. + */ + static final String USER_AGENT = "HttpTunnelClient"; + + static final String OPEN_TUNNEL_REQUEST_URI = "/http-tunnel/open"; + + static final String CLOSE_TUNNEL_REQUEST_URI = "/http-tunnel/close"; + + static final String CLIENT_SEND_REQUEST_URI = "/http-tunnel/send"; + + static final String CLIENT_RECV_REQUEST_URI = "/http-tunnel/poll"; + + static final String CONTENT_TYPE = "application/octet-stream"; + + public static HttpRequest createOpenTunnelRequest(SocketAddress host) + { + return createOpenTunnelRequest(convertToHostString(host)); + } + + public static HttpRequest createOpenTunnelRequest(String host) + { + HttpRequest request = createRequestTemplate(host, null, OPEN_TUNNEL_REQUEST_URI); + setNoData(request); + return request; + } + + public static boolean isOpenTunnelRequest(HttpRequest request) + { + return isRequestTo(request, OPEN_TUNNEL_REQUEST_URI); + } + + public static boolean checkHost(HttpRequest request, SocketAddress expectedHost) + { + String host = request.getHeader(HttpHeaders.Names.HOST); + return expectedHost == null ? host == null : HttpTunnelMessageUtils.convertToHostString(expectedHost) + .equals(host); + } + + public static HttpRequest createSendDataRequest(SocketAddress host, String cookie, ChannelBuffer data) + { + return createSendDataRequest(convertToHostString(host), cookie, data); + } + + public static HttpRequest createSendDataRequest(String host, String cookie, ChannelBuffer data) + { + HttpRequest request = createRequestTemplate(host, cookie, CLIENT_SEND_REQUEST_URI); + request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, Long.toString(data.readableBytes())); + request.setContent(data); + + return request; + } + + public static boolean isSendDataRequest(HttpRequest request) + { + return isRequestTo(request, CLIENT_SEND_REQUEST_URI); + } + + public static HttpRequest createReceiveDataRequest(SocketAddress host, String tunnelId) + { + return createReceiveDataRequest(convertToHostString(host), tunnelId); + } + + public static HttpRequest createReceiveDataRequest(String host, String tunnelId) + { + HttpRequest request = createRequestTemplate(host, tunnelId, CLIENT_RECV_REQUEST_URI); + setNoData(request); + return request; + } + + public static boolean isReceiveDataRequest(HttpRequest request) + { + return isRequestTo(request, CLIENT_RECV_REQUEST_URI); + } + + public static HttpRequest createCloseTunnelRequest(String host, String tunnelId) + { + HttpRequest request = createRequestTemplate(host, tunnelId, CLOSE_TUNNEL_REQUEST_URI); + setNoData(request); + return request; + } + + public static boolean isCloseTunnelRequest(HttpRequest request) + { + return isRequestTo(request, CLOSE_TUNNEL_REQUEST_URI); + } + + public static boolean isServerToClientRequest(HttpRequest request) + { + return isRequestTo(request, CLIENT_RECV_REQUEST_URI); + } + + public static String convertToHostString(SocketAddress hostAddress) + { + StringWriter host = new StringWriter(); + InetSocketAddress inetSocketAddr = (InetSocketAddress) hostAddress; + InetAddress addr = inetSocketAddr.getAddress(); + if (addr instanceof Inet6Address) + { + host.append('['); + host.append(addr.getHostAddress()); + host.append(']'); + } + else if (addr != null) + { + host.append(addr.getHostAddress()); + } + else + { + host.append(inetSocketAddr.getHostName()); + } + + host.append(':'); + host.append(Integer.toString(inetSocketAddr.getPort())); + return host.toString(); + } + + private static HttpRequest createRequestTemplate(String host, String tunnelId, String uri) + { + HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, createCompleteUri(host, uri)); + request.setHeader(HttpHeaders.Names.HOST, host); + request.setHeader(HttpHeaders.Names.USER_AGENT, USER_AGENT); + if (tunnelId != null) + { + request.setHeader(HttpHeaders.Names.COOKIE, tunnelId); + } + + return request; + } + + private static String createCompleteUri(String host, String uri) + { + StringBuilder builder = new StringBuilder(HTTP_URL_PREFIX.length() + host.length() + uri.length()); + builder.append(HTTP_URL_PREFIX); + builder.append(host); + builder.append(uri); + + return builder.toString(); + } + + private static boolean isRequestTo(HttpRequest request, String uri) + { + URI decodedUri; + try + { + decodedUri = new URI(request.getUri()); + } + catch (URISyntaxException e) + { + return false; + } + + return HttpVersion.HTTP_1_1.equals(request.getProtocolVersion()) + && USER_AGENT.equals(request.getHeader(HttpHeaders.Names.USER_AGENT)) + && HttpMethod.POST.equals(request.getMethod()) && uri.equals(decodedUri.getPath()); + } + + private static void setNoData(HttpRequest request) + { + request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0"); + request.setContent(null); + } + + public static String extractTunnelId(HttpRequest request) + { + return request.getHeader(HttpHeaders.Names.COOKIE); + } + + private static byte[] toBytes(String string) + { + try + { + return string.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) + { + // UTF-8 is meant to be supported on all platforms + throw new RuntimeException("UTF-8 encoding not supported!"); + } + } + + public static HttpResponse createTunnelOpenResponse(String tunnelId) + { + HttpResponse response = createResponseTemplate(HttpResponseStatus.CREATED, null); + response.setHeader(HttpHeaders.Names.SET_COOKIE, tunnelId); + return response; + } + + public static boolean isTunnelOpenResponse(HttpResponse response) + { + return isResponseWithCode(response, HttpResponseStatus.CREATED); + } + + public static boolean isOKResponse(HttpResponse response) + { + return isResponseWithCode(response, HttpResponseStatus.OK); + } + + public static boolean hasContents(HttpResponse response, byte[] expectedContents) + { + if (response.getContent() != null && HttpHeaders.getContentLength(response) == expectedContents.length + && response.getContent().readableBytes() == expectedContents.length) + { + byte[] compareBytes = new byte[expectedContents.length]; + response.getContent().readBytes(compareBytes); + return Arrays.equals(expectedContents, compareBytes); + } + + return false; + } + + public static HttpResponse createTunnelCloseResponse() + { + HttpResponse response = createResponseTemplate(HttpResponseStatus.RESET_CONTENT, null); + return response; + } + + public static boolean isTunnelCloseResponse(HttpResponse response) + { + return isResponseWithCode(response, HttpResponseStatus.RESET_CONTENT); + } + + public static String extractCookie(HttpResponse response) + { + if (response.containsHeader(HttpHeaders.Names.SET_COOKIE)) + { + return response.getHeader(HttpHeaders.Names.SET_COOKIE); + } + + return null; + } + + public static HttpResponse createSendDataResponse() + { + return createOKResponseTemplate(null); + } + + public static HttpResponse createRecvDataResponse(ChannelBuffer data) + { + return createOKResponseTemplate(data); + } + + public static HttpResponse createRejection(HttpRequest request, String reason) + { + HttpVersion version = request != null ? request.getProtocolVersion() : HttpVersion.HTTP_1_1; + HttpResponse response = new DefaultHttpResponse(version, HttpResponseStatus.BAD_REQUEST); + response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=\"utf-8\""); + ChannelBuffer reasonBuffer = ChannelBuffers.wrappedBuffer(toBytes(reason)); + response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, Integer.toString(reasonBuffer.readableBytes())); + response.setContent(reasonBuffer); + return response; + } + + public static boolean isRejection(HttpResponse response) + { + return !HttpResponseStatus.OK.equals(response.getStatus()); + } + + public static Object extractErrorMessage(HttpResponse response) + { + if (response.getContent() == null || HttpHeaders.getContentLength(response) == 0) + { + return ""; + } + + byte[] bytes = new byte[response.getContent().readableBytes()]; + response.getContent().readBytes(bytes); + try + { + return new String(bytes, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + return ""; + } + } + + private static boolean isResponseWithCode(HttpResponse response, HttpResponseStatus status) + { + return HttpVersion.HTTP_1_1.equals(response.getProtocolVersion()) && status.equals(response.getStatus()); + } + + private static HttpResponse createOKResponseTemplate(ChannelBuffer data) + { + return createResponseTemplate(HttpResponseStatus.OK, data); + } + + private static HttpResponse createResponseTemplate(HttpResponseStatus status, ChannelBuffer data) + { + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status); + if (data != null) + { + response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, Integer.toString(data.readableBytes())); + response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream"); + response.setContent(data); + } + else + { + response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0"); + response.setContent(null); + } + return response; + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java new file mode 100644 index 00000000000..e0efaa8422e --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java @@ -0,0 +1,113 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; + +import org.jboss.netty.channel.AbstractServerChannel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineException; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jboss.netty.channel.socket.ServerSocketChannelConfig; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelServerChannel extends AbstractServerChannel implements ServerSocketChannel +{ + + private final ServerSocketChannel realChannel; + + private final HttpTunnelServerChannelConfig config; + + private final ServerMessageSwitch messageSwitch; + + private final ChannelFutureListener CLOSE_FUTURE_PROXY = new ChannelFutureListener() + { + public void operationComplete(ChannelFuture future) throws Exception + { + HttpTunnelServerChannel.this.setClosed(); + } + }; + + protected HttpTunnelServerChannel(HttpTunnelServerChannelFactory factory, ChannelPipeline pipeline) + { + super(factory, pipeline, new HttpTunnelServerChannelSink()); + + messageSwitch = new ServerMessageSwitch(new TunnelCreator()); + realChannel = factory.createRealChannel(this, messageSwitch); + HttpTunnelServerChannelSink sink = (HttpTunnelServerChannelSink) getPipeline().getSink(); + sink.setRealChannel(realChannel); + sink.setCloseListener(CLOSE_FUTURE_PROXY); + config = new HttpTunnelServerChannelConfig(realChannel); + Channels.fireChannelOpen(this); + } + + public ServerSocketChannelConfig getConfig() + { + return config; + } + + public InetSocketAddress getLocalAddress() + { + return realChannel.getLocalAddress(); + } + + public InetSocketAddress getRemoteAddress() + { + // server channels never have a remote address + return null; + } + + public boolean isBound() + { + return realChannel.isBound(); + } + + /** + * Used to hide the newChannel method from the public API. + */ + private final class TunnelCreator implements HttpTunnelAcceptedChannelFactory + { + + public HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId, InetSocketAddress remoteAddress) + { + ChannelPipeline childPipeline = null; + try + { + childPipeline = getConfig().getPipelineFactory().getPipeline(); + } + catch (Exception e) + { + throw new ChannelPipelineException("Failed to initialize a pipeline.", e); + } + HttpTunnelAcceptedChannelConfig config = new HttpTunnelAcceptedChannelConfig(); + HttpTunnelAcceptedChannelSink sink = new HttpTunnelAcceptedChannelSink(messageSwitch, newTunnelId, config); + return new HttpTunnelAcceptedChannel(HttpTunnelServerChannel.this, getFactory(), childPipeline, sink, + remoteAddress, config); + } + + public String generateTunnelId() + { + return config.getTunnelIdGenerator().generateId(); + } + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java new file mode 100644 index 00000000000..b41bd5a8451 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java @@ -0,0 +1,150 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.Map; +import java.util.Map.Entry; + +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jboss.netty.channel.socket.ServerSocketChannelConfig; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelServerChannelConfig implements ServerSocketChannelConfig +{ + + private ChannelPipelineFactory pipelineFactory; + + private final ServerSocketChannel realChannel; + + private TunnelIdGenerator tunnelIdGenerator = new DefaultTunnelIdGenerator(); + + public HttpTunnelServerChannelConfig(ServerSocketChannel realChannel) + { + this.realChannel = realChannel; + } + + private ServerSocketChannelConfig getWrappedConfig() + { + return realChannel.getConfig(); + } + + public int getBacklog() + { + return getWrappedConfig().getBacklog(); + } + + public int getReceiveBufferSize() + { + return getWrappedConfig().getReceiveBufferSize(); + } + + public boolean isReuseAddress() + { + return getWrappedConfig().isReuseAddress(); + } + + public void setBacklog(int backlog) + { + getWrappedConfig().setBacklog(backlog); + } + + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) + { + getWrappedConfig().setPerformancePreferences(connectionTime, latency, bandwidth); + } + + public void setReceiveBufferSize(int receiveBufferSize) + { + getWrappedConfig().setReceiveBufferSize(receiveBufferSize); + } + + public void setReuseAddress(boolean reuseAddress) + { + getWrappedConfig().setReuseAddress(reuseAddress); + } + + public ChannelBufferFactory getBufferFactory() + { + return getWrappedConfig().getBufferFactory(); + } + + public int getConnectTimeoutMillis() + { + return getWrappedConfig().getConnectTimeoutMillis(); + } + + public ChannelPipelineFactory getPipelineFactory() + { + return pipelineFactory; + } + + public void setBufferFactory(ChannelBufferFactory bufferFactory) + { + getWrappedConfig().setBufferFactory(bufferFactory); + } + + public void setConnectTimeoutMillis(int connectTimeoutMillis) + { + getWrappedConfig().setConnectTimeoutMillis(connectTimeoutMillis); + } + + public boolean setOption(String name, Object value) + { + if (name.equals("pipelineFactory")) + { + setPipelineFactory((ChannelPipelineFactory) value); + return true; + } + else if (name.equals("tunnelIdGenerator")) + { + setTunnelIdGenerator((TunnelIdGenerator) value); + return true; + } + else + { + return getWrappedConfig().setOption(name, value); + } + } + + public void setOptions(Map options) + { + for (Entry e : options.entrySet()) + { + setOption(e.getKey(), e.getValue()); + } + } + + public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) + { + this.pipelineFactory = pipelineFactory; + } + + public void setTunnelIdGenerator(TunnelIdGenerator tunnelIdGenerator) + { + this.tunnelIdGenerator = tunnelIdGenerator; + } + + public TunnelIdGenerator getTunnelIdGenerator() + { + return tunnelIdGenerator; + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java new file mode 100644 index 00000000000..322fe7d6bb7 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.DefaultChannelGroup; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jboss.netty.channel.socket.ServerSocketChannelFactory; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class HttpTunnelServerChannelFactory implements ServerSocketChannelFactory +{ + + private final ServerSocketChannelFactory realConnectionFactory; + + private final ChannelGroup realConnections; + + public HttpTunnelServerChannelFactory(ServerSocketChannelFactory realConnectionFactory) + { + this.realConnectionFactory = realConnectionFactory; + realConnections = new DefaultChannelGroup(); + } + + public HttpTunnelServerChannel newChannel(ChannelPipeline pipeline) + { + return new HttpTunnelServerChannel(this, pipeline); + } + + ServerSocketChannel createRealChannel(HttpTunnelServerChannel channel, ServerMessageSwitch messageSwitch) + { + ChannelPipeline realChannelPipeline = Channels.pipeline(); + AcceptedServerChannelPipelineFactory realPipelineFactory = new AcceptedServerChannelPipelineFactory(messageSwitch); + realChannelPipeline.addFirst(TunnelWrappedServerChannelHandler.NAME, new TunnelWrappedServerChannelHandler( + channel, realPipelineFactory, realConnections)); + ServerSocketChannel newChannel = realConnectionFactory.newChannel(realChannelPipeline); + realConnections.add(newChannel); + return newChannel; + } + + public void releaseExternalResources() + { + realConnections.close().awaitUninterruptibly(); + realConnectionFactory.releaseExternalResources(); + } + +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java new file mode 100644 index 00000000000..39551409dba --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java @@ -0,0 +1,99 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.SocketAddress; + +import org.jboss.netty.channel.AbstractChannelSink; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.socket.ServerSocketChannel; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class HttpTunnelServerChannelSink extends AbstractChannelSink +{ + + private ChannelFutureListener closeHook; + + private ServerSocketChannel realChannel; + + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception + { + + if (e instanceof ChannelStateEvent) + { + ChannelStateEvent ev = (ChannelStateEvent) e; + switch (ev.getState()) + { + case OPEN : + if (Boolean.FALSE.equals(ev.getValue())) + { + realChannel.close().addListener(closeHook); + } + break; + case BOUND : + if (ev.getValue() != null) + { + realChannel.bind((SocketAddress) ev.getValue()).addListener(new ChannelFutureProxy(e.getFuture())); + } + else + { + realChannel.unbind().addListener(new ChannelFutureProxy(e.getFuture())); + } + break; + } + } + } + + private final class ChannelFutureProxy implements ChannelFutureListener + { + private final ChannelFuture upstreamFuture; + + ChannelFutureProxy(ChannelFuture upstreamFuture) + { + this.upstreamFuture = upstreamFuture; + } + + public void operationComplete(ChannelFuture future) throws Exception + { + if (future.isSuccess()) + { + upstreamFuture.setSuccess(); + } + else + { + upstreamFuture.setFailure(future.getCause()); + } + } + } + + public void setRealChannel(ServerSocketChannel realChannel) + { + this.realChannel = realChannel; + } + + public void setCloseListener(ChannelFutureListener closeHook) + { + this.closeHook = closeHook; + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java new file mode 100644 index 00000000000..c54d9472ae9 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java @@ -0,0 +1,68 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.DESATURATED; +import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.NO_CHANGE; +import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.SATURATED; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * This class is used to monitor the amount of data that has yet to be pushed to + * the underlying socket, in order to implement the "high/low water mark" facility + * that controls Channel.isWritable() and the interest ops of http tunnels. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class SaturationManager +{ + private AtomicLong desaturationPoint; + private AtomicLong saturationPoint; + private final AtomicLong queueSize; + private final AtomicBoolean saturated; + + public SaturationManager(long desaturationPoint, long saturationPoint) + { + this.desaturationPoint = new AtomicLong(desaturationPoint); + this.saturationPoint = new AtomicLong(saturationPoint); + queueSize = new AtomicLong(0); + saturated = new AtomicBoolean(false); + } + + public SaturationStateChange queueSizeChanged(long sizeDelta) { + long newQueueSize = queueSize.addAndGet(sizeDelta); + if(newQueueSize <= desaturationPoint.get()) { + if(saturated.compareAndSet(true, false)) { + return DESATURATED; + } + } else if(newQueueSize > saturationPoint.get()) { + if(saturated.compareAndSet(false, true)) { + return SATURATED; + } + } + + return NO_CHANGE; + } + + public void updateThresholds(long desaturationPoint, long saturationPoint) { + this.desaturationPoint.set(desaturationPoint); + this.saturationPoint.set(saturationPoint); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java new file mode 100644 index 00000000000..3173ef8706f --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java @@ -0,0 +1,31 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +/** + * Represents the state change of a chanel in response in the amount of pending data to be + * sent - either no change occurs, the channel becomes desaturated (indicating that writing + * can safely commence) or it becomes saturated (indicating that writing should cease). + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +enum SaturationStateChange { + NO_CHANGE, + DESATURATED, + SATURATED +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java new file mode 100644 index 00000000000..31918020f78 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java @@ -0,0 +1,268 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +/** + * This is the gateway between the accepted TCP channels that are used to communicate with the client + * ends of the http tunnel and the virtual server accepted tunnel. As a tunnel can last for longer than + * the lifetime of the client channels that are used to service it, this layer of abstraction is + * necessary. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class ServerMessageSwitch implements ServerMessageSwitchUpstreamInterface, ServerMessageSwitchDownstreamInterface +{ + + private static final InternalLogger LOG = InternalLoggerFactory.getInstance(ServerMessageSwitch.class.getName()); + + private final String tunnelIdPrefix; + + private final HttpTunnelAcceptedChannelFactory newChannelFactory; + + private final ConcurrentHashMap tunnelsById; + + public ServerMessageSwitch(HttpTunnelAcceptedChannelFactory newChannelFactory) + { + this.newChannelFactory = newChannelFactory; + tunnelIdPrefix = Long.toHexString(new Random().nextLong()); + tunnelsById = new ConcurrentHashMap(); + } + + public String createTunnel(InetSocketAddress remoteAddress) + { + String newTunnelId = String.format("%s_%s", tunnelIdPrefix, newChannelFactory.generateTunnelId()); + TunnelInfo newTunnel = new TunnelInfo(); + newTunnel.tunnelId = newTunnelId; + tunnelsById.put(newTunnelId, newTunnel); + newTunnel.localChannel = newChannelFactory.newChannel(newTunnelId, remoteAddress); + return newTunnelId; + } + + public boolean isOpenTunnel(String tunnelId) + { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + return tunnel != null; + } + + public void pollOutboundData(String tunnelId, Channel channel) + { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + if (tunnel == null) + { + if (LOG.isWarnEnabled()) + { + LOG.warn("Poll request for tunnel " + tunnelId + " which does not exist or already closed"); + } + respondAndClose(channel, + HttpTunnelMessageUtils.createRejection(null, "Unknown tunnel, possibly already closed")); + return; + } + + if (!tunnel.responseChannel.compareAndSet(null, channel)) + { + if (LOG.isWarnEnabled()) + { + LOG.warn("Duplicate poll request detected for tunnel " + tunnelId); + } + respondAndClose(channel, + HttpTunnelMessageUtils.createRejection(null, "Only one poll request at a time per tunnel allowed")); + return; + } + + sendQueuedData(tunnel); + } + + private void respondAndClose(Channel channel, HttpResponse response) + { + Channels.write(channel, response).addListener(ChannelFutureListener.CLOSE); + } + + private void sendQueuedData(TunnelInfo state) + { + Queue queuedData = state.queuedResponses; + Channel responseChannel = state.responseChannel.getAndSet(null); + if (responseChannel == null) + { + // no response channel, or another thread has already used it + return; + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("sending response for tunnel id " + state.tunnelId + " to " + responseChannel.getRemoteAddress()); + } + QueuedResponse messageToSend = queuedData.poll(); + if (messageToSend == null) + { + // no data to send, restore the response channel and bail out + state.responseChannel.set(responseChannel); + return; + } + + HttpResponse response = HttpTunnelMessageUtils.createRecvDataResponse(messageToSend.data); + final ChannelFuture originalFuture = messageToSend.writeFuture; + Channels.write(responseChannel, response).addListener(new RelayedChannelFutureListener(originalFuture)); + } + + public TunnelStatus routeInboundData(String tunnelId, ChannelBuffer inboundData) + { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + if (tunnel == null) + { + return TunnelStatus.CLOSED; + } + + if (tunnel.closing.get()) + { + // client has now been notified, forget the tunnel + tunnelsById.remove(tunnel); + return TunnelStatus.CLOSED; + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("routing inbound data for tunnel " + tunnelId); + } + tunnel.localChannel.dataReceived(inboundData); + return TunnelStatus.ALIVE; + } + + public void clientCloseTunnel(String tunnelId) + { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + tunnel.localChannel.clientClosed(); + tunnelsById.remove(tunnelId); + } + + public void serverCloseTunnel(String tunnelId) + { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + tunnel.closing.set(true); + + Channel responseChannel = tunnel.responseChannel.getAndSet(null); + if (responseChannel == null) + { + // response channel is already in use, client will be notified + // of close at next opportunity + return; + } + + respondAndClose(responseChannel, HttpTunnelMessageUtils.createTunnelCloseResponse()); + // client has been notified, forget the tunnel + tunnelsById.remove(tunnelId); + } + + public void routeOutboundData(String tunnelId, ChannelBuffer data, ChannelFuture writeFuture) + { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + if (tunnel == null) + { + // tunnel is closed + if (LOG.isWarnEnabled()) + { + LOG.warn("attempt made to send data out on tunnel id " + tunnelId + " which is unknown or closed"); + } + return; + } + + ChannelFutureAggregator aggregator = new ChannelFutureAggregator(writeFuture); + List fragments = WriteSplitter.split(data, HttpTunnelMessageUtils.MAX_BODY_SIZE); + + if (LOG.isDebugEnabled()) + { + LOG.debug("routing outbound data for tunnel " + tunnelId); + } + for (ChannelBuffer fragment : fragments) + { + ChannelFuture fragmentFuture = Channels.future(writeFuture.getChannel()); + aggregator.addFuture(fragmentFuture); + tunnel.queuedResponses.offer(new QueuedResponse(fragment, fragmentFuture)); + } + + sendQueuedData(tunnel); + } + + /** + * Used to pass the result received from one ChannelFutureListener to another verbatim. + */ + private final class RelayedChannelFutureListener implements ChannelFutureListener + { + private final ChannelFuture originalFuture; + + private RelayedChannelFutureListener(ChannelFuture originalFuture) + { + this.originalFuture = originalFuture; + } + + public void operationComplete(ChannelFuture future) throws Exception + { + if (future.isSuccess()) + { + originalFuture.setSuccess(); + } + else + { + originalFuture.setFailure(future.getCause()); + } + } + } + + private static final class TunnelInfo + { + public String tunnelId; + + public HttpTunnelAcceptedChannelReceiver localChannel; + + public final AtomicReference responseChannel = new AtomicReference(null); + + public final Queue queuedResponses = new ConcurrentLinkedQueue(); + + public final AtomicBoolean closing = new AtomicBoolean(false); + } + + private static final class QueuedResponse + { + public ChannelBuffer data; + + public ChannelFuture writeFuture; + + QueuedResponse(ChannelBuffer data, ChannelFuture writeFuture) + { + this.data = data; + this.writeFuture = writeFuture; + } + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java new file mode 100644 index 00000000000..6e4e8552c12 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java @@ -0,0 +1,36 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelFuture; + +/** + * The interface from a HttpTunnelAcceptedChannel to the ServerMessageSwitch. + * This primarily exists for mock object testing purposes. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +interface ServerMessageSwitchDownstreamInterface +{ + + public void serverCloseTunnel(String tunnelId); + + public void routeOutboundData(String tunnelId, ChannelBuffer data, ChannelFuture writeFuture); + +} \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java new file mode 100644 index 00000000000..9491f6bd469 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java @@ -0,0 +1,57 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; + +/** + * The interface from a TCP channel which is being used to communicate with the client + * end of an http tunnel and the server message switch. + * + * This primarily exists for mock testing purposes. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +interface ServerMessageSwitchUpstreamInterface +{ + + public String createTunnel(InetSocketAddress remoteAddress); + + public boolean isOpenTunnel(String tunnelId); + + public void clientCloseTunnel(String tunnelId); + + /** + * Passes some received data from a client for forwarding to the server's view + * of the tunnel. + * @return the current status of the tunnel. ALIVE indicates the tunnel is still + * functional, CLOSED indicates it is closed and the client should be notified + * of this (and will be forgotten after this notification). + */ + public TunnelStatus routeInboundData(String tunnelId, ChannelBuffer inboundData); + + public void pollOutboundData(String tunnelId, Channel responseChannel); + + public static enum TunnelStatus { + ALIVE, CLOSED + } + +} \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java new file mode 100644 index 00000000000..75a5388917d --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +/** + * This interface is used by the server end of an http tunnel to generate new + * tunnel ids for accepted client connections. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public interface TunnelIdGenerator +{ + + /** + * Generates the next tunnel ID to be used, which must be unique + * (i.e. ensure with high probability that it will not clash with + * an existing tunnel ID). This method must be thread safe, and + * preferably lock free. + */ + public String generateId(); + +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java new file mode 100644 index 00000000000..6f3ba2796a5 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.SocketAddress; + +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ChildChannelStateEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.group.ChannelGroup; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +class TunnelWrappedServerChannelHandler extends SimpleChannelUpstreamHandler +{ + + public static final String NAME = "TunnelWrappedServerChannelHandler"; + + private final HttpTunnelServerChannel tunnelChannel; + + private final AcceptedServerChannelPipelineFactory pipelineFactory; + + private final ChannelGroup allChannels; + + public TunnelWrappedServerChannelHandler(HttpTunnelServerChannel tunnelChannel, + AcceptedServerChannelPipelineFactory pipelineFactory, ChannelGroup allChannels) + { + this.tunnelChannel = tunnelChannel; + this.pipelineFactory = pipelineFactory; + this.allChannels = allChannels; + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + e.getChannel().getConfig().setPipelineFactory(pipelineFactory); + super.channelOpen(ctx, e); + } + + @Override + public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + Channels.fireChannelBound(tunnelChannel, (SocketAddress) e.getValue()); + super.channelBound(ctx, e); + } + + @Override + public void channelUnbound(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + Channels.fireChannelUnbound(tunnelChannel); + super.channelUnbound(ctx, e); + } + + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + Channels.fireChannelClosed(tunnelChannel); + super.channelClosed(ctx, e); + } + + @Override + public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception + { + allChannels.add(e.getChildChannel()); + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java new file mode 100644 index 00000000000..5236f3497e5 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelDownstreamHandler; + +/** + * Downstream handler which places an upper bound on the size of written + * {@link ChannelBuffer ChannelBuffers}. If a buffer + * is bigger than the specified upper bound, the buffer is broken up + * into two or more smaller pieces. + *

+ * This is utilised by the http tunnel to smooth out the per-byte latency, + * by placing an upper bound on HTTP request / response body sizes. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public class WriteFragmenter extends SimpleChannelDownstreamHandler +{ + + public static final String NAME = "writeFragmenter"; + + private int splitThreshold; + + public WriteFragmenter(int splitThreshold) + { + this.splitThreshold = splitThreshold; + } + + public void setSplitThreshold(int splitThreshold) + { + this.splitThreshold = splitThreshold; + } + + @Override + public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + ChannelBuffer data = (ChannelBuffer) e.getMessage(); + + if (data.readableBytes() <= splitThreshold) + { + super.writeRequested(ctx, e); + } + else + { + List fragments = WriteSplitter.split(data, splitThreshold); + ChannelFutureAggregator aggregator = new ChannelFutureAggregator(e.getFuture()); + for (ChannelBuffer fragment : fragments) + { + ChannelFuture fragmentFuture = Channels.future(ctx.getChannel(), true); + aggregator.addFuture(fragmentFuture); + Channels.write(ctx, fragmentFuture, fragment); + } + } + } +} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java b/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java new file mode 100644 index 00000000000..e3cf664f9d5 --- /dev/null +++ b/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; + +/** + * Provides functionality to split a provided ChannelBuffer into multiple fragments which fit + * under a specified size threshold. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + * @author OneDrum Ltd. + */ +public final class WriteSplitter +{ + + public static List split(ChannelBuffer buffer, int splitThreshold) + { + int listSize = (int) ((float) buffer.readableBytes() / splitThreshold); + ArrayList fragmentList = new ArrayList(listSize); + + if (buffer.readableBytes() > splitThreshold) + { + int slicePosition = buffer.readerIndex(); + while (slicePosition < buffer.writerIndex()) + { + int chunkSize = Math.min(splitThreshold, buffer.writerIndex() - slicePosition); + ChannelBuffer chunk = buffer.slice(slicePosition, chunkSize); + fragmentList.add(chunk); + slicePosition += chunkSize; + } + } + else + { + fragmentList.add(ChannelBuffers.wrappedBuffer(buffer)); + } + + return fragmentList; + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java new file mode 100644 index 00000000000..b219566a499 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java @@ -0,0 +1,247 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelState; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class AcceptedServerChannelRequestDispatchTest +{ + + private static final String HOST = "test.server.com"; + + private static final String KNOWN_TUNNEL_ID = "1"; + + protected static final String UNKNOWN_TUNNEL_ID = "unknownTunnel"; + + JUnit4Mockery mockContext = new JUnit4Mockery(); + + private AcceptedServerChannelRequestDispatch handler; + + FakeSocketChannel channel; + + private FakeChannelSink sink; + + ServerMessageSwitchUpstreamInterface messageSwitch; + + @Before + public void setUp() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + messageSwitch = mockContext.mock(ServerMessageSwitchUpstreamInterface.class); + handler = new AcceptedServerChannelRequestDispatch(messageSwitch); + pipeline.addLast(AcceptedServerChannelRequestDispatch.NAME, handler); + sink = new FakeChannelSink(); + channel = new FakeSocketChannel(null, null, pipeline, sink); + channel.remoteAddress = InetSocketAddress.createUnresolved("test.client.com", 51231); + + mockContext.checking(new Expectations() + { + { + ignoring(messageSwitch).isOpenTunnel(KNOWN_TUNNEL_ID); + will(returnValue(true)); + } + }); + } + + @Test + public void testTunnelOpenRequest() + { + mockContext.checking(new Expectations() + { + { + one(messageSwitch).createTunnel(channel.remoteAddress); + will(returnValue(KNOWN_TUNNEL_ID)); + } + }); + + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createOpenTunnelRequest(HOST)); + assertEquals(1, sink.events.size()); + HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isTunnelOpenResponse(response)); + } + + @Test + public void testTunnelCloseRequest() + { + mockContext.checking(new Expectations() + { + { + one(messageSwitch).clientCloseTunnel(KNOWN_TUNNEL_ID); + } + }); + + HttpRequest request = HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, KNOWN_TUNNEL_ID); + Channels.fireMessageReceived(channel, request); + assertEquals(1, sink.events.size()); + ChannelEvent responseEvent = sink.events.poll(); + HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseEvent, HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isTunnelCloseResponse(response)); + checkClosesAfterWrite(responseEvent); + } + + @Test + public void testTunnelCloseRequestWithoutTunnelIdRejected() + { + HttpRequest request = HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, null); + checkRequestWithoutTunnelIdIsRejected(request); + } + + @Test + public void testTunnelCloseRequestWithUnknownTunnelId() + { + HttpRequest request = HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, UNKNOWN_TUNNEL_ID); + checkRequestWithUnknownTunnelIdIsRejected(request); + } + + @Test + public void testSendDataRequest() + { + final ChannelBuffer expectedData = ChannelBuffers.dynamicBuffer(); + expectedData.writeLong(1234L); + mockContext.checking(new Expectations() + { + { + one(messageSwitch).routeInboundData(KNOWN_TUNNEL_ID, expectedData); + } + }); + + HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, KNOWN_TUNNEL_ID, expectedData); + Channels.fireMessageReceived(channel, request); + + assertEquals(1, sink.events.size()); + HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isOKResponse(response)); + } + + @Test + public void testSendDataRequestWithNoContentRejected() + { + HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, KNOWN_TUNNEL_ID, + ChannelBuffers.dynamicBuffer()); + Channels.fireMessageReceived(channel, request); + + assertEquals(1, sink.events.size()); + checkResponseIsRejection("Send data requests must contain data"); + } + + @Test + public void testSendDataRequestForUnknownTunnelIdRejected() + { + HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, UNKNOWN_TUNNEL_ID, + ChannelBuffers.dynamicBuffer()); + checkRequestWithUnknownTunnelIdIsRejected(request); + } + + @Test + public void testSendDataRequestWithoutTunnelIdRejected() + { + HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, null, ChannelBuffers.dynamicBuffer()); + checkRequestWithoutTunnelIdIsRejected(request); + } + + @Test + public void testReceiveDataRequest() + { + mockContext.checking(new Expectations() + { + { + one(messageSwitch).pollOutboundData(KNOWN_TUNNEL_ID, channel); + } + }); + HttpRequest request = HttpTunnelMessageUtils.createReceiveDataRequest(HOST, KNOWN_TUNNEL_ID); + Channels.fireMessageReceived(channel, request); + } + + @Test + public void testReceiveDataRequestWithoutTunnelIdRejected() + { + HttpRequest request = HttpTunnelMessageUtils.createReceiveDataRequest(HOST, null); + checkRequestWithoutTunnelIdIsRejected(request); + } + + @Test + public void testReceiveDataRequestForUnknownTunnelIdRejected() + { + HttpRequest request = HttpTunnelMessageUtils.createReceiveDataRequest(HOST, UNKNOWN_TUNNEL_ID); + checkRequestWithUnknownTunnelIdIsRejected(request); + } + + private void checkRequestWithoutTunnelIdIsRejected(HttpRequest request) + { + Channels.fireMessageReceived(channel, request); + assertEquals(1, sink.events.size()); + ChannelEvent responseEvent = checkResponseIsRejection("no tunnel id specified in request"); + checkClosesAfterWrite(responseEvent); + } + + private void checkRequestWithUnknownTunnelIdIsRejected(HttpRequest request) + { + mockContext.checking(new Expectations() + { + { + one(messageSwitch).isOpenTunnel(UNKNOWN_TUNNEL_ID); + will(returnValue(false)); + } + }); + + Channels.fireMessageReceived(channel, request); + assertEquals(1, sink.events.size()); + ChannelEvent responseEvent = checkResponseIsRejection("specified tunnel is either closed or does not exist"); + checkClosesAfterWrite(responseEvent); + } + + private ChannelEvent checkResponseIsRejection(String errorMessage) + { + ChannelEvent responseEvent = sink.events.poll(); + + HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseEvent, HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isRejection(response)); + assertEquals(errorMessage, HttpTunnelMessageUtils.extractErrorMessage(response)); + + return responseEvent; + } + + private void checkClosesAfterWrite(ChannelEvent responseEvent) + { + responseEvent.getFuture().setSuccess(); + assertEquals(1, sink.events.size()); + NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.OPEN, false); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java new file mode 100644 index 00000000000..1b853da6859 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java @@ -0,0 +1,237 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.Map; +import java.util.Map.Entry; + +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.buffer.HeapChannelBufferFactory; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.SocketChannelConfig; +import org.jboss.netty.util.internal.ConversionUtil; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeChannelConfig implements SocketChannelConfig +{ + + private int receiveBufferSize = 1024; + + private int sendBufferSize = 1024; + + private int soLinger = 500; + + private int trafficClass = 0; + + private boolean keepAlive = true; + + private boolean reuseAddress = true; + + private boolean tcpNoDelay = false; + + private ChannelBufferFactory bufferFactory = new HeapChannelBufferFactory(); + + private int connectTimeout = 5000; + + private ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() + { + public ChannelPipeline getPipeline() throws Exception + { + return Channels.pipeline(); + } + }; + + private int writeTimeout = 3000; + + public int getReceiveBufferSize() + { + return receiveBufferSize; + } + + public void setReceiveBufferSize(int receiveBufferSize) + { + this.receiveBufferSize = receiveBufferSize; + } + + public int getSendBufferSize() + { + return sendBufferSize; + } + + public void setSendBufferSize(int sendBufferSize) + { + this.sendBufferSize = sendBufferSize; + } + + public int getSoLinger() + { + return soLinger; + } + + public void setSoLinger(int soLinger) + { + this.soLinger = soLinger; + } + + public int getTrafficClass() + { + return trafficClass; + } + + public void setTrafficClass(int trafficClass) + { + this.trafficClass = trafficClass; + } + + public boolean isKeepAlive() + { + return keepAlive; + } + + public void setKeepAlive(boolean keepAlive) + { + this.keepAlive = keepAlive; + } + + public boolean isReuseAddress() + { + return reuseAddress; + } + + public void setReuseAddress(boolean reuseAddress) + { + this.reuseAddress = reuseAddress; + } + + public boolean isTcpNoDelay() + { + return tcpNoDelay; + } + + public void setTcpNoDelay(boolean tcpNoDelay) + { + this.tcpNoDelay = tcpNoDelay; + } + + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) + { + // do nothing + } + + public ChannelBufferFactory getBufferFactory() + { + return bufferFactory; + } + + public void setBufferFactory(ChannelBufferFactory bufferFactory) + { + this.bufferFactory = bufferFactory; + } + + public int getConnectTimeoutMillis() + { + return connectTimeout; + } + + public void setConnectTimeoutMillis(int connectTimeoutMillis) + { + connectTimeout = connectTimeoutMillis; + } + + public ChannelPipelineFactory getPipelineFactory() + { + return pipelineFactory; + } + + public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) + { + this.pipelineFactory = pipelineFactory; + } + + public int getWriteTimeoutMillis() + { + return writeTimeout; + } + + public void setWriteTimeoutMillis(int writeTimeoutMillis) + { + writeTimeout = writeTimeoutMillis; + } + + public boolean setOption(String key, Object value) + { + if (key.equals("pipelineFactory")) + { + setPipelineFactory((ChannelPipelineFactory) value); + } + else if (key.equals("connectTimeoutMillis")) + { + setConnectTimeoutMillis(ConversionUtil.toInt(value)); + } + else if (key.equals("bufferFactory")) + { + setBufferFactory((ChannelBufferFactory) value); + } + else if (key.equals("receiveBufferSize")) + { + setReceiveBufferSize(ConversionUtil.toInt(value)); + } + else if (key.equals("sendBufferSize")) + { + setSendBufferSize(ConversionUtil.toInt(value)); + } + else if (key.equals("tcpNoDelay")) + { + setTcpNoDelay(ConversionUtil.toBoolean(value)); + } + else if (key.equals("keepAlive")) + { + setKeepAlive(ConversionUtil.toBoolean(value)); + } + else if (key.equals("reuseAddress")) + { + setReuseAddress(ConversionUtil.toBoolean(value)); + } + else if (key.equals("soLinger")) + { + setSoLinger(ConversionUtil.toInt(value)); + } + else if (key.equals("trafficClass")) + { + setTrafficClass(ConversionUtil.toInt(value)); + } + else + { + return false; + } + return true; + } + + public void setOptions(Map options) + { + for (Entry e : options.entrySet()) + { + setOption(e.getKey(), e.getValue()); + } + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java new file mode 100644 index 00000000000..7ed63f9ac86 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java @@ -0,0 +1,40 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.LinkedList; +import java.util.Queue; + +import org.jboss.netty.channel.AbstractChannelSink; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelPipeline; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeChannelSink extends AbstractChannelSink +{ + + public Queue events = new LinkedList(); + + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception + { + events.add(e); + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java new file mode 100644 index 00000000000..3958b011718 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.socket.ClientSocketChannelFactory; +import org.jboss.netty.channel.socket.SocketChannel; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeClientSocketChannelFactory implements ClientSocketChannelFactory +{ + + public List createdChannels; + + public FakeClientSocketChannelFactory() + { + createdChannels = new ArrayList(); + } + + public SocketChannel newChannel(ChannelPipeline pipeline) + { + FakeSocketChannel channel = new FakeSocketChannel(null, this, pipeline, new FakeChannelSink()); + createdChannels.add(channel); + return channel; + } + + public void releaseExternalResources() + { + // nothing to do + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java new file mode 100644 index 00000000000..0bda1e4f6ad --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java @@ -0,0 +1,92 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.jboss.netty.channel.Channels.fireChannelBound; +import static org.jboss.netty.channel.Channels.fireChannelConnected; +import static org.jboss.netty.channel.Channels.fireChannelOpen; + +import java.net.InetSocketAddress; + +import org.jboss.netty.channel.AbstractChannel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelSink; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jboss.netty.channel.socket.ServerSocketChannelConfig; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeServerSocketChannel extends AbstractChannel implements ServerSocketChannel +{ + + public boolean bound; + + public boolean connected; + + public InetSocketAddress remoteAddress; + + public InetSocketAddress localAddress; + + public ServerSocketChannelConfig config = new FakeServerSocketChannelConfig(); + + public FakeServerSocketChannel(ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink) + { + super(null, factory, pipeline, sink); + } + + public ServerSocketChannelConfig getConfig() + { + return config; + } + + public InetSocketAddress getLocalAddress() + { + return localAddress; + } + + public InetSocketAddress getRemoteAddress() + { + return remoteAddress; + } + + public boolean isBound() + { + return bound; + } + + public boolean isConnected() + { + return connected; + } + + public FakeSocketChannel acceptNewConnection(InetSocketAddress remoteAddress, ChannelSink sink) throws Exception + { + ChannelPipeline newPipeline = getConfig().getPipelineFactory().getPipeline(); + FakeSocketChannel newChannel = new FakeSocketChannel(this, getFactory(), newPipeline, sink); + newChannel.localAddress = localAddress; + newChannel.remoteAddress = remoteAddress; + fireChannelOpen(newChannel); + fireChannelBound(newChannel, newChannel.localAddress); + fireChannelConnected(this, newChannel.remoteAddress); + + return newChannel; + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java new file mode 100644 index 00000000000..539456c343e --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java @@ -0,0 +1,80 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.buffer.HeapChannelBufferFactory; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.DefaultChannelConfig; +import org.jboss.netty.channel.socket.ServerSocketChannelConfig; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeServerSocketChannelConfig extends DefaultChannelConfig implements ServerSocketChannelConfig +{ + + public int backlog = 5; + + public int receiveBufferSize = 1024; + + public boolean reuseAddress = false; + + public int connectionTimeout = 5000; + + public ChannelPipelineFactory pipelineFactory; + + public int writeTimeout = 5000; + + public ChannelBufferFactory bufferFactory = new HeapChannelBufferFactory(); + + public int getBacklog() + { + return backlog; + } + + public void setBacklog(int backlog) + { + this.backlog = backlog; + } + + public int getReceiveBufferSize() + { + return receiveBufferSize; + } + + public void setReceiveBufferSize(int receiveBufferSize) + { + this.receiveBufferSize = receiveBufferSize; + } + + public boolean isReuseAddress() + { + return reuseAddress; + } + + public void setReuseAddress(boolean reuseAddress) + { + this.reuseAddress = reuseAddress; + } + + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) + { + // ignore + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java new file mode 100644 index 00000000000..1da5623cc9f --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelSink; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jboss.netty.channel.socket.ServerSocketChannelFactory; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeServerSocketChannelFactory implements ServerSocketChannelFactory +{ + + public ChannelSink sink = new FakeChannelSink(); + + public FakeServerSocketChannel createdChannel; + + public ServerSocketChannel newChannel(ChannelPipeline pipeline) + { + createdChannel = new FakeServerSocketChannel(this, pipeline, sink); + return createdChannel; + } + + public void releaseExternalResources() + { + // nothing to do + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java new file mode 100644 index 00000000000..1cc94eb7602 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java @@ -0,0 +1,115 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; + +import org.jboss.netty.channel.AbstractChannel; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelSink; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.SocketChannel; +import org.jboss.netty.channel.socket.SocketChannelConfig; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class FakeSocketChannel extends AbstractChannel implements SocketChannel +{ + + public InetSocketAddress localAddress; + + public InetSocketAddress remoteAddress; + + public SocketChannelConfig config = new FakeChannelConfig(); + + public boolean bound = false; + + public boolean connected = false; + + public ChannelSink sink; + + public FakeSocketChannel(Channel parent, ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink) + { + super(parent, factory, pipeline, sink); + this.sink = sink; + } + + public InetSocketAddress getLocalAddress() + { + return localAddress; + } + + public SocketChannelConfig getConfig() + { + return config; + } + + public InetSocketAddress getRemoteAddress() + { + return remoteAddress; + } + + public boolean isBound() + { + return bound; + } + + public boolean isConnected() + { + return connected; + } + + public void emulateConnected(InetSocketAddress localAddress, InetSocketAddress remoteAddress, + ChannelFuture connectedFuture) + { + if (connected) + { + return; + } + + emulateBound(localAddress, null); + this.remoteAddress = remoteAddress; + connected = true; + Channels.fireChannelConnected(this, remoteAddress); + if (connectedFuture != null) + { + connectedFuture.setSuccess(); + } + } + + public void emulateBound(InetSocketAddress localAddress, ChannelFuture boundFuture) + { + if (bound) + { + return; + } + + bound = true; + this.localAddress = localAddress; + Channels.fireChannelBound(this, localAddress); + if (boundFuture != null) + { + boundFuture.setSuccess(); + } + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java new file mode 100644 index 00000000000..123e85af4f2 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class HttpTunnelAcceptedChannelSinkTest +{ + + private static final String TUNNEL_ID = "1"; + + private final JUnit4Mockery mockContext = new JUnit4Mockery(); + + ServerMessageSwitchDownstreamInterface messageSwitch; + + private HttpTunnelAcceptedChannelSink sink; + + private FakeSocketChannel channel; + + private UpstreamEventCatcher upstreamCatcher; + + @Before + public void setUp() throws Exception + { + messageSwitch = mockContext.mock(ServerMessageSwitchDownstreamInterface.class); + sink = new HttpTunnelAcceptedChannelSink(messageSwitch, TUNNEL_ID, new HttpTunnelAcceptedChannelConfig()); + ChannelPipeline pipeline = Channels.pipeline(); + upstreamCatcher = new UpstreamEventCatcher(); + pipeline.addLast(UpstreamEventCatcher.NAME, upstreamCatcher); + channel = new FakeSocketChannel(null, null, pipeline, sink); + upstreamCatcher.events.clear(); + } + + @Test + public void testSendInvalidDataType() + { + Channels.write(channel, new Object()); + assertEquals(1, upstreamCatcher.events.size()); + NettyTestUtils.checkIsExceptionEvent(upstreamCatcher.events.poll()); + } + + @Test + public void testUnbind() + { + mockContext.checking(new Expectations() + { + { + one(messageSwitch).serverCloseTunnel(TUNNEL_ID); + } + }); + Channels.unbind(channel); + } + + @Test + public void testDisconnect() + { + mockContext.checking(new Expectations() + { + { + one(messageSwitch).serverCloseTunnel(TUNNEL_ID); + } + }); + + Channels.disconnect(channel); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java new file mode 100644 index 00000000000..c2308957db2 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java @@ -0,0 +1,329 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.jboss.netty.channel.socket.SocketChannelConfig; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class HttpTunnelClientChannelConfigTest +{ + + JUnit4Mockery mockContext = new JUnit4Mockery(); + + SocketChannelConfig sendChannelConfig; + + SocketChannelConfig pollChannelConfig; + + HttpTunnelClientChannelConfig config; + + @Before + public void setUp() + { + sendChannelConfig = mockContext.mock(SocketChannelConfig.class, "sendChannelConfig"); + pollChannelConfig = mockContext.mock(SocketChannelConfig.class, "pollChannelConfig"); + + config = new HttpTunnelClientChannelConfig(sendChannelConfig, pollChannelConfig); + } + + @Test + public void testGetReceiveBufferSize() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).getReceiveBufferSize(); + will(returnValue(100)); + } + }); + + assertEquals(100, config.getReceiveBufferSize()); + } + + @Test + public void testGetSendBufferSize() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).getSendBufferSize(); + will(returnValue(100)); + } + }); + + assertEquals(100, config.getSendBufferSize()); + } + + @Test + public void testGetSoLinger() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).getSoLinger(); + will(returnValue(100)); + } + }); + + assertEquals(100, config.getSoLinger()); + } + + @Test + public void testTrafficClass() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).getTrafficClass(); + will(returnValue(1)); + } + }); + + assertEquals(1, config.getTrafficClass()); + } + + @Test + public void testIsKeepAlive() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).isKeepAlive(); + will(returnValue(true)); + } + }); + + assertTrue(config.isKeepAlive()); + } + + @Test + public void testIsReuseAddress() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).isReuseAddress(); + will(returnValue(true)); + } + }); + + assertTrue(config.isReuseAddress()); + } + + @Test + public void testIsTcpNoDelay() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).isTcpNoDelay(); + will(returnValue(true)); + } + }); + + assertTrue(config.isTcpNoDelay()); + } + + @Test + public void testSetKeepAlive() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setKeepAlive(true); + one(sendChannelConfig).setKeepAlive(true); + } + }); + + config.setKeepAlive(true); + } + + @Test + public void testSetPerformancePreferences() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setPerformancePreferences(100, 200, 300); + one(sendChannelConfig).setPerformancePreferences(100, 200, 300); + } + }); + + config.setPerformancePreferences(100, 200, 300); + } + + @Test + public void testSetReceiveBufferSize() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setReceiveBufferSize(100); + one(sendChannelConfig).setReceiveBufferSize(100); + } + }); + + config.setReceiveBufferSize(100); + } + + @Test + public void testSetReuseAddress() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setReuseAddress(true); + one(sendChannelConfig).setReuseAddress(true); + } + }); + + config.setReuseAddress(true); + } + + @Test + public void testSetSendBufferSize() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setSendBufferSize(100); + one(sendChannelConfig).setSendBufferSize(100); + } + }); + + config.setSendBufferSize(100); + } + + @Test + public void testSetSoLinger() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setSoLinger(100); + one(sendChannelConfig).setSoLinger(100); + } + }); + + config.setSoLinger(100); + } + + @Test + public void testTcpNoDelay() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setTcpNoDelay(true); + one(sendChannelConfig).setTcpNoDelay(true); + } + }); + + config.setTcpNoDelay(true); + } + + @Test + public void testSetTrafficClass() + { + mockContext.checking(new Expectations() + { + { + one(pollChannelConfig).setTrafficClass(1); + one(sendChannelConfig).setTrafficClass(1); + } + }); + + config.setTrafficClass(1); + } + + @Test + public void testSetHighWaterMark() + { + config.setWriteBufferHighWaterMark(128 * 1024); + assertEquals(128 * 1024, config.getWriteBufferHighWaterMark()); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetHighWaterMark_negative() + { + config.setWriteBufferHighWaterMark(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetHighWaterMark_zero() + { + config.setWriteBufferHighWaterMark(0); + } + + @Test + public void testSetLowWaterMark() + { + config.setWriteBufferLowWaterMark(100); + assertEquals(100, config.getWriteBufferLowWaterMark()); + } + + @Test + public void testSetLowWaterMark_zero() + { + // zero is permitted for the low water mark, unlike high water mark + config.setWriteBufferLowWaterMark(0); + assertEquals(0, config.getWriteBufferLowWaterMark()); + } + + @Test + public void testSetHighWaterMark_lowerThanLow() + { + config.setWriteBufferLowWaterMark(100); + try + { + config.setWriteBufferHighWaterMark(80); + fail("expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + assertEquals("Write buffer high water mark must be strictly greater than the low water mark", e.getMessage()); + } + } + + @Test + public void testSetLowWaterMark_higherThanHigh() + { + config.setWriteBufferHighWaterMark(128 * 1024); + try + { + config.setWriteBufferLowWaterMark(256 * 1024); + fail("expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + assertEquals("Write buffer low water mark must be strictly less than the high water mark", e.getMessage()); + } + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java new file mode 100644 index 00000000000..a7ed487015c --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java @@ -0,0 +1,255 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelState; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.junit.Before; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class HttpTunnelClientChannelTest +{ + + public static final int LOCAL_PORT = 50123; + + /** used to emulate the selection of a random port in response to a bind request + * on an ephemeral port. + */ + public static final int OTHER_LOCAL_PORT = 40652; + + public static final InetSocketAddress LOCAL_ADDRESS = InetSocketAddress.createUnresolved("localhost", LOCAL_PORT); + + public static final InetSocketAddress LOCAL_ADDRESS_EPHEMERAL_PORT = InetSocketAddress.createUnresolved("localhost", + 0); + + public static final InetSocketAddress REMOTE_ADDRESS = InetSocketAddress.createUnresolved("test.server.com", 12345); + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT; + + static + { + try + { + InetAddress localhostIPV4 = InetAddress.getByAddress(new byte[] + {127, 0, 0, 1}); + InetAddress localhostIPV6 = InetAddress.getByAddress(new byte[] + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}); + RESOLVED_LOCAL_ADDRESS_IPV4 = new InetSocketAddress(localhostIPV4, LOCAL_PORT); + RESOLVED_LOCAL_ADDRESS_IPV6 = new InetSocketAddress(localhostIPV6, LOCAL_PORT); + RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT = new InetSocketAddress(localhostIPV4, 0); + RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT = new InetSocketAddress(localhostIPV6, 0); + RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT = new InetSocketAddress(localhostIPV4, OTHER_LOCAL_PORT); + RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT = new InetSocketAddress(localhostIPV6, OTHER_LOCAL_PORT); + } + catch (UnknownHostException e) + { + throw new RuntimeException( + "Creation of InetAddresses should not fail when explicitly specified and the correct length", e); + } + } + + private UpstreamEventCatcher upstreamCatcher; + + private HttpTunnelClientChannel channel; + + private FakeClientSocketChannelFactory outboundFactory; + + private FakeSocketChannel sendChannel; + + private FakeSocketChannel pollChannel; + + private FakeChannelSink sendSink; + + private FakeChannelSink pollSink; + + @Before + public void setUp() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + upstreamCatcher = new UpstreamEventCatcher(); + pipeline.addLast(UpstreamEventCatcher.NAME, upstreamCatcher); + + outboundFactory = new FakeClientSocketChannelFactory(); + + HttpTunnelClientChannelFactory factory = new HttpTunnelClientChannelFactory(outboundFactory); + channel = factory.newChannel(pipeline); + + assertEquals(2, outboundFactory.createdChannels.size()); + + sendChannel = outboundFactory.createdChannels.get(0); + pollChannel = outboundFactory.createdChannels.get(1); + sendSink = (FakeChannelSink) sendChannel.sink; + pollSink = (FakeChannelSink) pollChannel.sink; + } + + @Test + public void testConnect() + { + Channels.connect(channel, REMOTE_ADDRESS); + + // this should result in a CONNECTED state event on the send channel, but not on the poll + // channel just yet + assertEquals(1, sendSink.events.size()); + assertEquals(0, pollSink.events.size()); + ChannelEvent sendChannelEvent = sendSink.events.poll(); + NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.CONNECTED, REMOTE_ADDRESS); + + // once the send channel indicates that it is connected, we should see the tunnel open request + // being sent + sendChannel.emulateConnected(LOCAL_ADDRESS, REMOTE_ADDRESS, ((ChannelStateEvent) sendChannelEvent).getFuture()); + assertEquals(1, sendSink.events.size()); + ChannelEvent openTunnelRequest = sendSink.events.poll(); + NettyTestUtils.checkIsDownstreamMessageEvent(openTunnelRequest, ChannelBuffer.class); + } + + @Test + public void testBind_unresolvedAddress() + { + // requesting a binding with an unresolved local address + // should attempt to bind the send channel with that address unaltered + // and attempt to bind the poll address with the same host name but + // an ephemeral port. We emulate a resolved IPV4 address for the bind + // response. + checkBinding(LOCAL_ADDRESS, LOCAL_ADDRESS, LOCAL_ADDRESS_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV4, + RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT); + } + + @Test + public void testBind_resolvedAddress_ipv4() + { + // variant that uses resolved addresses. The bind request + // for the poll channel should also use a resolved address, + // built from the provided resolved address. + checkBinding(RESOLVED_LOCAL_ADDRESS_IPV4, RESOLVED_LOCAL_ADDRESS_IPV4, + RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV4, + RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT); + } + + @Test + public void testBind_resolvedAddress_ipv6() + { + // variant that uses a resolved IPV6 address. + // bind request on the poll channel should use the same + // IPv6 host, with an ephemeral port. + checkBinding(RESOLVED_LOCAL_ADDRESS_IPV6, RESOLVED_LOCAL_ADDRESS_IPV6, + RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV6, + RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT); + } + + private void checkBinding(InetSocketAddress requestedBindAddress, InetSocketAddress expectedPollBindRequest, + InetSocketAddress expectedSendBindRequest, InetSocketAddress emulatedPollBindAddress, + InetSocketAddress emulatedSendBindAddress) + { + + ChannelFuture bindFuture = Channels.bind(channel, requestedBindAddress); + assertFalse(bindFuture.isDone()); + + assertEquals(1, sendSink.events.size()); + assertEquals(1, pollSink.events.size()); + + ChannelEvent sendChannelEvent = sendSink.events.poll(); + NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.BOUND, expectedPollBindRequest); + ChannelEvent pollChannelEvent = pollSink.events.poll(); + NettyTestUtils.checkIsStateEvent(pollChannelEvent, ChannelState.BOUND, expectedSendBindRequest); + + sendChannel.emulateBound(emulatedPollBindAddress, sendChannelEvent.getFuture()); + assertFalse(bindFuture.isDone()); + pollChannel.emulateBound(emulatedSendBindAddress, pollChannelEvent.getFuture()); + assertTrue(bindFuture.isDone()); + assertTrue(bindFuture.isSuccess()); + + assertEquals(channel.getLocalAddress(), emulatedPollBindAddress); + } + + @Test + public void testBind_preResolvedAddress_ipv6() + { + ChannelFuture bindFuture = Channels.bind(channel, RESOLVED_LOCAL_ADDRESS_IPV6); + assertFalse(bindFuture.isDone()); + + assertEquals(1, sendSink.events.size()); + assertEquals(1, pollSink.events.size()); + + ChannelEvent sendChannelEvent = sendSink.events.poll(); + NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.BOUND, RESOLVED_LOCAL_ADDRESS_IPV6); + ChannelEvent pollChannelEvent = pollSink.events.poll(); + NettyTestUtils + .checkIsStateEvent(pollChannelEvent, ChannelState.BOUND, RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT); + + sendChannel.emulateBound(RESOLVED_LOCAL_ADDRESS_IPV6, sendChannelEvent.getFuture()); + assertFalse(bindFuture.isDone()); + pollChannel.emulateBound(RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT, pollChannelEvent.getFuture()); + assertTrue(bindFuture.isDone()); + assertTrue(bindFuture.isSuccess()); + + assertEquals(channel.getLocalAddress(), RESOLVED_LOCAL_ADDRESS_IPV6); + } + + @Test + public void testBind_sendBindFails() + { + ChannelFuture bindFuture = Channels.bind(channel, LOCAL_ADDRESS); + assertFalse(bindFuture.isDone()); + + Exception bindFailureReason = new Exception("could not bind"); + ((ChannelStateEvent) sendSink.events.poll()).getFuture().setFailure(bindFailureReason); + assertTrue(bindFuture.isDone()); + assertFalse(bindFuture.isSuccess()); + assertSame(bindFailureReason, bindFuture.getCause()); + } + + @Test + public void testBind_pollBindFails() + { + ChannelFuture bindFuture = Channels.bind(channel, LOCAL_ADDRESS); + assertFalse(bindFuture.isDone()); + + Exception bindFailureReason = new Exception("could not bind"); + ((ChannelStateEvent) pollSink.events.poll()).getFuture().setFailure(bindFailureReason); + assertTrue(bindFuture.isDone()); + assertFalse(bindFuture.isSuccess()); + assertSame(bindFailureReason, bindFuture.getCause()); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java new file mode 100644 index 00000000000..3db51162cc4 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.DownstreamMessageEvent; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.junit.Before; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class HttpTunnelClientPollHandlerTest +{ + + private static final String TUNNEL_ID = "1"; + + private static final InetSocketAddress SERVER_ADDRESS = createAddress(new byte[] + {10, 0, 0, 3}, 12345); + + private static final InetSocketAddress PROXY_ADDRESS = createAddress(new byte[] + {10, 0, 0, 2}, 8888); + + private static final InetSocketAddress LOCAL_ADDRESS = createAddress(new byte[] + {10, 0, 0, 1}, 54321); + + private FakeSocketChannel channel; + + private FakeChannelSink sink; + + private HttpTunnelClientPollHandler handler; + + private MockChannelStateListener listener; + + private static InetSocketAddress createAddress(byte[] addr, int port) + { + try + { + return new InetSocketAddress(InetAddress.getByAddress(addr), port); + } + catch (UnknownHostException e) + { + throw new RuntimeException("Bad address in test"); + } + } + + @Before + public void setUp() throws Exception + { + sink = new FakeChannelSink(); + + ChannelPipeline pipeline = Channels.pipeline(); + listener = new MockChannelStateListener(); + listener.serverHostName = HttpTunnelMessageUtils.convertToHostString(SERVER_ADDRESS); + handler = new HttpTunnelClientPollHandler(listener); + handler.setTunnelId(TUNNEL_ID); + pipeline.addLast(HttpTunnelClientPollHandler.NAME, handler); + + channel = new FakeSocketChannel(null, null, pipeline, sink); + channel.remoteAddress = PROXY_ADDRESS; + channel.localAddress = LOCAL_ADDRESS; + } + + @Test + public void testSendsRequestOnConnect() + { + Channels.fireChannelConnected(channel, PROXY_ADDRESS); + assertEquals(1, sink.events.size()); + HttpRequest request = checkIsMessageEventContainingHttpRequest(sink.events.poll()); + assertTrue(HttpTunnelMessageUtils.isServerToClientRequest(request)); + assertTrue(HttpTunnelMessageUtils.checkHost(request, SERVER_ADDRESS)); + assertTrue(listener.fullyEstablished); + } + + @Test + public void testSendsReceivedDataSentUpstream() + { + HttpResponse response = HttpTunnelMessageUtils.createRecvDataResponse(NettyTestUtils.createData(1234L)); + Channels.fireMessageReceived(channel, response); + assertEquals(1, listener.messages.size()); + assertEquals(1234L, listener.messages.get(0).readLong()); + } + + @Test + public void testSendsAnotherRequestAfterResponse() + { + HttpResponse response = HttpTunnelMessageUtils.createRecvDataResponse(NettyTestUtils.createData(1234L)); + Channels.fireMessageReceived(channel, response); + assertEquals(1, sink.events.size()); + checkIsMessageEventContainingHttpRequest(sink.events.poll()); + } + + private HttpRequest checkIsMessageEventContainingHttpRequest(ChannelEvent event) + { + assertTrue(event instanceof DownstreamMessageEvent); + DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; + assertTrue(messageEvent.getMessage() instanceof HttpRequest); + return (HttpRequest) messageEvent.getMessage(); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java new file mode 100644 index 00000000000..361d143f88a --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java @@ -0,0 +1,217 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelState; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.DownstreamMessageEvent; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.junit.Before; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class HttpTunnelClientSendHandlerTest +{ + + private static final InetSocketAddress SERVER_ADDRESS = createAddress(new byte[] + {10, 0, 0, 3}, 12345); + + private static final InetSocketAddress PROXY_ADDRESS = createAddress(new byte[] + {10, 0, 0, 2}, 8888); + + private static final InetSocketAddress LOCAL_ADDRESS = createAddress(new byte[] + {10, 0, 0, 1}, 54321); + + private FakeSocketChannel channel; + + private FakeChannelSink sink; + + private HttpTunnelClientSendHandler handler; + + private MockChannelStateListener listener; + + @Before + public void setUp() + { + sink = new FakeChannelSink(); + ChannelPipeline pipeline = Channels.pipeline(); + listener = new MockChannelStateListener(); + listener.serverHostName = HttpTunnelMessageUtils.convertToHostString(SERVER_ADDRESS); + handler = new HttpTunnelClientSendHandler(listener); + pipeline.addLast(HttpTunnelClientSendHandler.NAME, handler); + channel = new FakeSocketChannel(null, null, pipeline, sink); + channel.remoteAddress = PROXY_ADDRESS; + channel.localAddress = LOCAL_ADDRESS; + } + + private static InetSocketAddress createAddress(byte[] addr, int port) + { + try + { + return new InetSocketAddress(InetAddress.getByAddress(addr), port); + } + catch (UnknownHostException e) + { + throw new RuntimeException("Bad address in test"); + } + } + + @Test + public void testSendsTunnelOpen() throws Exception + { + Channels.fireChannelConnected(channel, PROXY_ADDRESS); + assertEquals(1, sink.events.size()); + HttpRequest request = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpRequest.class); + assertTrue(HttpTunnelMessageUtils.isOpenTunnelRequest(request)); + assertTrue(HttpTunnelMessageUtils.checkHost(request, SERVER_ADDRESS)); + } + + @Test + public void testStoresTunnelId() throws Exception + { + emulateConnect(); + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelOpenResponse("newTunnel")); + assertEquals("newTunnel", handler.getTunnelId()); + assertEquals("newTunnel", listener.tunnelId); + } + + @Test + public void testSendData() + { + emulateConnectAndOpen(); + channel.write(NettyTestUtils.createData(1234L)); + assertEquals(1, sink.events.size()); + ChannelEvent sentEvent = sink.events.poll(); + checkIsSendDataRequestWithData(sentEvent, NettyTestUtils.createData(1234L)); + } + + @Test + public void testWillNotSendDataUntilTunnelIdSet() + { + emulateConnect(); + channel.write(NettyTestUtils.createData(1234L)); + + assertEquals(0, sink.events.size()); + + Channels.fireChannelConnected(channel, PROXY_ADDRESS); + assertEquals(1, sink.events.size()); + } + + @Test + public void testOnlyOneRequestAtATime() + { + emulateConnectAndOpen(); + + channel.write(NettyTestUtils.createData(1234L)); + assertEquals(1, sink.events.size()); + checkIsSendDataRequestWithData(sink.events.poll(), NettyTestUtils.createData(1234L)); + + channel.write(NettyTestUtils.createData(5678L)); + assertEquals(0, sink.events.size()); + + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createSendDataResponse()); + assertEquals(1, sink.events.size()); + checkIsSendDataRequestWithData(sink.events.poll(), NettyTestUtils.createData(5678L)); + } + + @Test + public void testDisconnect() + { + emulateConnectAndOpen(); + + channel.write(NettyTestUtils.createData(1234L)); + assertEquals(1, sink.events.size()); + checkIsSendDataRequestWithData(sink.events.poll(), NettyTestUtils.createData(1234L)); + + channel.disconnect(); + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createSendDataResponse()); + assertEquals(1, sink.events.size()); + + HttpRequest request = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpRequest.class); + assertTrue(HttpTunnelMessageUtils.isCloseTunnelRequest(request)); + assertEquals("newTunnel", HttpTunnelMessageUtils.extractTunnelId(request)); + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelCloseResponse()); + assertEquals(1, sink.events.size()); + NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.CONNECTED, null); + } + + @Test + public void testClose() + { + emulateConnectAndOpen(); + + channel.close(); + assertEquals(1, sink.events.size()); + HttpRequest request = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpRequest.class); + assertTrue(HttpTunnelMessageUtils.isCloseTunnelRequest(request)); + assertEquals("newTunnel", HttpTunnelMessageUtils.extractTunnelId(request)); + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelCloseResponse()); + assertEquals(1, sink.events.size()); + NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.OPEN, false); + } + + @Test + public void testWritesAfterCloseAreRejected() + { + emulateConnectAndOpen(); + + channel.close(); + assertFalse(channel.write(NettyTestUtils.createData(1234L)).isSuccess()); + } + + private void checkIsSendDataRequestWithData(ChannelEvent event, ChannelBuffer data) + { + assertTrue(event instanceof DownstreamMessageEvent); + DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; + assertTrue(messageEvent.getMessage() instanceof HttpRequest); + HttpRequest request = (HttpRequest) messageEvent.getMessage(); + assertTrue(HttpTunnelMessageUtils.isSendDataRequest(request)); + assertEquals(data.readableBytes(), HttpHeaders.getContentLength(request)); + + ChannelBuffer content = request.getContent(); + NettyTestUtils.assertEquals(data, content); + } + + private void emulateConnect() + { + channel.emulateConnected(LOCAL_ADDRESS, PROXY_ADDRESS, null); + sink.events.clear(); + } + + private void emulateConnectAndOpen() + { + emulateConnect(); + Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelOpenResponse("newTunnel")); + + sink.events.clear(); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java new file mode 100644 index 00000000000..1483b867256 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import org.jboss.netty.channel.ChannelException; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jboss.netty.channel.socket.ServerSocketChannelFactory; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class HttpTunnelServerChannelFactoryTest +{ + + private final JUnit4Mockery mockContext = new JUnit4Mockery(); + + ServerSocketChannelFactory realChannelFactory; + + private HttpTunnelServerChannelFactory factory; + + ServerSocketChannel realChannel; + + @Before + public void setUp() throws Exception + { + realChannelFactory = mockContext.mock(ServerSocketChannelFactory.class); + factory = new HttpTunnelServerChannelFactory(realChannelFactory); + ChannelPipeline pipeline = Channels.pipeline(); + realChannel = new FakeServerSocketChannel(factory, pipeline, new FakeChannelSink()); + } + + @Test + public void testNewChannel() + { + mockContext.checking(new Expectations() + { + { + one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); + will(returnValue(realChannel)); + } + }); + ChannelPipeline pipeline = Channels.pipeline(); + HttpTunnelServerChannel newChannel = factory.newChannel(pipeline); + assertNotNull(newChannel); + assertSame(pipeline, newChannel.getPipeline()); + } + + @Test + public void testNewChannel_forwardsWrappedFactoryFailure() + { + final ChannelException innerException = new ChannelException(); + mockContext.checking(new Expectations() + { + { + one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); + will(throwException(innerException)); + } + }); + + try + { + factory.newChannel(Channels.pipeline()); + fail("Expected ChannelException"); + } + catch (ChannelException e) + { + assertSame(innerException, e); + } + } + + // @Test + // public void testChannelCreation_withServerBootstrap() { + // mockContext.checking(new Expectations() {{ + // one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); will(returnValue(realChannel)); + // }}); + // + // ServerBootstrap bootstrap = new ServerBootstrap(factory); + // Channel newChannel = bootstrap.bind(new InetSocketAddress(80)); + // assertNotNull(newChannel); + // + // } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java new file mode 100644 index 00000000000..2beee03e9c7 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java @@ -0,0 +1,173 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.socket.ServerSocketChannel; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class HttpTunnelServerChannelSinkTest +{ + + private final JUnit4Mockery mockContext = new JUnit4Mockery(); + + private HttpTunnelServerChannelSink sink; + + private ChannelPipeline pipeline; + + private FakeSocketChannel channel; + + ServerSocketChannel realChannel; + + ChannelFuture realFuture; + + Throwable exceptionInPipeline; + + @Before + public void setUp() throws Exception + { + realChannel = mockContext.mock(ServerSocketChannel.class); + pipeline = Channels.pipeline(); + pipeline.addLast("exceptioncatcher", new ExceptionCatcher()); + sink = new HttpTunnelServerChannelSink(); + sink.setRealChannel(realChannel); + channel = new FakeSocketChannel(null, null, pipeline, sink); + realFuture = Channels.future(realChannel); + } + + @After + public void teardown() throws Exception + { + assertTrue("exception caught in pipeline: " + exceptionInPipeline, exceptionInPipeline == null); + } + + public void testCloseRequest() throws Exception + { + mockContext.checking(new Expectations() + { + { + one(realChannel).close(); + will(returnValue(realFuture)); + } + }); + + ChannelFuture virtualFuture1 = Channels.close(channel); + mockContext.assertIsSatisfied(); + ChannelFuture virtualFuture = virtualFuture1; + realFuture.setSuccess(); + assertTrue(virtualFuture.isSuccess()); + } + + @Test + public void testUnbindRequest_withSuccess() throws Exception + { + ChannelFuture virtualFuture = checkUnbind(); + realFuture.setSuccess(); + assertTrue(virtualFuture.isSuccess()); + } + + @Test + public void testUnbindRequest_withFailure() throws Exception + { + ChannelFuture virtualFuture = checkUnbind(); + realFuture.setFailure(new Exception("Something bad happened")); + assertFalse(virtualFuture.isSuccess()); + } + + private ChannelFuture checkUnbind() + { + mockContext.checking(new Expectations() + { + { + one(realChannel).unbind(); + will(returnValue(realFuture)); + } + }); + + ChannelFuture virtualFuture = Channels.unbind(channel); + mockContext.assertIsSatisfied(); + return virtualFuture; + } + + @Test + public void testBindRequest_withSuccess() + { + ChannelFuture virtualFuture = checkBind(); + realFuture.setSuccess(); + assertTrue(virtualFuture.isSuccess()); + } + + @Test + public void testBindRequest_withFailure() + { + ChannelFuture virtualFuture = checkBind(); + realFuture.setFailure(new Exception("Something bad happened")); + assertFalse(virtualFuture.isSuccess()); + } + + private ChannelFuture checkBind() + { + final SocketAddress toAddress = new InetSocketAddress(80); + mockContext.checking(new Expectations() + { + { + one(realChannel).bind(toAddress); + will(returnValue(realFuture)); + } + }); + + ChannelFuture virtualFuture = Channels.bind(channel, toAddress); + return virtualFuture; + } + + private final class ExceptionCatcher extends SimpleChannelUpstreamHandler + { + + ExceptionCatcher() + { + super(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception + { + exceptionInPipeline = e.getCause(); + } + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java new file mode 100644 index 00000000000..bbe8d63218a --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java @@ -0,0 +1,242 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelState; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.UpstreamChannelStateEvent; +import org.jboss.netty.channel.socket.ServerSocketChannelConfig; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class HttpTunnelServerChannelTest +{ + + JUnit4Mockery mockContext = new JUnit4Mockery(); + + private HttpTunnelServerChannel virtualChannel; + + private UpstreamEventCatcher upstreamEvents; + + private FakeServerSocketChannelFactory realChannelFactory; + + @Before + public void setUp() throws Exception + { + realChannelFactory = new FakeServerSocketChannelFactory(); + realChannelFactory.sink = new FakeChannelSink(); + + HttpTunnelServerChannelFactory factory = new HttpTunnelServerChannelFactory(realChannelFactory); + virtualChannel = factory.newChannel(createVirtualChannelPipeline()); + } + + private ChannelPipeline createVirtualChannelPipeline() + { + ChannelPipeline pipeline = Channels.pipeline(); + upstreamEvents = new UpstreamEventCatcher(); + pipeline.addLast(UpstreamEventCatcher.NAME, upstreamEvents); + return pipeline; + } + + @Test + public void testGetLocalAddress_delegatedToRealChannel() + { + realChannelFactory.createdChannel.localAddress = InetSocketAddress.createUnresolved("mycomputer", 80); + SocketAddress returned = virtualChannel.getLocalAddress(); + assertSame(realChannelFactory.createdChannel.localAddress, returned); + } + + @Test + public void testGetRemoteAddress_returnsNull() + { + assertNull(virtualChannel.getRemoteAddress()); + } + + @Test + public void testIsBound_delegatedToRealChannel() + { + realChannelFactory.createdChannel.bound = true; + assertTrue(virtualChannel.isBound()); + realChannelFactory.createdChannel.bound = false; + assertFalse(virtualChannel.isBound()); + } + + @Test + public void testConstruction_firesOpenEvent() + { + assertTrue(upstreamEvents.events.size() > 0); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.OPEN, Boolean.TRUE); + } + + @Test + public void testChannelBoundEventFromReal_replicatedOnVirtual() + { + upstreamEvents.events.clear(); + InetSocketAddress boundAddr = InetSocketAddress.createUnresolved("mycomputer", 12345); + Channels.fireChannelBound(realChannelFactory.createdChannel, boundAddr); + assertEquals(1, upstreamEvents.events.size()); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.BOUND, boundAddr); + } + + @Test + public void testChannelUnboundEventFromReal_replicatedOnVirtual() + { + upstreamEvents.events.clear(); + Channels.fireChannelUnbound(realChannelFactory.createdChannel); + assertEquals(1, upstreamEvents.events.size()); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.BOUND, null); + } + + @Test + public void testChannelClosedEventFromReal_replicatedOnVirtual() + { + upstreamEvents.events.clear(); + Channels.fireChannelClosed(realChannelFactory.createdChannel); + assertEquals(1, upstreamEvents.events.size()); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.OPEN, Boolean.FALSE); + } + + @Test + public void testHasConfiguration() + { + assertNotNull(virtualChannel.getConfig()); + } + + @Test + public void testChangePipelineFactoryDoesNotAffectRealChannel() + { + ChannelPipelineFactory realPipelineFactory = realChannelFactory.createdChannel.getConfig().getPipelineFactory(); + ChannelPipelineFactory virtualPipelineFactory = mockContext.mock(ChannelPipelineFactory.class); + virtualChannel.getConfig().setPipelineFactory(virtualPipelineFactory); + assertSame(virtualPipelineFactory, virtualChannel.getConfig().getPipelineFactory()); + + // channel pipeline factory is a special case: we do not want it set on the configuration + // of the underlying factory + assertSame(realPipelineFactory, realChannelFactory.createdChannel.getConfig().getPipelineFactory()); + } + + @Test + public void testChangingBacklogAffectsRealChannel() + { + virtualChannel.getConfig().setBacklog(1234); + assertEquals(1234, realChannelFactory.createdChannel.getConfig().getBacklog()); + } + + @Test + public void testChangingConnectTimeoutMillisAffectsRealChannel() + { + virtualChannel.getConfig().setConnectTimeoutMillis(54321); + assertEquals(54321, realChannelFactory.createdChannel.getConfig().getConnectTimeoutMillis()); + } + + @Test + public void testChangingPerformancePreferencesAffectsRealChannel() + { + final ServerSocketChannelConfig mockConfig = mockContext.mock(ServerSocketChannelConfig.class); + realChannelFactory.createdChannel.config = mockConfig; + mockContext.checking(new Expectations() + { + { + one(mockConfig).setPerformancePreferences(100, 200, 300); + } + }); + virtualChannel.getConfig().setPerformancePreferences(100, 200, 300); + mockContext.assertIsSatisfied(); + } + + @Test + public void testChangingReceiveBufferSizeAffectsRealChannel() + { + virtualChannel.getConfig().setReceiveBufferSize(10101); + assertEquals(10101, realChannelFactory.createdChannel.getConfig().getReceiveBufferSize()); + } + + @Test + public void testChangingReuseAddressAffectsRealChannel() + { + virtualChannel.getConfig().setReuseAddress(true); + assertEquals(true, realChannelFactory.createdChannel.getConfig().isReuseAddress()); + } + + @Test + public void testSetChannelPipelineFactoryViaOption() + { + final ServerSocketChannelConfig mockConfig = mockContext.mock(ServerSocketChannelConfig.class); + realChannelFactory.createdChannel.config = mockConfig; + + mockContext.checking(new Expectations() + { + { + never(mockConfig); + } + }); + + ChannelPipelineFactory factory = mockContext.mock(ChannelPipelineFactory.class); + virtualChannel.getConfig().setOption("pipelineFactory", factory); + assertSame(factory, virtualChannel.getConfig().getPipelineFactory()); + } + + @Test + public void testSetOptionAffectsRealChannel() + { + final ServerSocketChannelConfig mockConfig = mockContext.mock(ServerSocketChannelConfig.class); + realChannelFactory.createdChannel.config = mockConfig; + + mockContext.checking(new Expectations() + { + { + one(mockConfig).setOption("testOption", "testValue"); + } + }); + + virtualChannel.getConfig().setOption("testOption", "testValue"); + } + + private void checkIsUpstreamChannelStateEvent(ChannelEvent ev, Channel expectedChannel, ChannelState expectedState, + Object expectedValue) + { + assertTrue(ev instanceof UpstreamChannelStateEvent); + UpstreamChannelStateEvent checkedEv = (UpstreamChannelStateEvent) ev; + assertSame(expectedChannel, checkedEv.getChannel()); + assertEquals(expectedState, checkedEv.getState()); + assertEquals(expectedValue, checkedEv.getValue()); + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java new file mode 100644 index 00000000000..034c6e94881 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java @@ -0,0 +1,487 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +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 org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.DefaultChannelGroup; +import org.jboss.netty.channel.socket.ClientSocketChannelFactory; +import org.jboss.netty.channel.socket.ServerSocketChannelFactory; +import org.jboss.netty.channel.socket.SocketChannel; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class HttpTunnelSoakTester +{ + + private static final int SERVER_PORT = 20100; + + static final Logger LOG = Logger.getLogger(HttpTunnelSoakTester.class.getName()); + + private static final long BYTES_TO_SEND = 1024 * 1024 * 1024; + + private static final int MAX_WRITE_SIZE = 64 * 1024; + + private final ServerBootstrap serverBootstrap; + + private final ClientBootstrap clientBootstrap; + + final ChannelGroup channels; + + private final ExecutorService executor; + + final ScheduledExecutorService scheduledExecutor; + + final DataSender c2sDataSender = new DataSender("C2S"); + final DataSender s2cDataSender = new DataSender("S2C"); + + private DataVerifier c2sVerifier = new DataVerifier("C2S-Verifier"); + private DataVerifier s2cVerifier = new DataVerifier("S2C-Verifier"); + + private static byte[] SEND_STREAM; + + static { + SEND_STREAM = new byte[MAX_WRITE_SIZE + 127]; + for(int i=0; i < SEND_STREAM.length; i++) { + SEND_STREAM[i] = (byte)(i % 127); + } + } + + public HttpTunnelSoakTester() + { + scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); + executor = Executors.newCachedThreadPool(); + ServerSocketChannelFactory serverChannelFactory = new NioServerSocketChannelFactory(executor, executor); + HttpTunnelServerChannelFactory serverTunnelFactory = new HttpTunnelServerChannelFactory(serverChannelFactory); + + serverBootstrap = new ServerBootstrap(serverTunnelFactory); + serverBootstrap.setPipelineFactory(createServerPipelineFactory()); + + ClientSocketChannelFactory clientChannelFactory = new NioClientSocketChannelFactory(executor, executor); + HttpTunnelClientChannelFactory clientTunnelFactory = new HttpTunnelClientChannelFactory(clientChannelFactory); + + clientBootstrap = new ClientBootstrap(clientTunnelFactory); + clientBootstrap.setPipelineFactory(createClientPipelineFactory()); + configureProxy(); + + channels = new DefaultChannelGroup(); + } + + private void configureProxy() + { + String proxyHost = System.getProperty("http.proxyHost"); + if (proxyHost != null && proxyHost.length() != 0) + { + int proxyPort = Integer.getInteger("http.proxyPort", 80); + InetAddress chosenAddress = chooseAddress(proxyHost); + InetSocketAddress proxyAddress = new InetSocketAddress(chosenAddress, proxyPort); + if (!proxyAddress.isUnresolved()) + { + clientBootstrap.setOption(HttpTunnelClientChannelConfig.PROXY_ADDRESS_OPTION, proxyAddress); + System.out.println("Using " + proxyAddress + " as a proxy for this test run"); + } + else + { + System.err.println("Failed to resolve proxy address " + proxyAddress); + } + } + else + { + System.out.println("No proxy specified, will connect to server directly"); + } + } + + private InetAddress chooseAddress(String proxyHost) + { + try + { + InetAddress[] allByName = InetAddress.getAllByName(proxyHost); + for (InetAddress address : allByName) + { + if (address.isAnyLocalAddress() || address.isLinkLocalAddress()) + { + continue; + } + + return address; + } + + return null; + } + catch (UnknownHostException e) + { + return null; + } + } + + protected ChannelPipelineFactory createClientPipelineFactory() + { + return new ChannelPipelineFactory() + { + + public ChannelPipeline getPipeline() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("s2cVerifier", s2cVerifier); + pipeline.addLast("throttleControl", new SendThrottle(c2sDataSender)); + return pipeline; + } + }; + } + + protected ChannelPipelineFactory createServerPipelineFactory() + { + return new ChannelPipelineFactory() + { + + public ChannelPipeline getPipeline() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("c2sVerifier", c2sVerifier); + pipeline.addLast("throttleControl", new SendThrottle(s2cDataSender)); + pipeline.addLast("sendStarter", new SimpleChannelUpstreamHandler() { + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + Channel childChannel = e.getChannel(); + channels.add(childChannel); + s2cDataSender.setChannel(childChannel); + executor.execute(s2cDataSender); + }; + }); + return pipeline; + } + }; + } + + public void run() throws InterruptedException + { + LOG.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()); + + SocketChannel clientChannel = createClientChannel(); + if (clientChannel == null) + { + LOG.severe("no client channel - bailing out"); + return; + } + + channels.add(clientChannel); + c2sDataSender.setChannel(clientChannel); + + executor.execute(c2sDataSender); + + if(!c2sDataSender.waitForFinish(5, TimeUnit.MINUTES)) { + LOG.severe("Data send from client to server failed"); + } + + if(!s2cDataSender.waitForFinish(5, TimeUnit.MINUTES)) { + LOG.severe("Data send from server to client failed"); + } + + + LOG.log(Level.INFO, "Waiting for verification to complete"); + if (!c2sVerifier.waitForCompletion(30L, TimeUnit.SECONDS)) + { + LOG.warning("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"); + } + + LOG.info("closing client channel"); + closeChannel(clientChannel); + LOG.info("server channel status: " + (serverChannel.isOpen() ? "open" : "closed")); + LOG.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"); + } + } + catch (InterruptedException e) + { + LOG.severe("Interrupted while closing connection"); + } + + } + + private SocketChannel createClientChannel() + { + InetSocketAddress serverAddress = new InetSocketAddress("localhost", SERVER_PORT); + ChannelFuture clientChannelFuture = clientBootstrap.connect(serverAddress); + try + { + if (!clientChannelFuture.await(1000, TimeUnit.MILLISECONDS)) + { + LOG.severe("did not connect within acceptable time period"); + return null; + } + } + catch (InterruptedException e) + { + LOG.severe("Interrupted while waiting for client connect to be established"); + return null; + } + + if (!clientChannelFuture.isSuccess()) + { + LOG.log(Level.SEVERE, "did not connect successfully", clientChannelFuture.getCause()); + return null; + } + + HttpTunnelClientChannelConfig config = ((HttpTunnelClientChannelConfig)clientChannelFuture.getChannel().getConfig()); + config.setWriteBufferHighWaterMark(2 * 1024 * 1024); + config.setWriteBufferLowWaterMark(1024 * 1024); + + + return (SocketChannel) clientChannelFuture.getChannel(); + } + + private ChannelBuffer createRandomSizeBuffer(AtomicInteger nextWriteByte) + { + Random random = new Random(); + int arraySize = random.nextInt(MAX_WRITE_SIZE) + 1; + + // cheaply create the buffer by wrapping an appropriately sized section of the pre-built array + ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(SEND_STREAM, nextWriteByte.get(), arraySize); + nextWriteByte.set((nextWriteByte.get() + arraySize) % 127); + + return buffer; + } + + public static void main(String[] args) throws Exception + { + HttpTunnelSoakTester soakTester = new HttpTunnelSoakTester(); + try + { + soakTester.run(); + } + finally + { + soakTester.shutdown(); + } + } + + private void shutdown() + { + serverBootstrap.releaseExternalResources(); + clientBootstrap.releaseExternalResources(); + executor.shutdownNow(); + scheduledExecutor.shutdownNow(); + } + + private class DataVerifier extends SimpleChannelUpstreamHandler + { + private String name; + + private int expectedNext = 0; + + private int verifiedBytes = 0; + + private CountDownLatch completionLatch = new CountDownLatch(1); + + public DataVerifier(String name) + { + this.name = name; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + ChannelBuffer bytesToVerify = (ChannelBuffer)e.getMessage(); + + while (bytesToVerify.readable()) + { + byte readByte = bytesToVerify.readByte(); + if (readByte != expectedNext) + { + LOG.log(Level.SEVERE, "{0}: received a byte out of sequence. Expected {1}, got {2}", new Object[] { name, expectedNext, readByte }); + System.exit(-1); + return; + } + + expectedNext = (expectedNext + 1) % 127; + verifiedBytes++; + } + + if (verifiedBytes >= BYTES_TO_SEND) + { + completionLatch.countDown(); + return; + } + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + channels.add(ctx.getChannel()); + } + + public boolean waitForCompletion(long timeout, TimeUnit timeoutUnit) throws InterruptedException { + return completionLatch.await(timeout, timeoutUnit); + } + } + + private class SendThrottle extends SimpleChannelUpstreamHandler + { + private final DataSender sender; + + public SendThrottle(DataSender sender) + { + this.sender = sender; + } + + @Override + public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + boolean writeEnabled = ctx.getChannel().isWritable(); + sender.setWriteEnabled(writeEnabled); + + } + } + + private class DataSender implements Runnable { + + private AtomicReference channel = new AtomicReference(); + private long totalBytesSent = 0; + private long numWrites = 0; + private long runStartTime = System.currentTimeMillis(); + private boolean firstRun = true; + private AtomicBoolean writeEnabled = new AtomicBoolean(true); + private AtomicBoolean running = new AtomicBoolean(false); + private CountDownLatch finishLatch = new CountDownLatch(1); + private String name; + private AtomicInteger nextWriteByte = new AtomicInteger(0); + + public DataSender(String name) { + this.name = name; + } + + public void setChannel(Channel channel) + { + this.channel.set(channel); + } + + public void setWriteEnabled(boolean enabled) + { + writeEnabled.set(enabled); + if(enabled && !this.isRunning() && finishLatch.getCount() > 0) { + executor.execute(this); + } + } + + @Override + public void run() + { + if(!running.compareAndSet(false, true)) { + LOG.log(Level.WARNING, "{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); + } + + if(firstRun) { + firstRun = false; + runStartTime = System.currentTimeMillis(); + LOG.log(Level.INFO, "{0}: sending data", name); + } + + while (totalBytesSent < BYTES_TO_SEND) + { + if(!writeEnabled.get()) { + running.set(false); + return; + } + + ChannelBuffer randomBytesForSend = createRandomSizeBuffer(nextWriteByte); + totalBytesSent += randomBytesForSend.readableBytes(); + + channel.get().write(ChannelBuffers.wrappedBuffer(randomBytesForSend)); + + numWrites++; + if (numWrites % 100 == 0) + { + LOG.log(Level.INFO, "{0}: {1} writes dispatched, totalling {2} bytes", new Object[] + {name, numWrites, totalBytesSent}); + } + } + + LOG.log(Level.INFO, "{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 } ); + + finishLatch.countDown(); + running.set(false); + } + + public boolean isRunning() { + return running.get(); + } + + public boolean waitForFinish(long timeout, TimeUnit timeoutUnit) throws InterruptedException { + return finishLatch.await(timeout, timeoutUnit); + } + + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java new file mode 100644 index 00000000000..6209b1eb9b9 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java @@ -0,0 +1,228 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.DefaultChannelGroup; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class HttpTunnelTest +{ + + private HttpTunnelClientChannelFactory clientFactory; + + private HttpTunnelServerChannelFactory serverFactory; + + private ClientBootstrap clientBootstrap; + + private ServerBootstrap serverBootstrap; + + ChannelGroup activeConnections; + + ChannelHandler clientCaptureHandler; + + ServerEndHandler connectionCaptureHandler; + + Channel serverEnd; + + CountDownLatch serverEndLatch; + + ChannelBuffer receivedBytes; + + CountDownLatch messageReceivedLatch; + + ChannelBuffer clientReceivedBytes; + + CountDownLatch clientMessageReceivedLatch; + + private Channel serverChannel; + + @Before + public void setUp() throws UnknownHostException + { + activeConnections = new DefaultChannelGroup(); + clientFactory = new HttpTunnelClientChannelFactory(new NioClientSocketChannelFactory( + Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); + serverFactory = new HttpTunnelServerChannelFactory(new NioServerSocketChannelFactory( + Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); + + clientBootstrap = new ClientBootstrap(clientFactory); + + clientCaptureHandler = new ClientEndHandler(); + clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() + { + + public ChannelPipeline getPipeline() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("clientCapture", clientCaptureHandler); + return pipeline; + } + }); + + clientReceivedBytes = ChannelBuffers.dynamicBuffer(); + clientMessageReceivedLatch = new CountDownLatch(1); + + serverBootstrap = new ServerBootstrap(serverFactory); + + connectionCaptureHandler = new ServerEndHandler(); + serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() + { + + public ChannelPipeline getPipeline() throws Exception + { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("capture", connectionCaptureHandler); + return pipeline; + } + }); + + serverEndLatch = new CountDownLatch(1); + receivedBytes = ChannelBuffers.dynamicBuffer(); + messageReceivedLatch = new CountDownLatch(1); + + serverChannel = serverBootstrap.bind(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); + activeConnections.add(serverChannel); + } + + @After + public void tearDown() throws Exception + { + activeConnections.disconnect().await(1000L); + clientBootstrap.releaseExternalResources(); + serverBootstrap.releaseExternalResources(); + } + + @Test(timeout = 2000) + public void testConnectClientToServer() throws Exception + { + ChannelFuture connectFuture = clientBootstrap.connect(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); + assertTrue(connectFuture.await(1000L)); + assertTrue(connectFuture.isSuccess()); + assertNotNull(connectFuture.getChannel()); + + Channel clientChannel = connectFuture.getChannel(); + activeConnections.add(clientChannel); + assertEquals(serverChannel.getLocalAddress(), clientChannel.getRemoteAddress()); + + assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); + assertNotNull(serverEnd); + assertEquals(clientChannel.getLocalAddress(), serverEnd.getRemoteAddress()); + } + + @Test + public void testSendDataFromClientToServer() throws Exception + { + ChannelFuture connectFuture = clientBootstrap.connect(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); + assertTrue(connectFuture.await(1000L)); + + Channel clientEnd = connectFuture.getChannel(); + activeConnections.add(clientEnd); + + assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); + + ChannelFuture writeFuture = Channels.write(clientEnd, NettyTestUtils.createData(100L)); + assertTrue(writeFuture.await(1000L)); + assertTrue(writeFuture.isSuccess()); + + assertTrue(messageReceivedLatch.await(1000L, TimeUnit.MILLISECONDS)); + assertEquals(100L, receivedBytes.readLong()); + } + + @Test + public void testSendDataFromServerToClient() throws Exception + { + ChannelFuture connectFuture = clientBootstrap.connect(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); + assertTrue(connectFuture.await(1000L)); + + Channel clientEnd = connectFuture.getChannel(); + activeConnections.add(clientEnd); + + assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); + + ChannelFuture writeFuture = Channels.write(serverEnd, NettyTestUtils.createData(4321L)); + assertTrue(writeFuture.await(1000L)); + assertTrue(writeFuture.isSuccess()); + + assertTrue(clientMessageReceivedLatch.await(1000, TimeUnit.MILLISECONDS)); + assertEquals(4321L, clientReceivedBytes.readLong()); + } + + class ServerEndHandler extends SimpleChannelUpstreamHandler + { + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception + { + serverEnd = e.getChannel(); + activeConnections.add(serverEnd); + serverEndLatch.countDown(); + super.channelConnected(ctx, e); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + receivedBytes.writeBytes((ChannelBuffer) e.getMessage()); + messageReceivedLatch.countDown(); + } + } + + class ClientEndHandler extends SimpleChannelUpstreamHandler + { + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception + { + clientReceivedBytes.writeBytes((ChannelBuffer) e.getMessage()); + clientMessageReceivedLatch.countDown(); + } + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java new file mode 100644 index 00000000000..fd5cb10ec32 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java @@ -0,0 +1,66 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelFuture; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class MockChannelStateListener implements HttpTunnelClientWorkerOwner +{ + + public boolean fullyEstablished = false; + + public List messages = new ArrayList(); + + public String tunnelId = null; + + public String serverHostName = null; + + public void fullyEstablished() + { + fullyEstablished = true; + } + + public void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress) + { + // not relevant for test + } + + public void onMessageReceived(ChannelBuffer content) + { + messages.add(content); + } + + public void onTunnelOpened(String tunnelId) + { + this.tunnelId = tunnelId; + } + + public String getServerHostName() + { + return serverHostName; + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java new file mode 100644 index 00000000000..1358aa9bdb0 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java @@ -0,0 +1,200 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import junit.framework.Assert; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelState; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.DownstreamMessageEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.UpstreamMessageEvent; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class NettyTestUtils +{ + + public static ByteBuffer convertReadable(ChannelBuffer b) + { + int startIndex = b.readerIndex(); + ByteBuffer converted = ByteBuffer.allocate(b.readableBytes()); + b.readBytes(converted); + b.readerIndex(startIndex); + converted.flip(); + return converted; + } + + public static void assertEquals(ChannelBuffer expected, ChannelBuffer actual) + { + if (expected.readableBytes() != actual.readableBytes()) + { + Assert.failNotEquals("channel buffers have differing readable sizes", expected.readableBytes(), + actual.readableBytes()); + } + + int startPositionExpected = expected.readerIndex(); + int startPositionActual = actual.readerIndex(); + int position = 0; + while (expected.readable()) + { + byte expectedByte = expected.readByte(); + byte actualByte = actual.readByte(); + if (expectedByte != actualByte) + { + Assert.failNotEquals("channel buffers differ at position " + position, expectedByte, actualByte); + } + + position++; + } + + expected.readerIndex(startPositionExpected); + actual.readerIndex(startPositionActual); + } + + public static boolean checkEquals(ChannelBuffer expected, ChannelBuffer actual) + { + if (expected.readableBytes() != actual.readableBytes()) + { + return false; + } + + int position = 0; + while (expected.readable()) + { + byte expectedByte = expected.readByte(); + byte actualByte = actual.readByte(); + if (expectedByte != actualByte) + { + return false; + } + + position++; + } + + return true; + } + + public static List splitIntoChunks(int chunkSize, ChannelBuffer... buffers) + { + LinkedList chunks = new LinkedList(); + + ArrayList sourceBuffers = new ArrayList(); + Collections.addAll(sourceBuffers, buffers); + Iterator sourceIter = sourceBuffers.iterator(); + ChannelBuffer chunk = ChannelBuffers.buffer(chunkSize); + while (sourceIter.hasNext()) + { + ChannelBuffer source = sourceIter.next(); + + int index = source.readerIndex(); + while (source.writerIndex() > index) + { + int fragmentSize = Math.min(source.writerIndex() - index, chunk.writableBytes()); + chunk.writeBytes(source, index, fragmentSize); + if (!chunk.writable()) + { + chunks.add(chunk); + chunk = ChannelBuffers.buffer(chunkSize); + } + index += fragmentSize; + } + } + + if (chunk.readable()) + { + chunks.add(chunk); + } + + return chunks; + } + + public static ChannelBuffer createData(long containedNumber) + { + ChannelBuffer data = ChannelBuffers.dynamicBuffer(); + data.writeLong(containedNumber); + return data; + } + + public static void checkIsUpstreamMessageEventContainingData(ChannelEvent event, ChannelBuffer expectedData) + { + ChannelBuffer data = checkIsUpstreamMessageEvent(event, ChannelBuffer.class); + assertEquals(expectedData, data); + } + + public static T checkIsUpstreamMessageEvent(ChannelEvent event, Class expectedMessageType) + { + assertTrue(event instanceof UpstreamMessageEvent); + UpstreamMessageEvent messageEvent = (UpstreamMessageEvent) event; + assertTrue(expectedMessageType.isInstance(messageEvent.getMessage())); + return expectedMessageType.cast(messageEvent.getMessage()); + } + + public static T checkIsDownstreamMessageEvent(ChannelEvent event, Class expectedMessageType) + { + assertTrue(event instanceof DownstreamMessageEvent); + DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; + assertTrue(expectedMessageType.isInstance(messageEvent.getMessage())); + return expectedMessageType.cast(messageEvent.getMessage()); + } + + public static InetSocketAddress createAddress(byte[] addr, int port) + { + try + { + return new InetSocketAddress(InetAddress.getByAddress(addr), port); + } + catch (UnknownHostException e) + { + throw new RuntimeException("Bad address in test"); + } + } + + public static Throwable checkIsExceptionEvent(ChannelEvent ev) + { + assertTrue(ev instanceof ExceptionEvent); + ExceptionEvent exceptionEv = (ExceptionEvent) ev; + return exceptionEv.getCause(); + } + + public static ChannelStateEvent checkIsStateEvent(ChannelEvent event, ChannelState expectedState, + Object expectedValue) + { + assertTrue(event instanceof ChannelStateEvent); + ChannelStateEvent stateEvent = (ChannelStateEvent) event; + Assert.assertEquals(expectedState, stateEvent.getState()); + Assert.assertEquals(expectedValue, stateEvent.getValue()); + return stateEvent; + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java new file mode 100644 index 00000000000..2b9987993d2 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class NettyTestUtilsTest +{ + + @Test + public void testSplitIntoChunks() + { + ChannelBuffer a = createFullBuffer(20, (byte) 0); + ChannelBuffer b = createFullBuffer(20, (byte) 1); + ChannelBuffer c = createFullBuffer(20, (byte) 2); + + List chunks = NettyTestUtils.splitIntoChunks(10, a, b, c); + assertEquals(6, chunks.size()); + for (ChannelBuffer chunk : chunks) + { + assertEquals(10, chunk.readableBytes()); + } + + // reader index should not be modified by splitIntoChunks() + assertEquals(0, a.readerIndex()); + assertEquals(0, b.readerIndex()); + assertEquals(0, c.readerIndex()); + } + + @Test + public void testSplitIntoChunks_chunksCrossBoundaries() + { + ChannelBuffer a = createFullBuffer(5, (byte) 0); + ChannelBuffer b = createFullBuffer(5, (byte) 1); + ChannelBuffer c = createFullBuffer(5, (byte) 2); + + List chunks = NettyTestUtils.splitIntoChunks(4, a, b, c); + assertEquals(4, chunks.size()); + checkBufferContains(chunks.get(0), new byte[] + {0, 0, 0, 0}); + checkBufferContains(chunks.get(1), new byte[] + {0, 1, 1, 1}); + checkBufferContains(chunks.get(2), new byte[] + {1, 1, 2, 2}); + checkBufferContains(chunks.get(3), new byte[] + {2, 2, 2}); + } + + @Test + public void testSplitIntoChunks_smallestChunksPossible() + { + ChannelBuffer a = createFullBuffer(5, (byte) 0); + ChannelBuffer b = createFullBuffer(5, (byte) 1); + ChannelBuffer c = createFullBuffer(5, (byte) 2); + + List chunks = NettyTestUtils.splitIntoChunks(1, a, b, c); + assertEquals(15, chunks.size()); + checkBufferContains(chunks.get(0), new byte[] + {0}); + checkBufferContains(chunks.get(5), new byte[] + {1}); + checkBufferContains(chunks.get(10), new byte[] + {2}); + } + + @Test + public void testSplitIntoChunks_sourceBuffersArePartiallyRead() + { + ChannelBuffer a = createFullBuffer(5, (byte) 0); + a.readerIndex(1); + ChannelBuffer b = createFullBuffer(5, (byte) 1); + b.readerIndex(2); + ChannelBuffer c = createFullBuffer(5, (byte) 2); + + // will be ignored, as fully read + ChannelBuffer d = createFullBuffer(5, (byte) 3); + d.readerIndex(5); + ChannelBuffer e = createFullBuffer(5, (byte) 4); + e.readerIndex(4); + + List chunks = NettyTestUtils.splitIntoChunks(3, a, b, c, d, e); + checkBufferContains(chunks.get(0), new byte[] + {0, 0, 0}); + checkBufferContains(chunks.get(1), new byte[] + {0, 1, 1}); + checkBufferContains(chunks.get(2), new byte[] + {1, 2, 2}); + checkBufferContains(chunks.get(3), new byte[] + {2, 2, 2}); + checkBufferContains(chunks.get(4), new byte[] + {4}); + } + + private void checkBufferContains(ChannelBuffer channelBuffer, byte[] bs) + { + if (channelBuffer.readableBytes() != bs.length) + { + fail("buffer does not have enough bytes"); + } + + for (int i = 0; i < bs.length; i++) + { + assertEquals("byte at position " + i + " does not match", bs[i], channelBuffer.getByte(i)); + } + } + + private ChannelBuffer createFullBuffer(int size, byte value) + { + ChannelBuffer buffer = ChannelBuffers.buffer(size); + byte[] contents = new byte[size]; + for (int i = 0; i < contents.length; i++) + { + contents[i] = value; + } + buffer.writeBytes(contents); + return buffer; + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java new file mode 100644 index 00000000000..1ece636a9fd --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import org.jboss.netty.channel.ChannelDownstreamHandler; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelUpstreamHandler; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class NullChannelHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler +{ + + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception + { + ctx.sendUpstream(e); + } + + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception + { + ctx.sendDownstream(e); + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java new file mode 100644 index 00000000000..d60340d92fe --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java @@ -0,0 +1,34 @@ +package org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.*; +import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.*; + +import org.junit.Before; +import org.junit.Test; + +public class SaturationManagerTest +{ + + private SaturationManager manager; + + @Before + public void setUp() { + manager = new SaturationManager(100L, 200L); + } + + @Test + public void testQueueSizeChanged() + { + assertEquals(NO_CHANGE, manager.queueSizeChanged(100L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(99L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(1L)); + assertEquals(SATURATED, manager.queueSizeChanged(1L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(10L)); + + assertEquals(NO_CHANGE, manager.queueSizeChanged(-10L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(-1L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(-1L)); + assertEquals(DESATURATED, manager.queueSizeChanged(-99L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(-100L)); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java new file mode 100644 index 00000000000..8851804ffc2 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; + +import java.net.InetSocketAddress; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.httptunnel.ServerMessageSwitchUpstreamInterface.TunnelStatus; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jmock.Expectations; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +@RunWith(JMock.class) +public class ServerMessageSwitchTest +{ + + public static final InetSocketAddress REMOTE_ADDRESS = InetSocketAddress.createUnresolved("test.client.com", 52354); + + private final JUnit4Mockery mockContext = new JUnit4Mockery(); + + private ServerMessageSwitch messageSwitch; + + HttpTunnelAcceptedChannelFactory newChannelFactory; + + private FakeChannelSink responseCatcher; + + private FakeSocketChannel htunChannel; + + private FakeSocketChannel requesterChannel; + + private HttpTunnelAcceptedChannelReceiver htunAcceptedChannel; + + @Before + public void setUp() throws Exception + { + newChannelFactory = mockContext.mock(HttpTunnelAcceptedChannelFactory.class); + messageSwitch = new ServerMessageSwitch(newChannelFactory); + + htunAcceptedChannel = mockContext.mock(HttpTunnelAcceptedChannelReceiver.class); + createRequesterChannel(); + + mockContext.checking(new Expectations() + { + { + one(newChannelFactory).newChannel(with(any(String.class)), with(equal(REMOTE_ADDRESS))); + will(returnValue(htunAcceptedChannel)); + ignoring(newChannelFactory).generateTunnelId(); + will(returnValue("TEST_TUNNEL")); + } + }); + } + + private FakeSocketChannel createRequesterChannel() + { + ChannelPipeline requesterChannelPipeline = Channels.pipeline(); + responseCatcher = new FakeChannelSink(); + requesterChannel = new FakeSocketChannel(null, null, requesterChannelPipeline, responseCatcher); + responseCatcher.events.clear(); + + return requesterChannel; + } + + @Test + public void testRouteInboundData() + { + final ChannelBuffer inboundData = ChannelBuffers.dynamicBuffer(); + inboundData.writeLong(1234L); + + mockContext.checking(new Expectations() + { + { + one(htunAcceptedChannel).dataReceived(with(same(inboundData))); + } + }); + + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.routeInboundData(tunnelId, inboundData); + mockContext.assertIsSatisfied(); + } + + @Test + public void testRouteOutboundData_onPoll() + { + ChannelBuffer outboundData = ChannelBuffers.dynamicBuffer(); + outboundData.writeLong(1234L); + + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.routeOutboundData(tunnelId, outboundData, Channels.future(htunChannel)); + messageSwitch.pollOutboundData(tunnelId, requesterChannel); + + assertEquals(1, responseCatcher.events.size()); + HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseCatcher.events.poll(), + HttpResponse.class); + NettyTestUtils.assertEquals(outboundData, response.getContent()); + } + + @Test + public void testRouteOutboundData_withDanglingRequest() + { + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.pollOutboundData(tunnelId, requesterChannel); + assertEquals(0, responseCatcher.events.size()); + + ChannelBuffer outboundData = ChannelBuffers.dynamicBuffer(); + outboundData.writeLong(1234L); + + messageSwitch.routeOutboundData(tunnelId, outboundData, Channels.future(htunChannel)); + assertEquals(1, responseCatcher.events.size()); + HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseCatcher.events.poll(), + HttpResponse.class); + NettyTestUtils.assertEquals(outboundData, response.getContent()); + } + + @Test + public void testCloseTunnel() + { + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.serverCloseTunnel(tunnelId); + assertEquals(TunnelStatus.CLOSED, messageSwitch.routeInboundData(tunnelId, ChannelBuffers.dynamicBuffer())); + } + + /* TODO: require tests that check the various permutations of a client sending or polling + data after the server has closed the connection */ + + /* TODO: require tests that check what happens when a client closes a connection */ + + @Test + public void testRouteInboundDataIgnoredAfterClose() + { + ChannelBuffer data = NettyTestUtils.createData(1234L); + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.serverCloseTunnel(tunnelId); + + mockContext.checking(new Expectations() + { + { + never(htunAcceptedChannel).dataReceived(with(any(ChannelBuffer.class))); + } + }); + + messageSwitch.routeInboundData(tunnelId, data); + mockContext.assertIsSatisfied(); + } + + @Test + public void testRouteOutboundDataIgnoredAfterClose() + { + ChannelBuffer data = NettyTestUtils.createData(1234L); + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.serverCloseTunnel(tunnelId); + messageSwitch.routeOutboundData(tunnelId, data, Channels.future(htunChannel)); + assertEquals(0, responseCatcher.events.size()); + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java new file mode 100644 index 00000000000..91fa22b833c --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java @@ -0,0 +1,42 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import java.util.LinkedList; +import java.util.Queue; + +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelUpstreamHandler; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class UpstreamEventCatcher implements ChannelUpstreamHandler +{ + + public static final String NAME = "upstreamCatcher"; + + public Queue events = new LinkedList(); + + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception + { + events.add(e); + } + +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java new file mode 100644 index 00000000000..3aa294f4459 --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.junit.Before; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class WriteFragmenterTest +{ + + private FakeSocketChannel channel; + + private WriteFragmenter fragmenter; + + private FakeChannelSink downstreamCatcher; + + @Before + public void setUp() throws Exception + { + fragmenter = new WriteFragmenter(100); + + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast(WriteFragmenter.NAME, fragmenter); + downstreamCatcher = new FakeChannelSink(); + channel = new FakeSocketChannel(null, null, pipeline, downstreamCatcher); + } + + @Test + public void testLeavesWritesBeneathThresholdUntouched() + { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[99]); + Channels.write(channel, data); + + assertEquals(1, downstreamCatcher.events.size()); + ChannelBuffer sentData = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), + ChannelBuffer.class); + assertSame(data, sentData); + } + + @Test + public void testLeavesMessagesOnThresholdUntouched() + { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[100]); + Channels.write(channel, data); + + assertEquals(1, downstreamCatcher.events.size()); + ChannelBuffer sentData = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), + ChannelBuffer.class); + assertSame(data, sentData); + } + + @Test + public void testSplitsMessagesAboveThreshold_twoChunks() + { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[101]); + Channels.write(channel, data); + + assertEquals(2, downstreamCatcher.events.size()); + ChannelBuffer chunk1 = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), + ChannelBuffer.class); + ChannelBuffer chunk2 = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), + ChannelBuffer.class); + assertEquals(100, chunk1.readableBytes()); + assertEquals(1, chunk2.readableBytes()); + } + + @Test + public void testSplitsMessagesAboveThreshold_multipleChunks() + { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); + Channels.write(channel, data); + + assertEquals(26, downstreamCatcher.events.size()); + for (int i = 0; i < 25; i++) + { + ChannelBuffer chunk = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), + ChannelBuffer.class); + assertEquals(100, chunk.readableBytes()); + } + + ChannelBuffer endChunk = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), + ChannelBuffer.class); + assertEquals(40, endChunk.readableBytes()); + } + + @Test + public void testChannelFutureTriggeredOnlyWhenAllChunksWritten() + { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); + ChannelFuture mainWriteFuture = Channels.write(channel, data); + + assertEquals(26, downstreamCatcher.events.size()); + for (int i = 0; i < 25; i++) + { + ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setSuccess(); + assertFalse(mainWriteFuture.isDone()); + } + + ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setSuccess(); + assertTrue(mainWriteFuture.isDone()); + assertTrue(mainWriteFuture.isSuccess()); + } + + @Test + public void testChannelFutureFailsOnFirstWriteFailure() + { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); + ChannelFuture mainWriteFuture = Channels.write(channel, data); + + assertEquals(26, downstreamCatcher.events.size()); + for (int i = 0; i < 10; i++) + { + ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setSuccess(); + assertFalse(mainWriteFuture.isDone()); + } + + ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setFailure(new Exception("Something bad happened")); + assertTrue(mainWriteFuture.isDone()); + assertFalse(mainWriteFuture.isSuccess()); + + // check all the subsequent writes got cancelled + for (int i = 0; i < 15; i++) + { + assertTrue(((MessageEvent) downstreamCatcher.events.poll()).getFuture().isCancelled()); + } + } +} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java b/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java new file mode 100644 index 00000000000..c4453b14e0d --- /dev/null +++ b/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.channel.socket.httptunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.junit.Test; + +/** + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Iain McGinniss (iain.mcginniss@onedrum.com) + */ +public class WriteSplitterTest +{ + + private static final int SPLIT_THRESHOLD = 1024; + + @Test + public void testSplit_bufferUnderThreshold() + { + ChannelBuffer buffer = createBufferWithContents(800); + List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(1, fragments.size()); + } + + @Test + public void testSplit_bufferMatchesThreshold() + { + ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD); + List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(1, fragments.size()); + } + + @Test + public void testSplit_bufferOverThreshold() + { + ChannelBuffer buffer = createBufferWithContents((int) (SPLIT_THRESHOLD * 1.5)); + List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(2, fragments.size()); + + ChannelBuffer fragment1 = fragments.get(0); + checkMatches(buffer, fragment1); + ChannelBuffer fragment2 = fragments.get(1); + checkMatches(buffer, fragment2); + } + + @Test + public void testSplit_largeNumberOfFragments() + { + ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD * 250); + List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(250, fragments.size()); + + for (ChannelBuffer fragment : fragments) + { + checkMatches(buffer, fragment); + } + } + + private void checkMatches(ChannelBuffer mainBuffer, ChannelBuffer fragment) + { + assertTrue(mainBuffer.readableBytes() >= fragment.readableBytes()); + while (fragment.readable()) + { + assertEquals(mainBuffer.readByte(), fragment.readByte()); + } + } + + private ChannelBuffer createBufferWithContents(int size) + { + byte[] contents = new byte[size]; + for (int i = 0; i < contents.length; i++) + { + contents[i] = (byte) (i % 10); + } + + return ChannelBuffers.copiedBuffer(contents); + } + +} From 5f5c87b4204d90f975533d50175ac83c1b22df4a Mon Sep 17 00:00:00 2001 From: iainmcgin Date: Fri, 10 Dec 2010 17:47:47 +0000 Subject: [PATCH 3/6] Changing surefire version back to 2.5 (was changed to 2.6 while I was attempting to get jmock tests to pass, forgot to change back) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a54a46d1c33..4767764bc44 100644 --- a/pom.xml +++ b/pom.xml @@ -259,7 +259,7 @@ maven-surefire-plugin - 2.6 + 2.5 once From 6335119aaae42cecabb93bfa34138e7ce2156588 Mon Sep 17 00:00:00 2001 From: iainmcgin Date: Fri, 8 Apr 2011 12:37:39 +0100 Subject: [PATCH 4/6] Moved http tunnel implementation into org.jboss.netty.channel.socket.http package, replacing the original implementation. Existing usage example has been deleted and should be replaced by an updated example using the new API. --- .../AcceptedServerChannelPipelineFactory.java | 2 +- .../AcceptedServerChannelRequestDispatch.java | 2 +- .../ChannelFutureAggregator.java | 2 +- .../DefaultTunnelIdGenerator.java | 2 +- .../HttpTunnelAcceptedChannel.java | 2 +- .../HttpTunnelAcceptedChannelConfig.java | 2 +- .../HttpTunnelAcceptedChannelFactory.java | 2 +- .../HttpTunnelAcceptedChannelReceiver.java | 2 +- .../HttpTunnelAcceptedChannelSink.java | 2 +- .../HttpTunnelChannelConfig.java | 2 +- .../HttpTunnelClientChannel.java | 2 +- .../HttpTunnelClientChannelConfig.java | 2 +- .../HttpTunnelClientChannelFactory.java | 2 +- .../HttpTunnelClientChannelSink.java | 2 +- .../HttpTunnelClientPollHandler.java | 2 +- .../HttpTunnelClientSendHandler.java | 2 +- .../HttpTunnelClientWorkerOwner.java | 2 +- .../HttpTunnelMessageUtils.java | 2 +- .../HttpTunnelServerChannel.java | 2 +- .../HttpTunnelServerChannelConfig.java | 2 +- .../HttpTunnelServerChannelFactory.java | 2 +- .../HttpTunnelServerChannelSink.java | 2 +- .../HttpTunnelingClientSocketChannel.java | 418 ------------------ ...tpTunnelingClientSocketChannelFactory.java | 58 --- ...HttpTunnelingClientSocketPipelineSink.java | 78 ---- .../socket/http/HttpTunnelingServlet.java | 246 ----------- .../HttpTunnelingSocketChannelConfig.java | 347 --------------- .../SaturationManager.java | 8 +- .../SaturationStateChange.java | 2 +- .../ServerMessageSwitch.java | 2 +- ...erverMessageSwitchDownstreamInterface.java | 2 +- .../ServerMessageSwitchUpstreamInterface.java | 2 +- .../TunnelIdGenerator.java | 2 +- .../TunnelWrappedServerChannelHandler.java | 2 +- .../{httptunnel => http}/WriteFragmenter.java | 2 +- .../{httptunnel => http}/WriteSplitter.java | 2 +- .../tunnel/HttpTunnelingClientExample.java | 122 ----- .../tunnel/LocalEchoServerRegistration.java | 54 --- ...eptedServerChannelRequestDispatchTest.java | 2 +- .../FakeChannelConfig.java | 2 +- .../{httptunnel => http}/FakeChannelSink.java | 2 +- .../FakeClientSocketChannelFactory.java | 2 +- .../FakeServerSocketChannel.java | 2 +- .../FakeServerSocketChannelConfig.java | 2 +- .../FakeServerSocketChannelFactory.java | 2 +- .../FakeSocketChannel.java | 2 +- .../HttpTunnelAcceptedChannelSinkTest.java | 2 +- .../HttpTunnelClientChannelConfigTest.java | 2 +- .../HttpTunnelClientChannelTest.java | 2 +- .../HttpTunnelClientPollHandlerTest.java | 2 +- .../HttpTunnelClientSendHandlerTest.java | 2 +- .../HttpTunnelServerChannelFactoryTest.java | 2 +- .../HttpTunnelServerChannelSinkTest.java | 2 +- .../HttpTunnelServerChannelTest.java | 2 +- .../HttpTunnelSoakTester.java | 2 +- .../{httptunnel => http}/HttpTunnelTest.java | 2 +- .../MockChannelStateListener.java | 2 +- .../{httptunnel => http}/NettyTestUtils.java | 2 +- .../NettyTestUtilsTest.java | 2 +- .../NullChannelHandler.java | 2 +- .../SaturationManagerTest.java | 4 +- .../ServerMessageSwitchTest.java | 4 +- .../UpstreamEventCatcher.java | 2 +- .../WriteFragmenterTest.java | 2 +- .../WriteSplitterTest.java | 2 +- 65 files changed, 63 insertions(+), 1386 deletions(-) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/AcceptedServerChannelPipelineFactory.java (97%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/AcceptedServerChannelRequestDispatch.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/ChannelFutureAggregator.java (97%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/DefaultTunnelIdGenerator.java (96%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelAcceptedChannel.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelAcceptedChannelConfig.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelAcceptedChannelFactory.java (95%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelAcceptedChannelReceiver.java (92%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelAcceptedChannelSink.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelChannelConfig.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientChannel.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientChannelConfig.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientChannelFactory.java (97%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientChannelSink.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientPollHandler.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientSendHandler.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientWorkerOwner.java (97%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelMessageUtils.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannel.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannelConfig.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannelFactory.java (97%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannelSink.java (98%) delete mode 100644 src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannel.java delete mode 100644 src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannelFactory.java delete mode 100644 src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketPipelineSink.java delete mode 100644 src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingServlet.java delete mode 100644 src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingSocketChannelConfig.java rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/SaturationManager.java (85%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/SaturationStateChange.java (93%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/ServerMessageSwitch.java (99%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/ServerMessageSwitchDownstreamInterface.java (95%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/ServerMessageSwitchUpstreamInterface.java (97%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/TunnelIdGenerator.java (95%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/TunnelWrappedServerChannelHandler.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/WriteFragmenter.java (98%) rename src/main/java/org/jboss/netty/channel/socket/{httptunnel => http}/WriteSplitter.java (97%) delete mode 100644 src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java delete mode 100644 src/main/java/org/jboss/netty/example/http/tunnel/LocalEchoServerRegistration.java rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/AcceptedServerChannelRequestDispatchTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeChannelConfig.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeChannelSink.java (95%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeClientSocketChannelFactory.java (96%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeServerSocketChannel.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeServerSocketChannelConfig.java (97%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeServerSocketChannelFactory.java (96%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/FakeSocketChannel.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelAcceptedChannelSinkTest.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientChannelConfigTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientChannelTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientPollHandlerTest.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelClientSendHandlerTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannelFactoryTest.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannelSinkTest.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelServerChannelTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelSoakTester.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/HttpTunnelTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/MockChannelStateListener.java (97%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/NettyTestUtils.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/NettyTestUtilsTest.java (98%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/NullChannelHandler.java (96%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/SaturationManagerTest.java (85%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/ServerMessageSwitchTest.java (97%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/UpstreamEventCatcher.java (96%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/WriteFragmenterTest.java (99%) rename src/test/java/org/jboss/netty/channel/socket/{httptunnel => http}/WriteSplitterTest.java (98%) diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java rename to src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java index e5155eea0bd..668361d921a 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelPipelineFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java rename to src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java index 3dd5db36036..1fbbcba0d11 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatch.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; import java.net.SocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java b/src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java rename to src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java index bada6091cd2..92b56c0e130 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ChannelFutureAggregator.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java b/src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java similarity index 96% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java rename to src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java index 041aaac744a..2e20f381bcf 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/DefaultTunnelIdGenerator.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.security.SecureRandom; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java index 1d10936f5eb..07a61de8da6 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannel.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.jboss.netty.channel.Channels.fireChannelBound; import static org.jboss.netty.channel.Channels.fireChannelConnected; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java index bdfa60109ec..9f480e4dc28 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; /** * Configuration the server end of an http tunnel. diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java similarity index 95% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java index 600ac398511..5b5014f15a2 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java similarity index 92% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java index ab9852273a2..6ff8592bafd 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelReceiver.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.buffer.ChannelBuffer; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java index 29e631576fa..1f947d06630 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSink.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java index d8821b37938..a9867ce5d0a 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.DefaultChannelConfig; import org.jboss.netty.channel.socket.SocketChannelConfig; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java index 70bb38d4f54..aa6b9f2609b 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannel.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; import java.net.SocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java index 12667632ed4..0088c140d78 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.SocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java index 5842ec03125..27b75834bb4 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.group.ChannelGroup; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java index 6c5fa3121aa..cf3a6c13614 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelSink.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java index ce8cfa9bad2..fd35dc8fd3a 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandler.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java index 4ef997fd54c..3347616b758 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandler.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java index c34ee3e2ee9..830ab074a38 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientWorkerOwner.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java index e5e28a96302..a92b804d166 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelMessageUtils.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.io.StringWriter; import java.io.UnsupportedEncodingException; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java index e0efaa8422e..8eae5adc970 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannel.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java index b41bd5a8451..f77362dc1ef 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.Map; import java.util.Map.Entry; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java index 322fe7d6bb7..61ef849ca99 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.Channels; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java rename to src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java index 39551409dba..74454617ef4 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSink.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.SocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannel.java deleted file mode 100644 index bcd93b326b2..00000000000 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannel.java +++ /dev/null @@ -1,418 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.channel.socket.http; - -import static org.jboss.netty.channel.Channels.*; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.channels.NotYetConnectedException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.AbstractChannel; -import org.jboss.netty.channel.ChannelException; -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelSink; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.DefaultChannelPipeline; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.channel.socket.ClientSocketChannelFactory; -import org.jboss.netty.channel.socket.SocketChannel; -import org.jboss.netty.handler.codec.http.DefaultHttpChunk; -import org.jboss.netty.handler.codec.http.DefaultHttpRequest; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpRequestEncoder; -import org.jboss.netty.handler.codec.http.HttpResponse; -import org.jboss.netty.handler.codec.http.HttpResponseDecoder; -import org.jboss.netty.handler.codec.http.HttpResponseStatus; -import org.jboss.netty.handler.codec.http.HttpVersion; -import org.jboss.netty.handler.ssl.SslHandler; - -/** - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @author Trustin Lee - * @version $Rev$, $Date$ - */ -class HttpTunnelingClientSocketChannel extends AbstractChannel - implements org.jboss.netty.channel.socket.SocketChannel { - - final HttpTunnelingSocketChannelConfig config; - - volatile boolean requestHeaderWritten; - - final Object interestOpsLock = new Object(); - - final SocketChannel realChannel; - - private final HttpTunnelingClientSocketChannel.ServletChannelHandler handler = new ServletChannelHandler(); - - HttpTunnelingClientSocketChannel( - ChannelFactory factory, - ChannelPipeline pipeline, - ChannelSink sink, ClientSocketChannelFactory clientSocketChannelFactory) { - - super(null, factory, pipeline, sink); - - config = new HttpTunnelingSocketChannelConfig(this); - DefaultChannelPipeline channelPipeline = new DefaultChannelPipeline(); - channelPipeline.addLast("decoder", new HttpResponseDecoder()); - channelPipeline.addLast("encoder", new HttpRequestEncoder()); - channelPipeline.addLast("handler", handler); - realChannel = clientSocketChannelFactory.newChannel(channelPipeline); - - fireChannelOpen(this); - } - - @Override - public HttpTunnelingSocketChannelConfig getConfig() { - return config; - } - - @Override - public InetSocketAddress getLocalAddress() { - return realChannel.getLocalAddress(); - } - - @Override - public InetSocketAddress getRemoteAddress() { - return realChannel.getRemoteAddress(); - } - - @Override - public boolean isBound() { - return realChannel.isBound(); - } - - @Override - public boolean isConnected() { - return realChannel.isConnected(); - } - - @Override - public int getInterestOps() { - return realChannel.getInterestOps(); - } - - @Override - public boolean isWritable() { - return realChannel.isWritable(); - } - - @Override - protected boolean setClosed() { - return super.setClosed(); - } - - @Override - public ChannelFuture write(Object message, SocketAddress remoteAddress) { - if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) { - return super.write(message, null); - } - else { - return getUnsupportedOperationFuture(); - } - } - - void bindReal(final SocketAddress localAddress, final ChannelFuture future) { - realChannel.bind(localAddress).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - if (f.isSuccess()) { - future.setSuccess(); - } else { - future.setFailure(f.getCause()); - } - } - }); - } - - void connectReal(final SocketAddress remoteAddress, final ChannelFuture future) { - final SocketChannel virtualChannel = this; - realChannel.connect(remoteAddress).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - final String serverName = config.getServerName(); - final int serverPort = ((InetSocketAddress) remoteAddress).getPort(); - final String serverPath = config.getServerPath(); - - if (f.isSuccess()) { - // Configure SSL - SSLContext sslContext = config.getSslContext(); - ChannelFuture sslHandshakeFuture = null; - if (sslContext != null) { - // Create a new SSLEngine from the specified SSLContext. - SSLEngine engine; - if (serverName != null) { - engine = sslContext.createSSLEngine(serverName, serverPort); - } else { - engine = sslContext.createSSLEngine(); - } - - // Configure the SSLEngine. - engine.setUseClientMode(true); - engine.setEnableSessionCreation(config.isEnableSslSessionCreation()); - String[] enabledCipherSuites = config.getEnabledSslCipherSuites(); - if (enabledCipherSuites != null) { - engine.setEnabledCipherSuites(enabledCipherSuites); - } - String[] enabledProtocols = config.getEnabledSslProtocols(); - if (enabledProtocols != null) { - engine.setEnabledProtocols(enabledProtocols); - } - - SslHandler sslHandler = new SslHandler(engine); - realChannel.getPipeline().addFirst("ssl", sslHandler); - sslHandshakeFuture = sslHandler.handshake(); - } - - // Send the HTTP request. - final HttpRequest req = new DefaultHttpRequest( - HttpVersion.HTTP_1_1, HttpMethod.POST, serverPath); - if (serverName != null) { - req.setHeader(HttpHeaders.Names.HOST, serverName); - } - req.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream"); - req.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); - req.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY); - req.setHeader(HttpHeaders.Names.USER_AGENT, HttpTunnelingClientSocketChannel.class.getName()); - - if (sslHandshakeFuture == null) { - realChannel.write(req); - requestHeaderWritten = true; - future.setSuccess(); - fireChannelConnected(virtualChannel, remoteAddress); - } else { - sslHandshakeFuture.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - if (f.isSuccess()) { - realChannel.write(req); - requestHeaderWritten = true; - future.setSuccess(); - fireChannelConnected(virtualChannel, remoteAddress); - } else { - future.setFailure(f.getCause()); - fireExceptionCaught(virtualChannel, f.getCause()); - } - } - }); - } - } else { - future.setFailure(f.getCause()); - fireExceptionCaught(virtualChannel, f.getCause()); - } - } - }); - } - - void writeReal(final ChannelBuffer a, final ChannelFuture future) { - if (!requestHeaderWritten) { - throw new NotYetConnectedException(); - } - - final int size = a.readableBytes(); - final ChannelFuture f; - - if (size == 0) { - f = realChannel.write(ChannelBuffers.EMPTY_BUFFER); - } else { - f = realChannel.write(new DefaultHttpChunk(a)); - } - - f.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - if (f.isSuccess()) { - future.setSuccess(); - if (size != 0) { - fireWriteComplete(HttpTunnelingClientSocketChannel.this, size); - } - } else { - future.setFailure(f.getCause()); - } - } - }); - } - - private ChannelFuture writeLastChunk() { - if (!requestHeaderWritten) { - throw new NotYetConnectedException(); - } else { - return realChannel.write(HttpChunk.LAST_CHUNK); - } - } - - void setInterestOpsReal(final int interestOps, final ChannelFuture future) { - realChannel.setInterestOps(interestOps).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - if (f.isSuccess()) { - future.setSuccess(); - } else { - future.setFailure(f.getCause()); - } - } - }); - } - - void disconnectReal(final ChannelFuture future) { - writeLastChunk().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - realChannel.disconnect().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - if (f.isSuccess()) { - future.setSuccess(); - } else { - future.setFailure(f.getCause()); - } - } - }); - } - }); - } - - void unbindReal(final ChannelFuture future) { - writeLastChunk().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - realChannel.unbind().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - if (f.isSuccess()) { - future.setSuccess(); - } else { - future.setFailure(f.getCause()); - } - } - }); - } - }); - } - - void closeReal(final ChannelFuture future) { - writeLastChunk().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - realChannel.close().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) { - // Note: If 'future' refers to the closeFuture, - // setSuccess() and setFailure() do nothing. - // AbstractChannel.setClosed() should be called instead. - // (See AbstractChannel.ChannelCloseFuture) - - if (f.isSuccess()) { - future.setSuccess(); - } else { - future.setFailure(f.getCause()); - } - - // Notify the closeFuture. - setClosed(); - } - }); - } - }); - } - - final class ServletChannelHandler extends SimpleChannelUpstreamHandler { - - private volatile boolean readingChunks; - final SocketChannel virtualChannel = HttpTunnelingClientSocketChannel.this; - - @Override - public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) - throws Exception { - fireChannelBound(virtualChannel, (SocketAddress) e.getValue()); - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - if (!readingChunks) { - HttpResponse res = (HttpResponse) e.getMessage(); - if (res.getStatus().getCode() != HttpResponseStatus.OK.getCode()) { - throw new ChannelException("Unexpected HTTP response status: " + res.getStatus()); - } - - if (res.isChunked()) { - readingChunks = true; - } else { - ChannelBuffer content = res.getContent(); - if (content.readable()) { - fireMessageReceived(HttpTunnelingClientSocketChannel.this, content); - } - // Reached to the end of response - close the request. - closeReal(succeededFuture(virtualChannel)); - } - } else { - HttpChunk chunk = (HttpChunk) e.getMessage(); - if (!chunk.isLast()) { - fireMessageReceived(HttpTunnelingClientSocketChannel.this, chunk.getContent()); - } else { - readingChunks = false; - // Reached to the end of response - close the request. - closeReal(succeededFuture(virtualChannel)); - } - } - } - - @Override - public void channelInterestChanged(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - fireChannelInterestChanged(virtualChannel); - } - - @Override - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - fireChannelDisconnected(virtualChannel); - } - - @Override - public void channelUnbound(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - fireChannelUnbound(virtualChannel); - } - - @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) - throws Exception { - fireChannelClosed(virtualChannel); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - fireExceptionCaught(virtualChannel, e.getCause()); - realChannel.close(); - } - } -} diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannelFactory.java deleted file mode 100644 index a82a5db1748..00000000000 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannelFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.channel.socket.http; - -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelSink; -import org.jboss.netty.channel.socket.ClientSocketChannelFactory; -import org.jboss.netty.channel.socket.SocketChannel; - -/** - * Creates a client-side {@link SocketChannel} which connects to an - * {@link HttpTunnelingServlet} to communicate with the server application - * behind the {@link HttpTunnelingServlet}. Please refer to the - * package summary for - * the detailed usage. - * - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @author Trustin Lee - * @version $Rev$, $Date$ - * - * @apiviz.landmark - */ -public class HttpTunnelingClientSocketChannelFactory implements ClientSocketChannelFactory { - - private final ChannelSink sink = new HttpTunnelingClientSocketPipelineSink(); - private final ClientSocketChannelFactory clientSocketChannelFactory; - - /** - * Creates a new instance. - */ - public HttpTunnelingClientSocketChannelFactory(ClientSocketChannelFactory clientSocketChannelFactory) { - this.clientSocketChannelFactory = clientSocketChannelFactory; - } - - @Override - public SocketChannel newChannel(ChannelPipeline pipeline) { - return new HttpTunnelingClientSocketChannel(this, pipeline, sink, clientSocketChannelFactory); - } - - @Override - public void releaseExternalResources() { - clientSocketChannelFactory.releaseExternalResources(); - } -} diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketPipelineSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketPipelineSink.java deleted file mode 100644 index f4774d5bebe..00000000000 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketPipelineSink.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.channel.socket.http; - -import java.net.SocketAddress; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.channel.AbstractChannelSink; -import org.jboss.netty.channel.ChannelEvent; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelState; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.MessageEvent; - -/** - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @author Trustin Lee - * @version $Rev$, $Date$ - */ -final class HttpTunnelingClientSocketPipelineSink extends AbstractChannelSink { - - HttpTunnelingClientSocketPipelineSink() { - super(); - } - - @Override - public void eventSunk( - ChannelPipeline pipeline, ChannelEvent e) throws Exception { - HttpTunnelingClientSocketChannel channel = (HttpTunnelingClientSocketChannel) e.getChannel(); - ChannelFuture future = e.getFuture(); - if (e instanceof ChannelStateEvent) { - ChannelStateEvent stateEvent = (ChannelStateEvent) e; - ChannelState state = stateEvent.getState(); - Object value = stateEvent.getValue(); - switch (state) { - case OPEN: - if (Boolean.FALSE.equals(value)) { - channel.closeReal(future); - } - break; - case BOUND: - if (value != null) { - channel.bindReal((SocketAddress) value, future); - } else { - channel.unbindReal(future); - } - break; - case CONNECTED: - if (value != null) { - channel.connectReal((SocketAddress) value, future); - } else { - channel.closeReal(future); - } - break; - case INTEREST_OPS: - channel.setInterestOpsReal(((Integer) value).intValue(), future); - break; - } - } else if (e instanceof MessageEvent) { - channel.writeReal(((ChannelBuffer) ((MessageEvent) e).getMessage()), future); - } - } -} diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingServlet.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingServlet.java deleted file mode 100644 index 8e93c6f1e2f..00000000000 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingServlet.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.channel.socket.http; - -import java.io.EOFException; -import java.io.IOException; -import java.io.PushbackInputStream; -import java.net.SocketAddress; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.channel.local.DefaultLocalClientChannelFactory; -import org.jboss.netty.channel.local.LocalAddress; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.logging.InternalLogger; -import org.jboss.netty.logging.InternalLoggerFactory; - -/** - * An {@link HttpServlet} that proxies an incoming data to the actual server - * and vice versa. Please refer to the - * package summary for - * the detailed usage. - * - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @author Trustin Lee - * @version $Rev$, $Date$ - * - * @apiviz.landmark - */ -public class HttpTunnelingServlet extends HttpServlet { - - private static final long serialVersionUID = 4259910275899756070L; - - private static final String ENDPOINT = "endpoint"; - - static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpTunnelingServlet.class); - - private volatile SocketAddress remoteAddress; - private volatile ChannelFactory channelFactory; - - @Override - public void init() throws ServletException { - ServletConfig config = getServletConfig(); - String endpoint = config.getInitParameter(ENDPOINT); - if (endpoint == null) { - throw new ServletException("init-param '" + ENDPOINT + "' must be specified."); - } - - try { - remoteAddress = parseEndpoint(endpoint.trim()); - } catch (ServletException e) { - throw e; - } catch (Exception e) { - throw new ServletException("Failed to parse an endpoint.", e); - } - - try { - channelFactory = createChannelFactory(remoteAddress); - } catch (ServletException e) { - throw e; - } catch (Exception e) { - throw new ServletException("Failed to create a channel factory.", e); - } - - // Stuff for testing purpose - //ServerBootstrap b = new ServerBootstrap(new DefaultLocalServerChannelFactory()); - //b.getPipeline().addLast("logger", new LoggingHandler(getClass(), InternalLogLevel.INFO, true)); - //b.getPipeline().addLast("handler", new EchoHandler()); - //b.bind(remoteAddress); - } - - protected SocketAddress parseEndpoint(String endpoint) throws Exception { - if (endpoint.startsWith("local:")) { - return new LocalAddress(endpoint.substring(6).trim()); - } else { - throw new ServletException( - "Invalid or unknown endpoint: " + endpoint); - } - } - - protected ChannelFactory createChannelFactory(SocketAddress remoteAddress) throws Exception { - if (remoteAddress instanceof LocalAddress) { - return new DefaultLocalClientChannelFactory(); - } else { - throw new ServletException( - "Unsupported remote address type: " + - remoteAddress.getClass().getName()); - } - } - - @Override - public void destroy() { - try { - destroyChannelFactory(channelFactory); - } catch (Exception e) { - logger.warn("Failed to destroy a channel factory.", e); - } - } - - protected void destroyChannelFactory(ChannelFactory factory) throws Exception { - factory.releaseExternalResources(); - } - - @Override - protected void service(HttpServletRequest req, HttpServletResponse res) - throws ServletException, IOException { - if (!"POST".equalsIgnoreCase(req.getMethod())) { - logger.warn("Unallowed method: " + req.getMethod()); - res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - return; - } - - final ChannelPipeline pipeline = Channels.pipeline(); - final ServletOutputStream out = res.getOutputStream(); - final OutboundConnectionHandler handler = new OutboundConnectionHandler(out); - pipeline.addLast("handler", handler); - - Channel channel = channelFactory.newChannel(pipeline); - ChannelFuture future = channel.connect(remoteAddress).awaitUninterruptibly(); - if (!future.isSuccess()) { - Throwable cause = future.getCause(); - logger.warn("Endpoint unavailable: " + cause.getMessage(), cause); - res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - return; - } - - ChannelFuture lastWriteFuture = null; - try { - res.setStatus(HttpServletResponse.SC_OK); - res.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream"); - res.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY); - - // Initiate chunked encoding by flushing the headers. - out.flush(); - - PushbackInputStream in = - new PushbackInputStream(req.getInputStream()); - while (channel.isConnected()) { - ChannelBuffer buffer; - try { - buffer = read(in); - } catch (EOFException e) { - break; - } - if (buffer == null) { - break; - } - lastWriteFuture = channel.write(buffer); - } - } finally { - if (lastWriteFuture == null) { - channel.close(); - } else { - lastWriteFuture.addListener(ChannelFutureListener.CLOSE); - } - } - } - - private static ChannelBuffer read(PushbackInputStream in) throws IOException { - byte[] buf; - int readBytes; - - int bytesToRead = in.available(); - if (bytesToRead > 0) { - buf = new byte[bytesToRead]; - readBytes = in.read(buf); - } else if (bytesToRead == 0) { - int b = in.read(); - if (b < 0 || in.available() < 0) { - return null; - } - in.unread(b); - bytesToRead = in.available(); - buf = new byte[bytesToRead]; - readBytes = in.read(buf); - } else { - return null; - } - - assert readBytes > 0; - - ChannelBuffer buffer; - if (readBytes == buf.length) { - buffer = ChannelBuffers.wrappedBuffer(buf); - } else { - // A rare case, but it sometimes happen. - buffer = ChannelBuffers.wrappedBuffer(buf, 0, readBytes); - } - return buffer; - } - - private static final class OutboundConnectionHandler extends SimpleChannelUpstreamHandler { - - private final ServletOutputStream out; - - public OutboundConnectionHandler(ServletOutputStream out) { - this.out = out; - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); - synchronized (this) { - buffer.readBytes(out, buffer.readableBytes()); - out.flush(); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - logger.warn("Unexpected exception while HTTP tunneling", e.getCause()); - e.getChannel().close(); - } - } -} diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingSocketChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingSocketChannelConfig.java deleted file mode 100644 index 8c688ed85bc..00000000000 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelingSocketChannelConfig.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.channel.socket.http; - -import java.util.Map; -import java.util.Map.Entry; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSession; - -import org.jboss.netty.buffer.ChannelBufferFactory; -import org.jboss.netty.channel.ChannelConfig; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.socket.SocketChannel; -import org.jboss.netty.channel.socket.SocketChannelConfig; -import org.jboss.netty.util.internal.ConversionUtil; - -/** - * The {@link ChannelConfig} of a client-side HTTP tunneling - * {@link SocketChannel}. A {@link SocketChannel} created by - * {@link HttpTunnelingClientSocketChannelFactory} will return an instance of - * this configuration type for {@link SocketChannel#getConfig()}. - * - *

Available options

- * - * In addition to the options provided by {@link SocketChannelConfig}, - * {@link HttpTunnelingSocketChannelConfig} allows the following options in - * the option map: - * - * - * - * - * - * - * - * - * - * - * - * - * - *
NameAssociated setter method
{@code "sslContext"}{@link #setSslContext(SSLContext)}
{@code "enabledSslCiperSuites"}{@link #setEnabledSslCipherSuites(String[])}
{@code "enabledSslProtocols"}{@link #setEnabledSslProtocols(String[])}
{@code "enableSslSessionCreation"}{@link #setEnableSslSessionCreation(boolean)}
- * - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @author Trustin Lee - * @version $Rev$, $Date$ - * - * @apiviz.landmark - */ -public final class HttpTunnelingSocketChannelConfig implements SocketChannelConfig { - - private final HttpTunnelingClientSocketChannel channel; - private volatile String serverName; - private volatile String serverPath = "/netty-tunnel"; - private volatile SSLContext sslContext; - private volatile String[] enabledSslCipherSuites; - private volatile String[] enabledSslProtocols; - private volatile boolean enableSslSessionCreation = true; - - /** - * Creates a new instance. - */ - HttpTunnelingSocketChannelConfig(HttpTunnelingClientSocketChannel channel) { - this.channel = channel; - } - - /** - * Returns the host name of the HTTP server. If {@code null}, the - * {@code "Host"} header is not sent by the HTTP tunneling client. - */ - public String getServerName() { - return serverName; - } - - /** - * Sets the host name of the HTTP server. If {@code null}, the - * {@code "Host"} header is not sent by the HTTP tunneling client. - */ - public void setServerName(String serverName) { - this.serverName = serverName; - } - - /** - * Returns the path where the {@link HttpTunnelingServlet} is mapped to. - * The default value is {@code "/netty-tunnel"}. - */ - public String getServerPath() { - return serverPath; - } - - /** - * Sets the path where the {@link HttpTunnelingServlet} is mapped to. - * The default value is {@code "/netty-tunnel"}. - */ - public void setServerPath(String serverPath) { - if (serverPath == null) { - throw new NullPointerException("serverPath"); - } - this.serverPath = serverPath; - } - - /** - * Returns the {@link SSLContext} which is used to establish an HTTPS - * connection. If {@code null}, a plain-text HTTP connection is established. - */ - public SSLContext getSslContext() { - return sslContext; - } - - /** - * Sets the {@link SSLContext} which is used to establish an HTTPS connection. - * If {@code null}, a plain-text HTTP connection is established. - */ - public void setSslContext(SSLContext sslContext) { - this.sslContext = sslContext; - } - - /** - * Returns the cipher suites enabled for use on an {@link SSLEngine}. - * If {@code null}, the default value will be used. - * - * @see SSLEngine#getEnabledCipherSuites() - */ - public String[] getEnabledSslCipherSuites() { - String[] suites = enabledSslCipherSuites; - if (suites == null) { - return null; - } else { - return suites.clone(); - } - } - - /** - * Sets the cipher suites enabled for use on an {@link SSLEngine}. - * If {@code null}, the default value will be used. - * - * @see SSLEngine#setEnabledCipherSuites(String[]) - */ - public void setEnabledSslCipherSuites(String[] suites) { - if (suites == null) { - enabledSslCipherSuites = null; - } else { - enabledSslCipherSuites = suites.clone(); - } - } - - /** - * Returns the protocol versions enabled for use on an {@link SSLEngine}. - * - * @see SSLEngine#getEnabledProtocols() - */ - public String[] getEnabledSslProtocols() { - String[] protocols = enabledSslProtocols; - if (protocols == null) { - return null; - } else { - return protocols.clone(); - } - } - - /** - * Sets the protocol versions enabled for use on an {@link SSLEngine}. - * - * @see SSLEngine#setEnabledProtocols(String[]) - */ - public void setEnabledSslProtocols(String[] protocols) { - if (protocols == null) { - enabledSslProtocols = null; - } else { - enabledSslProtocols = protocols.clone(); - } - } - - /** - * Returns {@code true} if new {@link SSLSession}s may be established by - * an {@link SSLEngine}. - * - * @see SSLEngine#getEnableSessionCreation() - */ - public boolean isEnableSslSessionCreation() { - return enableSslSessionCreation; - } - - /** - * Sets whether new {@link SSLSession}s may be established by an - * {@link SSLEngine}. - * - * @see SSLEngine#setEnableSessionCreation(boolean) - */ - public void setEnableSslSessionCreation(boolean flag) { - enableSslSessionCreation = flag; - } - - @Override - public void setOptions(Map options) { - for (Entry e: options.entrySet()) { - setOption(e.getKey(), e.getValue()); - } - } - - @Override - public boolean setOption(String key, Object value) { - if (channel.realChannel.getConfig().setOption(key, value)) { - return true; - } - - if (key.equals("serverName")){ - setServerName(String.valueOf(value)); - } else if (key.equals("serverPath")){ - setServerPath(String.valueOf(value)); - } else if (key.equals("sslContext")) { - setSslContext((SSLContext) value); - } else if (key.equals("enabledSslCipherSuites")){ - setEnabledSslCipherSuites(ConversionUtil.toStringArray(value)); - } else if (key.equals("enabledSslProtocols")){ - setEnabledSslProtocols(ConversionUtil.toStringArray(value)); - } else if (key.equals("enableSslSessionCreation")){ - setEnableSslSessionCreation(ConversionUtil.toBoolean(value)); - } else { - return false; - } - - return true; - } - - @Override - public int getReceiveBufferSize() { - return channel.realChannel.getConfig().getReceiveBufferSize(); - } - - @Override - public int getSendBufferSize() { - return channel.realChannel.getConfig().getSendBufferSize(); - } - - @Override - public int getSoLinger() { - return channel.realChannel.getConfig().getSoLinger(); - } - - @Override - public int getTrafficClass() { - return channel.realChannel.getConfig().getTrafficClass(); - } - - @Override - public boolean isKeepAlive() { - return channel.realChannel.getConfig().isKeepAlive(); - } - - @Override - public boolean isReuseAddress() { - return channel.realChannel.getConfig().isReuseAddress(); - } - - @Override - public boolean isTcpNoDelay() { - return channel.realChannel.getConfig().isTcpNoDelay(); - } - - @Override - public void setKeepAlive(boolean keepAlive) { - channel.realChannel.getConfig().setKeepAlive(keepAlive); - } - - @Override - public void setPerformancePreferences( - int connectionTime, int latency, int bandwidth) { - channel.realChannel.getConfig().setPerformancePreferences(connectionTime, latency, bandwidth); - } - - @Override - public void setReceiveBufferSize(int receiveBufferSize) { - channel.realChannel.getConfig().setReceiveBufferSize(receiveBufferSize); - } - - @Override - public void setReuseAddress(boolean reuseAddress) { - channel.realChannel.getConfig().setReuseAddress(reuseAddress); - } - - @Override - public void setSendBufferSize(int sendBufferSize) { - channel.realChannel.getConfig().setSendBufferSize(sendBufferSize); - - } - - @Override - public void setSoLinger(int soLinger) { - channel.realChannel.getConfig().setSoLinger(soLinger); - } - - @Override - public void setTcpNoDelay(boolean tcpNoDelay) { - channel.realChannel.getConfig().setTcpNoDelay(tcpNoDelay); - } - - @Override - public void setTrafficClass(int trafficClass) { - channel.realChannel.getConfig().setTrafficClass(trafficClass); - } - - @Override - public ChannelBufferFactory getBufferFactory() { - return channel.realChannel.getConfig().getBufferFactory(); - } - - @Override - public int getConnectTimeoutMillis() { - return channel.realChannel.getConfig().getConnectTimeoutMillis(); - } - - @Override - public ChannelPipelineFactory getPipelineFactory() { - return channel.realChannel.getConfig().getPipelineFactory(); - } - - @Override - public void setBufferFactory(ChannelBufferFactory bufferFactory) { - channel.realChannel.getConfig().setBufferFactory(bufferFactory); - } - - @Override - public void setConnectTimeoutMillis(int connectTimeoutMillis) { - channel.realChannel.getConfig().setConnectTimeoutMillis(connectTimeoutMillis); - } - - @Override - public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) { - channel.realChannel.getConfig().setPipelineFactory(pipelineFactory); - } -} diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java b/src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java similarity index 85% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java rename to src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java index c54d9472ae9..603d43008f1 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationManager.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java @@ -13,11 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; -import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.DESATURATED; -import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.NO_CHANGE; -import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.SATURATED; +import static org.jboss.netty.channel.socket.http.SaturationStateChange.DESATURATED; +import static org.jboss.netty.channel.socket.http.SaturationStateChange.NO_CHANGE; +import static org.jboss.netty.channel.socket.http.SaturationStateChange.SATURATED; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java b/src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java similarity index 93% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java rename to src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java index 3173ef8706f..f5eb8c8d4b6 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/SaturationStateChange.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; /** * Represents the state change of a chanel in response in the amount of pending data to be diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java similarity index 99% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java rename to src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java index 31918020f78..bc935e510dc 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitch.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; import java.util.List; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java similarity index 95% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java rename to src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java index 6e4e8552c12..45d4f831677 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchDownstreamInterface.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.ChannelFuture; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java rename to src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java index 9491f6bd469..8af74a217d5 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchUpstreamInterface.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java b/src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java similarity index 95% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java rename to src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java index 75a5388917d..91c5d4e3568 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelIdGenerator.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; /** * This interface is used by the server end of an http tunnel to generate new diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java b/src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java rename to src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java index 6f3ba2796a5..963dbe421a8 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/TunnelWrappedServerChannelHandler.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.SocketAddress; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java b/src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java similarity index 98% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java rename to src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java index 5236f3497e5..f0fd2c820c6 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenter.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.List; diff --git a/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java b/src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java similarity index 97% rename from src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java rename to src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java index e3cf664f9d5..42657373911 100644 --- a/src/main/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitter.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java b/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java deleted file mode 100644 index 0561a92dc6b..00000000000 --- a/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.example.http.tunnel; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.InetSocketAddress; -import java.net.URI; -import java.util.concurrent.Executors; - -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.socket.http.HttpTunnelingClientSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; -import org.jboss.netty.handler.codec.string.StringDecoder; -import org.jboss.netty.handler.codec.string.StringEncoder; -import org.jboss.netty.handler.logging.LoggingHandler; -import org.jboss.netty.logging.InternalLogLevel; - -/** - * An HTTP tunneled version of the telnet client example. Please refer to the - * API documentation of the org.jboss.netty.channel.socket.http package - * for the detailed instruction on how to deploy the server-side HTTP tunnel in - * your Servlet container. - * - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @version $Rev$, $Date$ - */ -public class HttpTunnelingClientExample { - - public static void main(String[] args) throws Exception { - if (args.length != 1) { - System.err.println( - "Usage: " + HttpTunnelingClientExample.class.getSimpleName() + - " "); - System.err.println( - "Example: " + HttpTunnelingClientExample.class.getSimpleName() + - " http://localhost:8080/netty-tunnel"); - return; - } - - URI uri = new URI(args[0]); - String scheme = uri.getScheme() == null? "http" : uri.getScheme(); - - // Configure the client. - ClientBootstrap b = new ClientBootstrap( - new HttpTunnelingClientSocketChannelFactory( - new OioClientSocketChannelFactory(Executors.newCachedThreadPool()))); - - b.setPipelineFactory(new ChannelPipelineFactory() { - @Override - public ChannelPipeline getPipeline() throws Exception { - return Channels.pipeline( - new StringDecoder(), - new StringEncoder(), - new LoggingHandler(InternalLogLevel.INFO)); - } - }); - - // Set additional options required by the HTTP tunneling transport. - b.setOption("serverName", uri.getHost()); - b.setOption("serverPath", uri.getRawPath()); - - // Configure SSL if necessary - if (scheme.equals("https")) { - b.setOption("sslContext", SecureChatSslContextFactory.getClientContext()); - } else if (!scheme.equals("http")) { - // Only HTTP and HTTPS are supported. - System.err.println("Only HTTP(S) is supported."); - return; - } - - // Make the connection attempt. - ChannelFuture channelFuture = b.connect( - new InetSocketAddress(uri.getHost(), uri.getPort())); - channelFuture.awaitUninterruptibly(); - - // Read commands from the stdin. - System.out.println("Enter text ('quit' to exit)"); - ChannelFuture lastWriteFuture = null; - BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); - for (; ;) { - String line = in.readLine(); - if (line == null || "quit".equalsIgnoreCase(line)) { - break; - } - - // Sends the received line to the server. - lastWriteFuture = channelFuture.getChannel().write(line); - } - - // Wait until all messages are flushed before closing the channel. - if (lastWriteFuture != null) { - lastWriteFuture.awaitUninterruptibly(); - } - - channelFuture.getChannel().close(); - // Wait until the connection is closed or the connection attempt fails. - channelFuture.getChannel().getCloseFuture().awaitUninterruptibly(); - - // Shut down all threads. - b.releaseExternalResources(); - } -} diff --git a/src/main/java/org/jboss/netty/example/http/tunnel/LocalEchoServerRegistration.java b/src/main/java/org/jboss/netty/example/http/tunnel/LocalEchoServerRegistration.java deleted file mode 100644 index cf49ae23883..00000000000 --- a/src/main/java/org/jboss/netty/example/http/tunnel/LocalEchoServerRegistration.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2009 Red Hat, Inc. - * - * Red Hat 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 org.jboss.netty.example.http.tunnel; - -import org.jboss.netty.bootstrap.ServerBootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.local.DefaultLocalServerChannelFactory; -import org.jboss.netty.channel.local.LocalAddress; -import org.jboss.netty.example.echo.EchoServerHandler; - -/** - * Deploy this in JBossAS 5 or other IoC container by adding the following bean. - * - *
- * <bean name="org.jboss.netty.example.http.tunnel.LocalEchoServerRegistration"
- *       class="org.jboss.netty.example.http.tunnel.LocalEchoServerRegistration" />
- * 
- * - * @author The Netty Project - * @author Andy Taylor (andy.taylor@jboss.org) - * @version $Rev$, $Date$ - */ -public class LocalEchoServerRegistration { - - private final ChannelFactory factory = new DefaultLocalServerChannelFactory(); - private volatile Channel serverChannel; - - public void start() { - ServerBootstrap serverBootstrap = new ServerBootstrap(factory); - EchoServerHandler handler = new EchoServerHandler(); - serverBootstrap.getPipeline().addLast("handler", handler); - - // Note that "myLocalServer" is the endpoint which was specified in web.xml. - serverChannel = serverBootstrap.bind(new LocalAddress("myLocalServer")); - } - - public void stop() { - serverChannel.close(); - } -} diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java b/src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java index b219566a499..31664d4e007 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/AcceptedServerChannelRequestDispatchTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java index 1b853da6859..1372539af16 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelConfig.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.Map; import java.util.Map.Entry; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java similarity index 95% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java index 7ed63f9ac86..d439bcbd7ba 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeChannelSink.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.LinkedList; import java.util.Queue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java similarity index 96% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java index 3958b011718..f5465f7013b 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeClientSocketChannelFactory.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java index 0bda1e4f6ad..59f0a50948c 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannel.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.jboss.netty.channel.Channels.fireChannelBound; import static org.jboss.netty.channel.Channels.fireChannelConnected; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java similarity index 97% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java index 539456c343e..36dec1904f1 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelConfig.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.buffer.ChannelBufferFactory; import org.jboss.netty.buffer.HeapChannelBufferFactory; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java similarity index 96% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java index 1da5623cc9f..2784a715db1 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeServerSocketChannelFactory.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelSink; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java rename to src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java index 1cc94eb7602..dc8692ad02e 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/FakeSocketChannel.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java index 123e85af4f2..571314615ea 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelAcceptedChannelSinkTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java index c2308957db2..f1d398947b6 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelConfigTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java index a7ed487015c..bd0d4839af9 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientChannelTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java index 3db51162cc4..5955c444f37 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientPollHandlerTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java index 361d143f88a..987fcaca1a3 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelClientSendHandlerTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java index 1483b867256..d926cc67af8 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelFactoryTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java index 2beee03e9c7..c85d8038bb7 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelSinkTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java index bbe8d63218a..256c607fdb2 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelServerChannelTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java index 034c6e94881..7fe0d50b8ed 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelSoakTester.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java index 6209b1eb9b9..e1a44e87d4d 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/HttpTunnelTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java b/src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java similarity index 97% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java rename to src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java index fd5cb10ec32..a63c7bcd59c 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/MockChannelStateListener.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.net.InetSocketAddress; import java.util.ArrayList; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java rename to src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java index 1358aa9bdb0..e423bf0d2b5 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtils.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java index 2b9987993d2..2936c3da1fc 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NettyTestUtilsTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java b/src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java similarity index 96% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java rename to src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java index 1ece636a9fd..53c8de0158a 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/NullChannelHandler.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import org.jboss.netty.channel.ChannelDownstreamHandler; import org.jboss.netty.channel.ChannelEvent; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java b/src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java similarity index 85% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java index d60340d92fe..922a31dbf1e 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/SaturationManagerTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java @@ -1,7 +1,7 @@ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.*; -import static org.jboss.netty.channel.socket.httptunnel.SaturationStateChange.*; +import static org.jboss.netty.channel.socket.http.SaturationStateChange.*; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java b/src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java similarity index 97% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java index 8851804ffc2..f7b0b139c3a 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/ServerMessageSwitchTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; @@ -24,7 +24,7 @@ import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.socket.httptunnel.ServerMessageSwitchUpstreamInterface.TunnelStatus; +import org.jboss.netty.channel.socket.http.ServerMessageSwitchUpstreamInterface.TunnelStatus; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jmock.Expectations; import org.jmock.integration.junit4.JMock; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java b/src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java similarity index 96% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java rename to src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java index 91fa22b833c..4a2c7296fea 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/UpstreamEventCatcher.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import java.util.LinkedList; import java.util.Queue; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java b/src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java similarity index 99% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java index 3aa294f4459..63fbd695ba6 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteFragmenterTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java b/src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java similarity index 98% rename from src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java rename to src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java index c4453b14e0d..dd823833ac1 100644 --- a/src/test/java/org/jboss/netty/channel/socket/httptunnel/WriteSplitterTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java @@ -14,7 +14,7 @@ * under the License. */ -package org.jboss.netty.channel.socket.httptunnel; +package org.jboss.netty.channel.socket.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; From fed0f761ea2525a6c0833bfb0984f9cf02fd817a Mon Sep 17 00:00:00 2001 From: iainmcgin Date: Fri, 8 Apr 2011 14:22:12 +0100 Subject: [PATCH 5/6] Fixed formatting to conform to project standards --- .../AcceptedServerChannelPipelineFactory.java | 32 +- .../AcceptedServerChannelRequestDispatch.java | 292 +++--- .../socket/http/ChannelFutureAggregator.java | 67 +- .../socket/http/DefaultTunnelIdGenerator.java | 40 +- .../http/HttpTunnelAcceptedChannel.java | 148 ++-- .../http/HttpTunnelAcceptedChannelConfig.java | 187 ++-- .../HttpTunnelAcceptedChannelFactory.java | 8 +- .../HttpTunnelAcceptedChannelReceiver.java | 11 +- .../http/HttpTunnelAcceptedChannelSink.java | 174 ++-- .../socket/http/HttpTunnelChannelConfig.java | 237 +++-- .../socket/http/HttpTunnelClientChannel.java | 646 +++++++------- .../http/HttpTunnelClientChannelConfig.java | 257 +++--- .../http/HttpTunnelClientChannelFactory.java | 49 +- .../http/HttpTunnelClientChannelSink.java | 80 +- .../http/HttpTunnelClientPollHandler.java | 109 ++- .../http/HttpTunnelClientSendHandler.java | 396 ++++----- .../http/HttpTunnelClientWorkerOwner.java | 60 +- .../socket/http/HttpTunnelMessageUtils.java | 611 +++++++------ .../socket/http/HttpTunnelServerChannel.java | 155 ++-- .../http/HttpTunnelServerChannelConfig.java | 215 ++--- .../http/HttpTunnelServerChannelFactory.java | 57 +- .../http/HttpTunnelServerChannelSink.java | 101 +-- .../socket/http/SaturationManager.java | 69 +- .../socket/http/SaturationStateChange.java | 4 +- .../socket/http/ServerMessageSwitch.java | 433 +++++---- ...erverMessageSwitchDownstreamInterface.java | 8 +- .../ServerMessageSwitchUpstreamInterface.java | 34 +- .../socket/http/TunnelIdGenerator.java | 17 +- .../TunnelWrappedServerChannelHandler.java | 84 +- .../channel/socket/http/WriteFragmenter.java | 60 +- .../channel/socket/http/WriteSplitter.java | 44 +- ...eptedServerChannelRequestDispatchTest.java | 415 ++++----- .../socket/http/FakeChannelConfig.java | 334 +++---- .../channel/socket/http/FakeChannelSink.java | 13 +- .../http/FakeClientSocketChannelFactory.java | 41 +- .../socket/http/FakeServerSocketChannel.java | 82 +- .../http/FakeServerSocketChannelConfig.java | 68 +- .../http/FakeServerSocketChannelFactory.java | 24 +- .../socket/http/FakeSocketChannel.java | 147 ++-- .../HttpTunnelAcceptedChannelSinkTest.java | 92 +- .../HttpTunnelClientChannelConfigTest.java | 551 ++++++------ .../http/HttpTunnelClientChannelTest.java | 431 ++++----- .../http/HttpTunnelClientPollHandlerTest.java | 171 ++-- .../http/HttpTunnelClientSendHandlerTest.java | 356 ++++---- .../HttpTunnelServerChannelFactoryTest.java | 117 ++- .../http/HttpTunnelServerChannelSinkTest.java | 242 +++-- .../http/HttpTunnelServerChannelTest.java | 382 ++++---- .../socket/http/HttpTunnelSoakTester.java | 829 +++++++++--------- .../channel/socket/http/HttpTunnelTest.java | 303 +++---- .../socket/http/MockChannelStateListener.java | 47 +- .../channel/socket/http/NettyTestUtils.java | 289 +++--- .../socket/http/NettyTestUtilsTest.java | 202 ++--- .../socket/http/NullChannelHandler.java | 20 +- .../socket/http/SaturationManagerTest.java | 46 +- .../socket/http/ServerMessageSwitchTest.java | 283 +++--- .../socket/http/UpstreamEventCatcher.java | 15 +- .../socket/http/WriteFragmenterTest.java | 239 ++--- .../socket/http/WriteSplitterTest.java | 135 ++- 58 files changed, 5106 insertions(+), 5453 deletions(-) diff --git a/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java index 668361d921a..e67f4f3b60e 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelPipelineFactory.java @@ -32,25 +32,25 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class AcceptedServerChannelPipelineFactory implements ChannelPipelineFactory -{ +class AcceptedServerChannelPipelineFactory implements ChannelPipelineFactory { - private final ServerMessageSwitch messageSwitch; + private final ServerMessageSwitch messageSwitch; - public AcceptedServerChannelPipelineFactory(ServerMessageSwitch messageSwitch) - { - this.messageSwitch = messageSwitch; - } + public AcceptedServerChannelPipelineFactory( + ServerMessageSwitch messageSwitch) { + this.messageSwitch = messageSwitch; + } - public ChannelPipeline getPipeline() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder()); - pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder()); - pipeline.addLast("httpChunkAggregator", new HttpChunkAggregator(HttpTunnelMessageUtils.MAX_BODY_SIZE)); - pipeline.addLast("messageSwitchClient", new AcceptedServerChannelRequestDispatch(messageSwitch)); + pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder()); + pipeline.addLast("httpChunkAggregator", new HttpChunkAggregator( + HttpTunnelMessageUtils.MAX_BODY_SIZE)); + pipeline.addLast("messageSwitchClient", + new AcceptedServerChannelRequestDispatch(messageSwitch)); - return pipeline; - } + return pipeline; + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java index 1fbbcba0d11..f2c7831e6c2 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatch.java @@ -39,156 +39,144 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class AcceptedServerChannelRequestDispatch extends SimpleChannelUpstreamHandler -{ - - public static final String NAME = "AcceptedServerChannelRequestDispatch"; - - private static final InternalLogger LOG = InternalLoggerFactory - .getInstance(AcceptedServerChannelRequestDispatch.class); - - private final ServerMessageSwitchUpstreamInterface messageSwitch; - - public AcceptedServerChannelRequestDispatch(ServerMessageSwitchUpstreamInterface messageSwitch) - { - this.messageSwitch = messageSwitch; - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - HttpRequest request = (HttpRequest) e.getMessage(); - - if (HttpTunnelMessageUtils.isOpenTunnelRequest(request)) - { - handleOpenTunnel(ctx); - } - else if (HttpTunnelMessageUtils.isSendDataRequest(request)) - { - handleSendData(ctx, request); - } - else if (HttpTunnelMessageUtils.isReceiveDataRequest(request)) - { - handleReceiveData(ctx, request); - } - else if (HttpTunnelMessageUtils.isCloseTunnelRequest(request)) - { - handleCloseTunnel(ctx, request); - } - else - { - respondWithRejection(ctx, request, "invalid request to netty HTTP tunnel gateway"); - } - } - - private void handleOpenTunnel(ChannelHandlerContext ctx) - { - String tunnelId = messageSwitch.createTunnel((InetSocketAddress) ctx.getChannel().getRemoteAddress()); - if (LOG.isDebugEnabled()) - { - LOG.debug("open tunnel request received from " + ctx.getChannel().getRemoteAddress() + " - allocated ID " - + tunnelId); - } - respondWith(ctx, HttpTunnelMessageUtils.createTunnelOpenResponse(tunnelId)); - } - - private void handleCloseTunnel(ChannelHandlerContext ctx, HttpRequest request) - { - String tunnelId = checkTunnelId(request, ctx); - if (tunnelId == null) - { - return; - } - - if (LOG.isDebugEnabled()) - { - LOG.debug("close tunnel request received for tunnel " + tunnelId); - } - messageSwitch.clientCloseTunnel(tunnelId); - respondWith(ctx, HttpTunnelMessageUtils.createTunnelCloseResponse()).addListener(ChannelFutureListener.CLOSE); - } - - private void handleSendData(ChannelHandlerContext ctx, HttpRequest request) - { - String tunnelId = checkTunnelId(request, ctx); - if (tunnelId == null) - { - return; - } - if (LOG.isDebugEnabled()) - { - LOG.debug("send data request received for tunnel " + tunnelId); - } - - if (HttpHeaders.getContentLength(request) == 0 || request.getContent() == null - || request.getContent().readableBytes() == 0) - { - respondWithRejection(ctx, request, "Send data requests must contain data"); - return; - } - - messageSwitch.routeInboundData(tunnelId, request.getContent()); - respondWith(ctx, HttpTunnelMessageUtils.createSendDataResponse()); - } - - private void handleReceiveData(ChannelHandlerContext ctx, HttpRequest request) - { - String tunnelId = checkTunnelId(request, ctx); - if (tunnelId == null) - { - return; - } - if (LOG.isDebugEnabled()) - { - LOG.debug("poll data request received for tunnel " + tunnelId); - } - messageSwitch.pollOutboundData(tunnelId, ctx.getChannel()); - } - - private String checkTunnelId(HttpRequest request, ChannelHandlerContext ctx) - { - String tunnelId = HttpTunnelMessageUtils.extractTunnelId(request); - if (tunnelId == null) - { - respondWithRejection(ctx, request, "no tunnel id specified in request"); - } - else if (!messageSwitch.isOpenTunnel(tunnelId)) - { - respondWithRejection(ctx, request, "specified tunnel is either closed or does not exist"); - return null; - } - - return tunnelId; - } - - /** - * Sends the provided response back on the channel, returning the created ChannelFuture - * for this operation. - */ - private ChannelFuture respondWith(ChannelHandlerContext ctx, HttpResponse response) - { - ChannelFuture writeFuture = Channels.future(ctx.getChannel()); - Channels.write(ctx, writeFuture, response); - return writeFuture; - } - - /** - * Sends an HTTP 400 message back to on the channel with the specified error message, and asynchronously - * closes the channel after this is successfully sent. - */ - private void respondWithRejection(ChannelHandlerContext ctx, HttpRequest rejectedRequest, String errorMessage) - { - if (LOG.isWarnEnabled()) - { - SocketAddress remoteAddress = ctx.getChannel().getRemoteAddress(); - String tunnelId = HttpTunnelMessageUtils.extractTunnelId(rejectedRequest); - if (tunnelId == null) - { - tunnelId = ""; - } - LOG.warn("Rejecting request from " + remoteAddress + " representing tunnel " + tunnelId + ": " + errorMessage); - } - HttpResponse rejection = HttpTunnelMessageUtils.createRejection(rejectedRequest, errorMessage); - respondWith(ctx, rejection).addListener(ChannelFutureListener.CLOSE); - } +class AcceptedServerChannelRequestDispatch extends SimpleChannelUpstreamHandler { + + public static final String NAME = "AcceptedServerChannelRequestDispatch"; + + private static final InternalLogger LOG = InternalLoggerFactory + .getInstance(AcceptedServerChannelRequestDispatch.class); + + private final ServerMessageSwitchUpstreamInterface messageSwitch; + + public AcceptedServerChannelRequestDispatch( + ServerMessageSwitchUpstreamInterface messageSwitch) { + this.messageSwitch = messageSwitch; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + HttpRequest request = (HttpRequest) e.getMessage(); + + if (HttpTunnelMessageUtils.isOpenTunnelRequest(request)) { + handleOpenTunnel(ctx); + } else if (HttpTunnelMessageUtils.isSendDataRequest(request)) { + handleSendData(ctx, request); + } else if (HttpTunnelMessageUtils.isReceiveDataRequest(request)) { + handleReceiveData(ctx, request); + } else if (HttpTunnelMessageUtils.isCloseTunnelRequest(request)) { + handleCloseTunnel(ctx, request); + } else { + respondWithRejection(ctx, request, + "invalid request to netty HTTP tunnel gateway"); + } + } + + private void handleOpenTunnel(ChannelHandlerContext ctx) { + String tunnelId = + messageSwitch.createTunnel((InetSocketAddress) ctx.getChannel() + .getRemoteAddress()); + if (LOG.isDebugEnabled()) { + LOG.debug("open tunnel request received from " + + ctx.getChannel().getRemoteAddress() + " - allocated ID " + + tunnelId); + } + respondWith(ctx, + HttpTunnelMessageUtils.createTunnelOpenResponse(tunnelId)); + } + + private void handleCloseTunnel(ChannelHandlerContext ctx, + HttpRequest request) { + String tunnelId = checkTunnelId(request, ctx); + if (tunnelId == null) { + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("close tunnel request received for tunnel " + tunnelId); + } + messageSwitch.clientCloseTunnel(tunnelId); + respondWith(ctx, HttpTunnelMessageUtils.createTunnelCloseResponse()) + .addListener(ChannelFutureListener.CLOSE); + } + + private void handleSendData(ChannelHandlerContext ctx, HttpRequest request) { + String tunnelId = checkTunnelId(request, ctx); + if (tunnelId == null) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("send data request received for tunnel " + tunnelId); + } + + if (HttpHeaders.getContentLength(request) == 0 || + request.getContent() == null || + request.getContent().readableBytes() == 0) { + respondWithRejection(ctx, request, + "Send data requests must contain data"); + return; + } + + messageSwitch.routeInboundData(tunnelId, request.getContent()); + respondWith(ctx, HttpTunnelMessageUtils.createSendDataResponse()); + } + + private void handleReceiveData(ChannelHandlerContext ctx, + HttpRequest request) { + String tunnelId = checkTunnelId(request, ctx); + if (tunnelId == null) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("poll data request received for tunnel " + tunnelId); + } + messageSwitch.pollOutboundData(tunnelId, ctx.getChannel()); + } + + private String checkTunnelId(HttpRequest request, ChannelHandlerContext ctx) { + String tunnelId = HttpTunnelMessageUtils.extractTunnelId(request); + if (tunnelId == null) { + respondWithRejection(ctx, request, + "no tunnel id specified in request"); + } else if (!messageSwitch.isOpenTunnel(tunnelId)) { + respondWithRejection(ctx, request, + "specified tunnel is either closed or does not exist"); + return null; + } + + return tunnelId; + } + + /** + * Sends the provided response back on the channel, returning the created ChannelFuture + * for this operation. + */ + private ChannelFuture respondWith(ChannelHandlerContext ctx, + HttpResponse response) { + ChannelFuture writeFuture = Channels.future(ctx.getChannel()); + Channels.write(ctx, writeFuture, response); + return writeFuture; + } + + /** + * Sends an HTTP 400 message back to on the channel with the specified error message, and asynchronously + * closes the channel after this is successfully sent. + */ + private void respondWithRejection(ChannelHandlerContext ctx, + HttpRequest rejectedRequest, String errorMessage) { + if (LOG.isWarnEnabled()) { + SocketAddress remoteAddress = ctx.getChannel().getRemoteAddress(); + String tunnelId = + HttpTunnelMessageUtils.extractTunnelId(rejectedRequest); + if (tunnelId == null) { + tunnelId = ""; + } + LOG.warn("Rejecting request from " + remoteAddress + + " representing tunnel " + tunnelId + ": " + errorMessage); + } + HttpResponse rejection = + HttpTunnelMessageUtils.createRejection(rejectedRequest, + errorMessage); + respondWith(ctx, rejection).addListener(ChannelFutureListener.CLOSE); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java b/src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java index 92b56c0e130..2f9d53b81f4 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ChannelFutureAggregator.java @@ -31,48 +31,41 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class ChannelFutureAggregator implements ChannelFutureListener -{ +class ChannelFutureAggregator implements ChannelFutureListener { - private final ChannelFuture aggregateFuture; + private final ChannelFuture aggregateFuture; - private final Set pendingFutures; + private final Set pendingFutures; - public ChannelFutureAggregator(ChannelFuture aggregateFuture) - { - this.aggregateFuture = aggregateFuture; - pendingFutures = new HashSet(); - } + public ChannelFutureAggregator(ChannelFuture aggregateFuture) { + this.aggregateFuture = aggregateFuture; + pendingFutures = new HashSet(); + } - public void addFuture(ChannelFuture future) - { - pendingFutures.add(future); - future.addListener(this); - } + public void addFuture(ChannelFuture future) { + pendingFutures.add(future); + future.addListener(this); + } - public synchronized void operationComplete(ChannelFuture future) throws Exception - { - if (future.isCancelled()) - { - // TODO: what should the correct behaviour be when a fragment is cancelled? - // cancel all outstanding fragments and cancel the aggregate? - return; - } + public synchronized void operationComplete(ChannelFuture future) + throws Exception { + if (future.isCancelled()) { + // TODO: what should the correct behaviour be when a fragment is cancelled? + // cancel all outstanding fragments and cancel the aggregate? + return; + } - pendingFutures.remove(future); - if (!future.isSuccess()) - { - aggregateFuture.setFailure(future.getCause()); - for (ChannelFuture pendingFuture : pendingFutures) - { - pendingFuture.cancel(); - } - return; - } + pendingFutures.remove(future); + if (!future.isSuccess()) { + aggregateFuture.setFailure(future.getCause()); + for (ChannelFuture pendingFuture: pendingFutures) { + pendingFuture.cancel(); + } + return; + } - if (pendingFutures.isEmpty()) - { - aggregateFuture.setSuccess(); - } - } + if (pendingFutures.isEmpty()) { + aggregateFuture.setSuccess(); + } + } } \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java b/src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java index 2e20f381bcf..6e7ad944baa 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/DefaultTunnelIdGenerator.java @@ -27,27 +27,23 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class DefaultTunnelIdGenerator implements TunnelIdGenerator -{ - - private SecureRandom generator; - - public DefaultTunnelIdGenerator() - { - this(new SecureRandom()); - } - - public DefaultTunnelIdGenerator(SecureRandom generator) - { - this.generator = generator; - } - - public synchronized String generateId() - { - // synchronized to ensure that this code is thread safe. The Sun - // standard implementations seem to be synchronized or lock free - // but are not documented as guaranteeing this - return Integer.toHexString(generator.nextInt()); - } +public class DefaultTunnelIdGenerator implements TunnelIdGenerator { + + private SecureRandom generator; + + public DefaultTunnelIdGenerator() { + this(new SecureRandom()); + } + + public DefaultTunnelIdGenerator(SecureRandom generator) { + this.generator = generator; + } + + public synchronized String generateId() { + // synchronized to ensure that this code is thread safe. The Sun + // standard implementations seem to be synchronized or lock free + // but are not documented as guaranteeing this + return Integer.toHexString(generator.nextInt()); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java index 07a61de8da6..c6c713cb3d2 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannel.java @@ -40,78 +40,78 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class HttpTunnelAcceptedChannel extends AbstractChannel implements SocketChannel, HttpTunnelAcceptedChannelReceiver -{ - private final HttpTunnelAcceptedChannelConfig config; - - private final HttpTunnelAcceptedChannelSink sink; - - private final InetSocketAddress remoteAddress; - - protected HttpTunnelAcceptedChannel(HttpTunnelServerChannel parent, ChannelFactory factory, - ChannelPipeline pipeline, HttpTunnelAcceptedChannelSink sink, InetSocketAddress remoteAddress, HttpTunnelAcceptedChannelConfig config) - { - super(parent, factory, pipeline, sink); - this.config = config; - this.sink = sink; - this.remoteAddress = remoteAddress; - fireChannelOpen(this); - fireChannelBound(this, getLocalAddress()); - fireChannelConnected(this, getRemoteAddress()); - } - - public SocketChannelConfig getConfig() - { - return config; - } - - public InetSocketAddress getLocalAddress() - { - - return ((HttpTunnelServerChannel) getParent()).getLocalAddress(); - } - - public InetSocketAddress getRemoteAddress() - { - return remoteAddress; - } - - public boolean isBound() - { - return sink.isActive(); - } - - public boolean isConnected() - { - return sink.isActive(); - } - - public void clientClosed() - { - this.setClosed(); - Channels.fireChannelClosed(this); - } - - public void dataReceived(ChannelBuffer data) - { - Channels.fireMessageReceived(this, data); - } - - public void updateInterestOps(SaturationStateChange transition) { - switch(transition) { - case SATURATED: fireWriteEnabled(false); break; - case DESATURATED: fireWriteEnabled(true); break; - case NO_CHANGE: break; - } - } - - private void fireWriteEnabled(boolean enabled) { - int ops = OP_READ; - if(!enabled) { - ops |= OP_WRITE; - } - - setInterestOpsNow(ops); - Channels.fireChannelInterestChanged(this); - } +class HttpTunnelAcceptedChannel extends AbstractChannel implements + SocketChannel, HttpTunnelAcceptedChannelReceiver { + private final HttpTunnelAcceptedChannelConfig config; + + private final HttpTunnelAcceptedChannelSink sink; + + private final InetSocketAddress remoteAddress; + + protected HttpTunnelAcceptedChannel(HttpTunnelServerChannel parent, + ChannelFactory factory, ChannelPipeline pipeline, + HttpTunnelAcceptedChannelSink sink, + InetSocketAddress remoteAddress, + HttpTunnelAcceptedChannelConfig config) { + super(parent, factory, pipeline, sink); + this.config = config; + this.sink = sink; + this.remoteAddress = remoteAddress; + fireChannelOpen(this); + fireChannelBound(this, getLocalAddress()); + fireChannelConnected(this, getRemoteAddress()); + } + + public SocketChannelConfig getConfig() { + return config; + } + + public InetSocketAddress getLocalAddress() { + + return ((HttpTunnelServerChannel) getParent()).getLocalAddress(); + } + + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isBound() { + return sink.isActive(); + } + + public boolean isConnected() { + return sink.isActive(); + } + + public void clientClosed() { + this.setClosed(); + Channels.fireChannelClosed(this); + } + + public void dataReceived(ChannelBuffer data) { + Channels.fireMessageReceived(this, data); + } + + public void updateInterestOps(SaturationStateChange transition) { + switch (transition) { + case SATURATED: + fireWriteEnabled(false); + break; + case DESATURATED: + fireWriteEnabled(true); + break; + case NO_CHANGE: + break; + } + } + + private void fireWriteEnabled(boolean enabled) { + int ops = OP_READ; + if (!enabled) { + ops |= OP_WRITE; + } + + setInterestOpsNow(ops); + Channels.fireChannelInterestChanged(this); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java index 9f480e4dc28..e42e6b7c5b7 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelConfig.java @@ -26,105 +26,90 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelAcceptedChannelConfig extends HttpTunnelChannelConfig -{ - - private static final int SO_LINGER_DISABLED = -1; - - private static final int FAKE_SEND_BUFFER_SIZE = 16 * 1024; - - private static final int FAKE_RECEIVE_BUFFER_SIZE = 16 * 1024; - - // based on the values in RFC 791 - private static final int DEFAULT_TRAFFIC_CLASS = 0; - - @Override - public boolean isTcpNoDelay() - { - return true; - } - - @Override - public void setTcpNoDelay(boolean tcpNoDelay) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public int getSoLinger() - { - return SO_LINGER_DISABLED; - } - - @Override - public void setSoLinger(int soLinger) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public int getSendBufferSize() - { - return FAKE_SEND_BUFFER_SIZE; - } - - @Override - public void setSendBufferSize(int sendBufferSize) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public int getReceiveBufferSize() - { - return FAKE_RECEIVE_BUFFER_SIZE; - } - - @Override - public void setReceiveBufferSize(int receiveBufferSize) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public boolean isKeepAlive() - { - return true; - } - - @Override - public void setKeepAlive(boolean keepAlive) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public int getTrafficClass() - { - return DEFAULT_TRAFFIC_CLASS; - } - - @Override - public void setTrafficClass(int trafficClass) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public boolean isReuseAddress() - { - return false; - } - - @Override - public void setReuseAddress(boolean reuseAddress) - { - // we do not allow the value to be changed, as it will not be honoured - } - - @Override - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) - { - // we do not allow the value to be changed, as it will not be honoured - } +public class HttpTunnelAcceptedChannelConfig extends HttpTunnelChannelConfig { + + private static final int SO_LINGER_DISABLED = -1; + + private static final int FAKE_SEND_BUFFER_SIZE = 16 * 1024; + + private static final int FAKE_RECEIVE_BUFFER_SIZE = 16 * 1024; + + // based on the values in RFC 791 + private static final int DEFAULT_TRAFFIC_CLASS = 0; + + @Override + public boolean isTcpNoDelay() { + return true; + } + + @Override + public void setTcpNoDelay(boolean tcpNoDelay) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getSoLinger() { + return SO_LINGER_DISABLED; + } + + @Override + public void setSoLinger(int soLinger) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getSendBufferSize() { + return FAKE_SEND_BUFFER_SIZE; + } + + @Override + public void setSendBufferSize(int sendBufferSize) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getReceiveBufferSize() { + return FAKE_RECEIVE_BUFFER_SIZE; + } + + @Override + public void setReceiveBufferSize(int receiveBufferSize) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public boolean isKeepAlive() { + return true; + } + + @Override + public void setKeepAlive(boolean keepAlive) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public int getTrafficClass() { + return DEFAULT_TRAFFIC_CLASS; + } + + @Override + public void setTrafficClass(int trafficClass) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public boolean isReuseAddress() { + return false; + } + + @Override + public void setReuseAddress(boolean reuseAddress) { + // we do not allow the value to be changed, as it will not be honoured + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + // we do not allow the value to be changed, as it will not be honoured + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java index 5b5014f15a2..89198449ece 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelFactory.java @@ -26,9 +26,9 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -interface HttpTunnelAcceptedChannelFactory -{ - public HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId, InetSocketAddress remoteAddress); +interface HttpTunnelAcceptedChannelFactory { + public HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId, + InetSocketAddress remoteAddress); - public String generateTunnelId(); + public String generateTunnelId(); } \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java index 6ff8592bafd..061930abbfb 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelReceiver.java @@ -25,13 +25,12 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -interface HttpTunnelAcceptedChannelReceiver -{ +interface HttpTunnelAcceptedChannelReceiver { - public void updateInterestOps(SaturationStateChange transition); - - public void dataReceived(ChannelBuffer data); + public void updateInterestOps(SaturationStateChange transition); - public void clientClosed(); + public void dataReceived(ChannelBuffer data); + + public void clientClosed(); } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java index 1f947d06630..8a3a131d21c 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java @@ -37,97 +37,95 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class HttpTunnelAcceptedChannelSink extends AbstractChannelSink -{ - - private final SaturationManager saturationManager; - private final ServerMessageSwitchDownstreamInterface messageSwitch; - - private final String tunnelId; - - private AtomicBoolean active = new AtomicBoolean(false); - private HttpTunnelAcceptedChannelConfig config; - - public HttpTunnelAcceptedChannelSink(ServerMessageSwitchDownstreamInterface messageSwitch, String tunnelId, HttpTunnelAcceptedChannelConfig config) - { - this.messageSwitch = messageSwitch; - this.tunnelId = tunnelId; - this.config = config; - this.saturationManager = new SaturationManager(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark()); - } - - public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception - { - if (e instanceof MessageEvent) - { - handleMessageEvent((MessageEvent) e); - } - else if (e instanceof ChannelStateEvent) - { - handleStateEvent((ChannelStateEvent) e); - } - } - - private void handleMessageEvent(MessageEvent ev) - { - if (!(ev.getMessage() instanceof ChannelBuffer)) - { - throw new IllegalArgumentException("Attempt to send data which is not a ChannelBuffer:" + ev.getMessage()); - } - - final HttpTunnelAcceptedChannelReceiver channel = (HttpTunnelAcceptedChannelReceiver) ev.getChannel(); - final ChannelBuffer message = (ChannelBuffer) ev.getMessage(); - final int messageSize = message.readableBytes(); - final ChannelFuture future = ev.getFuture(); - - saturationManager.updateThresholds(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark()); - channel.updateInterestOps(saturationManager.queueSizeChanged(messageSize)); - future.addListener(new ChannelFutureListener() - { - - @Override - public void operationComplete(ChannelFuture future) throws Exception - { - channel.updateInterestOps(saturationManager.queueSizeChanged(-messageSize)); - } - }); - messageSwitch.routeOutboundData(tunnelId, message, future); - } - - private void handleStateEvent(ChannelStateEvent ev) - { - /* TODO: as any of disconnect, unbind or close destroys a server - channel, should we fire all three events always? */ - Channel owner = ev.getChannel(); - switch (ev.getState()) - { - case OPEN : - if (Boolean.FALSE.equals(ev.getValue())) - { - messageSwitch.serverCloseTunnel(tunnelId); - active.set(false); - Channels.fireChannelClosed(owner); +class HttpTunnelAcceptedChannelSink extends AbstractChannelSink { + + private final SaturationManager saturationManager; + + private final ServerMessageSwitchDownstreamInterface messageSwitch; + + private final String tunnelId; + + private AtomicBoolean active = new AtomicBoolean(false); + + private HttpTunnelAcceptedChannelConfig config; + + public HttpTunnelAcceptedChannelSink( + ServerMessageSwitchDownstreamInterface messageSwitch, + String tunnelId, HttpTunnelAcceptedChannelConfig config) { + this.messageSwitch = messageSwitch; + this.tunnelId = tunnelId; + this.config = config; + this.saturationManager = + new SaturationManager(config.getWriteBufferLowWaterMark(), + config.getWriteBufferHighWaterMark()); + } + + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) + throws Exception { + if (e instanceof MessageEvent) { + handleMessageEvent((MessageEvent) e); + } else if (e instanceof ChannelStateEvent) { + handleStateEvent((ChannelStateEvent) e); + } + } + + private void handleMessageEvent(MessageEvent ev) { + if (!(ev.getMessage() instanceof ChannelBuffer)) { + throw new IllegalArgumentException( + "Attempt to send data which is not a ChannelBuffer:" + + ev.getMessage()); + } + + final HttpTunnelAcceptedChannelReceiver channel = + (HttpTunnelAcceptedChannelReceiver) ev.getChannel(); + final ChannelBuffer message = (ChannelBuffer) ev.getMessage(); + final int messageSize = message.readableBytes(); + final ChannelFuture future = ev.getFuture(); + + saturationManager.updateThresholds(config.getWriteBufferLowWaterMark(), + config.getWriteBufferHighWaterMark()); + channel.updateInterestOps(saturationManager + .queueSizeChanged(messageSize)); + future.addListener(new ChannelFutureListener() { + + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + channel.updateInterestOps(saturationManager + .queueSizeChanged(-messageSize)); + } + }); + messageSwitch.routeOutboundData(tunnelId, message, future); + } + + private void handleStateEvent(ChannelStateEvent ev) { + /* TODO: as any of disconnect, unbind or close destroys a server + channel, should we fire all three events always? */ + Channel owner = ev.getChannel(); + switch (ev.getState()) { + case OPEN: + if (Boolean.FALSE.equals(ev.getValue())) { + messageSwitch.serverCloseTunnel(tunnelId); + active.set(false); + Channels.fireChannelClosed(owner); } break; - case BOUND : - if (ev.getValue() == null) - { - messageSwitch.serverCloseTunnel(tunnelId); - active.set(false); - Channels.fireChannelUnbound(owner); + case BOUND: + if (ev.getValue() == null) { + messageSwitch.serverCloseTunnel(tunnelId); + active.set(false); + Channels.fireChannelUnbound(owner); } - case CONNECTED : - if (ev.getValue() == null) - { - messageSwitch.serverCloseTunnel(tunnelId); - active.set(false); - Channels.fireChannelDisconnected(owner); + case CONNECTED: + if (ev.getValue() == null) { + messageSwitch.serverCloseTunnel(tunnelId); + active.set(false); + Channels.fireChannelDisconnected(owner); } - } - } + } + } - public boolean isActive() - { - return active.get(); - } + public boolean isActive() { + return active.get(); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java index a9867ce5d0a..a86ff2decf6 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelChannelConfig.java @@ -37,128 +37,117 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public abstract class HttpTunnelChannelConfig extends DefaultChannelConfig implements SocketChannelConfig -{ - - /** - * The minimum value that the high water mark may be set to, in addition to the - * constraint that the high water mark must be strictly greater than the low - * water mark. - */ - public static final int MIN_HIGH_WATER_MARK = 1; - - /** - * The minimum value that the low water mark may be set to. - */ - public static final int MIN_LOW_WATER_MARK = 0; - - /** - * The default level for the write buffer's high water mark, presently set to - * 64KByte. - */ - public static final int DEFAULT_HIGH_WATER_MARK = 64 * 1024; - - /** - * The default level for the write buffer's low water mark, presently set to - * 32KByte. - */ - public static final int DEFAULT_LOW_WATER_MARK = 32 * 1024; - - static final String HIGH_WATER_MARK_OPTION = "writeBufferhHighWaterMark"; - - static final String LOW_WATER_MARK_OPTION = "writeBufferLowWaterMark"; - - - protected volatile int writeBufferLowWaterMark = DEFAULT_LOW_WATER_MARK; - - protected volatile int writeBufferHighWaterMark = DEFAULT_HIGH_WATER_MARK; - - /** - * @return the current value (in bytes) of the high water mark. - */ - public int getWriteBufferHighWaterMark() - { - return writeBufferHighWaterMark; - } - - /** - * Similarly to {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferHighWaterMark(int) - * NioSocketChannelConfig.setWriteBufferHighWaterMark()}, - * the high water mark refers to the buffer size at which a user of the channel should stop writing. When the - * number of queued bytes exceeds the high water mark, {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWritable()} will - * return false. Once the number of queued bytes falls below the {@link #setWriteBufferLowWaterMark(int) low water mark}, - * {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWritable()} will return true again, indicating that the client - * can begin to send more data. - * - * @param level the number of queued bytes required to flip {@link org.jboss.netty.channel.Channel#isWritable()} to - * false. - * - * @see {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferHighWaterMark(int) NioSocketChannelConfig.setWriteBufferHighWaterMark()} - */ - public void setWriteBufferHighWaterMark(int level) - { - if (level <= writeBufferLowWaterMark) - { - throw new IllegalArgumentException( - "Write buffer high water mark must be strictly greater than the low water mark"); - } - - if (level < MIN_HIGH_WATER_MARK) - { - throw new IllegalArgumentException("Cannot set write buffer high water mark lower than " + MIN_HIGH_WATER_MARK); - } - - writeBufferHighWaterMark = level; - } - - /** - * @return the current value (in bytes) of the low water mark. - */ - public int getWriteBufferLowWaterMark() - { - return writeBufferLowWaterMark; - } - - /** - * The low water mark refers to the "safe" size of the queued byte buffer at which more data can be enqueued. When - * the {@link #setWriteBufferHighWaterMark(int) high water mark} is exceeded, {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWriteable()} - * will return false until the buffer drops below this level. By creating a sufficient gap between the high and low - * water marks, rapid oscillation between "write enabled" and "write disabled" can be avoided. - * - * @see {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferLowWaterMark(int) NioSocketChannelConfig.setWriteBufferLowWaterMark()} - */ - public void setWriteBufferLowWaterMark(int level) - { - if (level >= writeBufferHighWaterMark) - { - throw new IllegalArgumentException( - "Write buffer low water mark must be strictly less than the high water mark"); - } - - if (level < MIN_LOW_WATER_MARK) - { - throw new IllegalArgumentException("Cannot set write buffer low water mark lower than " + MIN_LOW_WATER_MARK); - } - - writeBufferLowWaterMark = level; - } - - @Override - public boolean setOption(String key, Object value) - { - if (HIGH_WATER_MARK_OPTION.equals(key)) - { - setWriteBufferHighWaterMark((Integer) value); - } - else if (LOW_WATER_MARK_OPTION.equals(key)) - { - setWriteBufferLowWaterMark((Integer) value); - } - else - { - return super.setOption(key, value); - } - - return true; - } +public abstract class HttpTunnelChannelConfig extends DefaultChannelConfig + implements SocketChannelConfig { + + /** + * The minimum value that the high water mark may be set to, in addition to the + * constraint that the high water mark must be strictly greater than the low + * water mark. + */ + public static final int MIN_HIGH_WATER_MARK = 1; + + /** + * The minimum value that the low water mark may be set to. + */ + public static final int MIN_LOW_WATER_MARK = 0; + + /** + * The default level for the write buffer's high water mark, presently set to + * 64KByte. + */ + public static final int DEFAULT_HIGH_WATER_MARK = 64 * 1024; + + /** + * The default level for the write buffer's low water mark, presently set to + * 32KByte. + */ + public static final int DEFAULT_LOW_WATER_MARK = 32 * 1024; + + static final String HIGH_WATER_MARK_OPTION = "writeBufferhHighWaterMark"; + + static final String LOW_WATER_MARK_OPTION = "writeBufferLowWaterMark"; + + protected volatile int writeBufferLowWaterMark = DEFAULT_LOW_WATER_MARK; + + protected volatile int writeBufferHighWaterMark = DEFAULT_HIGH_WATER_MARK; + + /** + * @return the current value (in bytes) of the high water mark. + */ + public int getWriteBufferHighWaterMark() { + return writeBufferHighWaterMark; + } + + /** + * Similarly to {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferHighWaterMark(int) + * NioSocketChannelConfig.setWriteBufferHighWaterMark()}, + * the high water mark refers to the buffer size at which a user of the channel should stop writing. When the + * number of queued bytes exceeds the high water mark, {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWritable()} will + * return false. Once the number of queued bytes falls below the {@link #setWriteBufferLowWaterMark(int) low water mark}, + * {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWritable()} will return true again, indicating that the client + * can begin to send more data. + * + * @param level the number of queued bytes required to flip {@link org.jboss.netty.channel.Channel#isWritable()} to + * false. + * + * @see {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferHighWaterMark(int) NioSocketChannelConfig.setWriteBufferHighWaterMark()} + */ + public void setWriteBufferHighWaterMark(int level) { + if (level <= writeBufferLowWaterMark) { + throw new IllegalArgumentException( + "Write buffer high water mark must be strictly greater than the low water mark"); + } + + if (level < MIN_HIGH_WATER_MARK) { + throw new IllegalArgumentException( + "Cannot set write buffer high water mark lower than " + + MIN_HIGH_WATER_MARK); + } + + writeBufferHighWaterMark = level; + } + + /** + * @return the current value (in bytes) of the low water mark. + */ + public int getWriteBufferLowWaterMark() { + return writeBufferLowWaterMark; + } + + /** + * The low water mark refers to the "safe" size of the queued byte buffer at which more data can be enqueued. When + * the {@link #setWriteBufferHighWaterMark(int) high water mark} is exceeded, {@link org.jboss.netty.channel.Channel#isWritable() Channel.isWriteable()} + * will return false until the buffer drops below this level. By creating a sufficient gap between the high and low + * water marks, rapid oscillation between "write enabled" and "write disabled" can be avoided. + * + * @see {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferLowWaterMark(int) NioSocketChannelConfig.setWriteBufferLowWaterMark()} + */ + public void setWriteBufferLowWaterMark(int level) { + if (level >= writeBufferHighWaterMark) { + throw new IllegalArgumentException( + "Write buffer low water mark must be strictly less than the high water mark"); + } + + if (level < MIN_LOW_WATER_MARK) { + throw new IllegalArgumentException( + "Cannot set write buffer low water mark lower than " + + MIN_LOW_WATER_MARK); + } + + writeBufferLowWaterMark = level; + } + + @Override + public boolean setOption(String key, Object value) { + if (HIGH_WATER_MARK_OPTION.equals(key)) { + setWriteBufferHighWaterMark((Integer) value); + } else if (LOW_WATER_MARK_OPTION.equals(key)) { + setWriteBufferLowWaterMark((Integer) value); + } else { + return super.setOption(key, value); + } + + return true; + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java index aa6b9f2609b..8dfe74c1878 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannel.java @@ -45,341 +45,333 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelClientChannel extends AbstractChannel implements SocketChannel -{ +public class HttpTunnelClientChannel extends AbstractChannel implements + SocketChannel { - private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HttpTunnelClientChannel.class); + private static final InternalLogger LOG = InternalLoggerFactory + .getInstance(HttpTunnelClientChannel.class); - private final HttpTunnelClientChannelConfig config; + private final HttpTunnelClientChannelConfig config; - private final SocketChannel sendChannel; + private final SocketChannel sendChannel; - private final SocketChannel pollChannel; + private final SocketChannel pollChannel; - private volatile String tunnelId; + private volatile String tunnelId; - private volatile ChannelFuture connectFuture; + private volatile ChannelFuture connectFuture; - private volatile boolean connected; - - private volatile boolean bound; - - volatile InetSocketAddress serverAddress; - - private volatile String serverHostName; - - private final WorkerCallbacks callbackProxy; - - private final SaturationManager saturationManager; - - /** - * @see {@link HttpTunnelClientChannelFactory#newChannel(ChannelPipeline)} - */ - protected HttpTunnelClientChannel(ChannelFactory factory, ChannelPipeline pipeline, - HttpTunnelClientChannelSink sink, ClientSocketChannelFactory outboundFactory, ChannelGroup realConnections) - { - super(null, factory, pipeline, sink); - - this.callbackProxy = new WorkerCallbacks(); - - sendChannel = outboundFactory.newChannel(createSendPipeline()); - pollChannel = outboundFactory.newChannel(createPollPipeline()); - config = new HttpTunnelClientChannelConfig(sendChannel.getConfig(), pollChannel.getConfig()); - saturationManager = new SaturationManager(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark()); - serverAddress = null; - - realConnections.add(sendChannel); - realConnections.add(pollChannel); - - Channels.fireChannelOpen(this); - } - - public HttpTunnelClientChannelConfig getConfig() - { - return config; - } - - public boolean isBound() - { - return bound; - } - - public boolean isConnected() - { - return connected; - } - - public InetSocketAddress getLocalAddress() - { - return sendChannel.getLocalAddress(); - } - - public InetSocketAddress getRemoteAddress() - { - return serverAddress; - } - - void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress) - { - this.connectFuture = connectFuture; - /* if we are using a proxy, the remoteAddress is swapped here for the address of the proxy. - * The send and poll channels can later ask for the correct server address using - * getServerHostName(). - */ - serverAddress = remoteAddress; - - SocketAddress connectTarget; - if (config.getProxyAddress() != null) - { - connectTarget = config.getProxyAddress(); - } - else - { - connectTarget = remoteAddress; - } - - Channels.connect(sendChannel, connectTarget); - } - - void onDisconnectRequest(final ChannelFuture disconnectFuture) - { - ChannelFutureListener disconnectListener = new ConsolidatingFutureListener(disconnectFuture, 2); - sendChannel.disconnect().addListener(disconnectListener); - pollChannel.disconnect().addListener(disconnectListener); - - disconnectFuture.addListener(new ChannelFutureListener() - { - public void operationComplete(ChannelFuture future) throws Exception - { - serverAddress = null; - } - }); - } - - void onBindRequest(InetSocketAddress localAddress, final ChannelFuture bindFuture) - { - ChannelFutureListener bindListener = new ConsolidatingFutureListener(bindFuture, 2); - // bind the send channel to the specified local address, and the poll channel to - // an ephemeral port on the same interface as the send channel - sendChannel.bind(localAddress).addListener(bindListener); - InetSocketAddress pollBindAddress; - if (localAddress.isUnresolved()) - { - pollBindAddress = InetSocketAddress.createUnresolved(localAddress.getHostName(), 0); - } - else - { - pollBindAddress = new InetSocketAddress(localAddress.getAddress(), 0); - } - pollChannel.bind(pollBindAddress).addListener(bindListener); - } - - void onUnbindRequest(final ChannelFuture unbindFuture) - { - ChannelFutureListener unbindListener = new ConsolidatingFutureListener(unbindFuture, 2); - sendChannel.unbind().addListener(unbindListener); - pollChannel.unbind().addListener(unbindListener); - } - - void onCloseRequest(final ChannelFuture closeFuture) - { - ChannelFutureListener closeListener = new CloseConsolidatingFutureListener(closeFuture, 2); - sendChannel.close().addListener(closeListener); - pollChannel.close().addListener(closeListener); - } - - private ChannelPipeline createSendPipeline() - { - ChannelPipeline pipeline = Channels.pipeline(); - - pipeline.addLast("reqencoder", new HttpRequestEncoder()); // downstream - pipeline.addLast("respdecoder", new HttpResponseDecoder()); // upstream - pipeline.addLast("aggregator", new HttpChunkAggregator(HttpTunnelMessageUtils.MAX_BODY_SIZE)); // upstream - pipeline.addLast("sendHandler", new HttpTunnelClientSendHandler(callbackProxy)); // both - pipeline.addLast("writeFragmenter", new WriteFragmenter(HttpTunnelMessageUtils.MAX_BODY_SIZE)); - - return pipeline; - } - - private ChannelPipeline createPollPipeline() - { - ChannelPipeline pipeline = Channels.pipeline(); - - pipeline.addLast("reqencoder", new HttpRequestEncoder()); // downstream - pipeline.addLast("respdecoder", new HttpResponseDecoder()); // upstream - pipeline.addLast("aggregator", new HttpChunkAggregator(HttpTunnelMessageUtils.MAX_BODY_SIZE)); // upstream - pipeline.addLast(HttpTunnelClientPollHandler.NAME, new HttpTunnelClientPollHandler(callbackProxy)); // both - - return pipeline; - } - - private void setTunnelIdForPollChannel() - { - HttpTunnelClientPollHandler pollHandler = pollChannel.getPipeline().get(HttpTunnelClientPollHandler.class); - pollHandler.setTunnelId(tunnelId); - } - - void sendData(final MessageEvent e) - { - saturationManager.updateThresholds(config.getWriteBufferLowWaterMark(), - config.getWriteBufferHighWaterMark()); - final ChannelFuture originalFuture = e.getFuture(); - final ChannelBuffer message = (ChannelBuffer) e.getMessage(); - final int messageSize = message.readableBytes(); - updateSaturationStatus(messageSize); - Channels.write(sendChannel, e.getMessage()).addListener(new ChannelFutureListener() - { - public void operationComplete(ChannelFuture future) throws Exception - { - if (future.isSuccess()) - { - originalFuture.setSuccess(); + private volatile boolean connected; + + private volatile boolean bound; + + volatile InetSocketAddress serverAddress; + + private volatile String serverHostName; + + private final WorkerCallbacks callbackProxy; + + private final SaturationManager saturationManager; + + /** + * @see {@link HttpTunnelClientChannelFactory#newChannel(ChannelPipeline)} + */ + protected HttpTunnelClientChannel(ChannelFactory factory, + ChannelPipeline pipeline, HttpTunnelClientChannelSink sink, + ClientSocketChannelFactory outboundFactory, + ChannelGroup realConnections) { + super(null, factory, pipeline, sink); + + this.callbackProxy = new WorkerCallbacks(); + + sendChannel = outboundFactory.newChannel(createSendPipeline()); + pollChannel = outboundFactory.newChannel(createPollPipeline()); + config = + new HttpTunnelClientChannelConfig(sendChannel.getConfig(), + pollChannel.getConfig()); + saturationManager = + new SaturationManager(config.getWriteBufferLowWaterMark(), + config.getWriteBufferHighWaterMark()); + serverAddress = null; + + realConnections.add(sendChannel); + realConnections.add(pollChannel); + + Channels.fireChannelOpen(this); + } + + public HttpTunnelClientChannelConfig getConfig() { + return config; + } + + public boolean isBound() { + return bound; + } + + public boolean isConnected() { + return connected; + } + + public InetSocketAddress getLocalAddress() { + return sendChannel.getLocalAddress(); + } + + public InetSocketAddress getRemoteAddress() { + return serverAddress; + } + + void onConnectRequest(ChannelFuture connectFuture, + InetSocketAddress remoteAddress) { + this.connectFuture = connectFuture; + /* if we are using a proxy, the remoteAddress is swapped here for the address of the proxy. + * The send and poll channels can later ask for the correct server address using + * getServerHostName(). + */ + serverAddress = remoteAddress; + + SocketAddress connectTarget; + if (config.getProxyAddress() != null) { + connectTarget = config.getProxyAddress(); + } else { + connectTarget = remoteAddress; + } + + Channels.connect(sendChannel, connectTarget); + } + + void onDisconnectRequest(final ChannelFuture disconnectFuture) { + ChannelFutureListener disconnectListener = + new ConsolidatingFutureListener(disconnectFuture, 2); + sendChannel.disconnect().addListener(disconnectListener); + pollChannel.disconnect().addListener(disconnectListener); + + disconnectFuture.addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) + throws Exception { + serverAddress = null; + } + }); + } + + void onBindRequest(InetSocketAddress localAddress, + final ChannelFuture bindFuture) { + ChannelFutureListener bindListener = + new ConsolidatingFutureListener(bindFuture, 2); + // bind the send channel to the specified local address, and the poll channel to + // an ephemeral port on the same interface as the send channel + sendChannel.bind(localAddress).addListener(bindListener); + InetSocketAddress pollBindAddress; + if (localAddress.isUnresolved()) { + pollBindAddress = + InetSocketAddress.createUnresolved( + localAddress.getHostName(), 0); + } else { + pollBindAddress = + new InetSocketAddress(localAddress.getAddress(), 0); + } + pollChannel.bind(pollBindAddress).addListener(bindListener); + } + + void onUnbindRequest(final ChannelFuture unbindFuture) { + ChannelFutureListener unbindListener = + new ConsolidatingFutureListener(unbindFuture, 2); + sendChannel.unbind().addListener(unbindListener); + pollChannel.unbind().addListener(unbindListener); + } + + void onCloseRequest(final ChannelFuture closeFuture) { + ChannelFutureListener closeListener = + new CloseConsolidatingFutureListener(closeFuture, 2); + sendChannel.close().addListener(closeListener); + pollChannel.close().addListener(closeListener); + } + + private ChannelPipeline createSendPipeline() { + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("reqencoder", new HttpRequestEncoder()); // downstream + pipeline.addLast("respdecoder", new HttpResponseDecoder()); // upstream + pipeline.addLast("aggregator", new HttpChunkAggregator( + HttpTunnelMessageUtils.MAX_BODY_SIZE)); // upstream + pipeline.addLast("sendHandler", new HttpTunnelClientSendHandler( + callbackProxy)); // both + pipeline.addLast("writeFragmenter", new WriteFragmenter( + HttpTunnelMessageUtils.MAX_BODY_SIZE)); + + return pipeline; + } + + private ChannelPipeline createPollPipeline() { + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addLast("reqencoder", new HttpRequestEncoder()); // downstream + pipeline.addLast("respdecoder", new HttpResponseDecoder()); // upstream + pipeline.addLast("aggregator", new HttpChunkAggregator( + HttpTunnelMessageUtils.MAX_BODY_SIZE)); // upstream + pipeline.addLast(HttpTunnelClientPollHandler.NAME, + new HttpTunnelClientPollHandler(callbackProxy)); // both + + return pipeline; + } + + private void setTunnelIdForPollChannel() { + HttpTunnelClientPollHandler pollHandler = + pollChannel.getPipeline() + .get(HttpTunnelClientPollHandler.class); + pollHandler.setTunnelId(tunnelId); + } + + void sendData(final MessageEvent e) { + saturationManager.updateThresholds(config.getWriteBufferLowWaterMark(), + config.getWriteBufferHighWaterMark()); + final ChannelFuture originalFuture = e.getFuture(); + final ChannelBuffer message = (ChannelBuffer) e.getMessage(); + final int messageSize = message.readableBytes(); + updateSaturationStatus(messageSize); + Channels.write(sendChannel, e.getMessage()).addListener( + new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) + throws Exception { + if (future.isSuccess()) { + originalFuture.setSuccess(); + } else { + originalFuture.setFailure(future.getCause()); + } + updateSaturationStatus(-messageSize); + } + }); + } + + private void updateSaturationStatus(int queueSizeDelta) { + SaturationStateChange transition = + saturationManager.queueSizeChanged(queueSizeDelta); + switch (transition) { + case SATURATED: + fireWriteEnabled(false); + break; + case DESATURATED: + fireWriteEnabled(true); + break; + case NO_CHANGE: + break; + } + } + + private void fireWriteEnabled(boolean enabled) { + int ops = OP_READ; + if (!enabled) { + ops |= OP_WRITE; + } + + setInterestOpsNow(ops); + Channels.fireChannelInterestChanged(this); + } + + private class ConsolidatingFutureListener implements ChannelFutureListener { + + private final ChannelFuture completionFuture; + + private final AtomicInteger eventsLeft; + + public ConsolidatingFutureListener(ChannelFuture completionFuture, + int numToConsolidate) { + this.completionFuture = completionFuture; + eventsLeft = new AtomicInteger(numToConsolidate); + } + + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + futureFailed(future); + } else { + if (eventsLeft.decrementAndGet() == 0) { + allFuturesComplete(); + } + } + } + + protected void allFuturesComplete() { + completionFuture.setSuccess(); + } + + protected void futureFailed(ChannelFuture future) { + completionFuture.setFailure(future.getCause()); + } + } + + /** + * Close futures are a special case, as marking them as successful or failed has no effect. + * Instead, we must call setClosed() on the channel itself, once all the child channels are + * closed or if we fail to close them for whatever reason. + */ + private final class CloseConsolidatingFutureListener extends + ConsolidatingFutureListener { + + public CloseConsolidatingFutureListener(ChannelFuture completionFuture, + int numToConsolidate) { + super(completionFuture, numToConsolidate); + } + + @Override + protected void futureFailed(ChannelFuture future) { + LOG.warn("Failed to close one of the child channels of tunnel " + + tunnelId); + HttpTunnelClientChannel.this.setClosed(); + } + + @Override + protected void allFuturesComplete() { + if (LOG.isDebugEnabled()) { + LOG.debug("Tunnel " + tunnelId + " closed"); } - else - { - originalFuture.setFailure(future.getCause()); + HttpTunnelClientChannel.this.setClosed(); + } + + } + + /** + * Contains the implementing methods of HttpTunnelClientWorkerOwner, so that these are hidden + * from the public API. + */ + private class WorkerCallbacks implements HttpTunnelClientWorkerOwner { + + public void onConnectRequest(ChannelFuture connectFuture, + InetSocketAddress remoteAddress) { + HttpTunnelClientChannel.this.onConnectRequest(connectFuture, + remoteAddress); + } + + public void onTunnelOpened(String tunnelId) { + HttpTunnelClientChannel.this.tunnelId = tunnelId; + setTunnelIdForPollChannel(); + Channels.connect(pollChannel, sendChannel.getRemoteAddress()); + } + + public void fullyEstablished() { + if (!bound) { + bound = true; + Channels.fireChannelBound(HttpTunnelClientChannel.this, + getLocalAddress()); } - updateSaturationStatus(-messageSize); - } - }); - } - - private void updateSaturationStatus(int queueSizeDelta) { - SaturationStateChange transition = saturationManager.queueSizeChanged(queueSizeDelta); - switch(transition) { - case SATURATED: fireWriteEnabled(false); break; - case DESATURATED: fireWriteEnabled(true); break; - case NO_CHANGE: break; - } - } - - private void fireWriteEnabled(boolean enabled) { - int ops = OP_READ; - if(!enabled) { - ops |= OP_WRITE; - } - - setInterestOpsNow(ops); - Channels.fireChannelInterestChanged(this); - } - - private class ConsolidatingFutureListener implements ChannelFutureListener - { - - private final ChannelFuture completionFuture; - - private final AtomicInteger eventsLeft; - - public ConsolidatingFutureListener(ChannelFuture completionFuture, int numToConsolidate) - { - this.completionFuture = completionFuture; - eventsLeft = new AtomicInteger(numToConsolidate); - } - - public void operationComplete(ChannelFuture future) throws Exception - { - if (!future.isSuccess()) - { - futureFailed(future); - } - else - { - if (eventsLeft.decrementAndGet() == 0) - { - allFuturesComplete(); + + connected = true; + connectFuture.setSuccess(); + Channels.fireChannelConnected(HttpTunnelClientChannel.this, + getRemoteAddress()); + } + + public void onMessageReceived(ChannelBuffer content) { + Channels.fireMessageReceived(HttpTunnelClientChannel.this, content); + } + + public String getServerHostName() { + if (serverHostName == null) { + serverHostName = + HttpTunnelMessageUtils + .convertToHostString(serverAddress); } - } - } - - protected void allFuturesComplete() - { - completionFuture.setSuccess(); - } - - protected void futureFailed(ChannelFuture future) - { - completionFuture.setFailure(future.getCause()); - } - } - - /** - * Close futures are a special case, as marking them as successful or failed has no effect. - * Instead, we must call setClosed() on the channel itself, once all the child channels are - * closed or if we fail to close them for whatever reason. - */ - private final class CloseConsolidatingFutureListener extends ConsolidatingFutureListener - { - - public CloseConsolidatingFutureListener(ChannelFuture completionFuture, int numToConsolidate) - { - super(completionFuture, numToConsolidate); - } - - @Override - protected void futureFailed(ChannelFuture future) - { - LOG.warn("Failed to close one of the child channels of tunnel " + tunnelId); - HttpTunnelClientChannel.this.setClosed(); - } - - @Override - protected void allFuturesComplete() - { - if (LOG.isDebugEnabled()) - { - LOG.debug("Tunnel " + tunnelId + " closed"); - } - HttpTunnelClientChannel.this.setClosed(); - } - - } - - /** - * Contains the implementing methods of HttpTunnelClientWorkerOwner, so that these are hidden - * from the public API. - */ - private class WorkerCallbacks implements HttpTunnelClientWorkerOwner - { - - public void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress) - { - HttpTunnelClientChannel.this.onConnectRequest(connectFuture, remoteAddress); - } - - public void onTunnelOpened(String tunnelId) - { - HttpTunnelClientChannel.this.tunnelId = tunnelId; - setTunnelIdForPollChannel(); - Channels.connect(pollChannel, sendChannel.getRemoteAddress()); - } - - public void fullyEstablished() - { - if (!bound) - { - bound = true; - Channels.fireChannelBound(HttpTunnelClientChannel.this, getLocalAddress()); - } - - connected = true; - connectFuture.setSuccess(); - Channels.fireChannelConnected(HttpTunnelClientChannel.this, getRemoteAddress()); - } - - public void onMessageReceived(ChannelBuffer content) - { - Channels.fireMessageReceived(HttpTunnelClientChannel.this, content); - } - - public String getServerHostName() - { - if (serverHostName == null) - { - serverHostName = HttpTunnelMessageUtils.convertToHostString(serverAddress); - } - - return serverHostName; - } - - } + + return serverHostName; + } + + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java index 0088c140d78..59b4271a159 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfig.java @@ -39,142 +39,123 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelClientChannelConfig extends HttpTunnelChannelConfig -{ - - static final String PROXY_ADDRESS_OPTION = "proxyAddress"; - - private final SocketChannelConfig sendChannelConfig; - - private final SocketChannelConfig pollChannelConfig; - - private volatile SocketAddress proxyAddress; - - HttpTunnelClientChannelConfig(SocketChannelConfig sendChannelConfig, SocketChannelConfig pollChannelConfig) - { - this.sendChannelConfig = sendChannelConfig; - this.pollChannelConfig = pollChannelConfig; - } - - /* HTTP TUNNEL SPECIFIC CONFIGURATION */ - // TODO Support all options in the old tunnel (see HttpTunnelingSocketChannelConfig) - // Mostly SSL, virtual host, and URL prefix - @Override - public boolean setOption(String key, Object value) - { - if (PROXY_ADDRESS_OPTION.equals(key)) - { - setProxyAddress((SocketAddress) value); - } - else - { - return super.setOption(key, value); - } - - return true; - } - - /** - * @return the address of the http proxy. If this is null, then no proxy - * should be used. - */ - public SocketAddress getProxyAddress() - { - return proxyAddress; - } - - /** - * Specify a proxy to be used for the http tunnel. If this is null, then - * no proxy should be used, otherwise this should be a directly accessible IPv4/IPv6 - * address and port. - */ - public void setProxyAddress(SocketAddress proxyAddress) - { - this.proxyAddress = proxyAddress; - } - - /* GENERIC SOCKET CHANNEL CONFIGURATION */ - - public int getReceiveBufferSize() - { - return pollChannelConfig.getReceiveBufferSize(); - } - - public int getSendBufferSize() - { - return pollChannelConfig.getSendBufferSize(); - } - - public int getSoLinger() - { - return pollChannelConfig.getSoLinger(); - } - - public int getTrafficClass() - { - return pollChannelConfig.getTrafficClass(); - } - - public boolean isKeepAlive() - { - return pollChannelConfig.isKeepAlive(); - } - - public boolean isReuseAddress() - { - return pollChannelConfig.isReuseAddress(); - } - - public boolean isTcpNoDelay() - { - return pollChannelConfig.isTcpNoDelay(); - } - - public void setKeepAlive(boolean keepAlive) - { - pollChannelConfig.setKeepAlive(keepAlive); - sendChannelConfig.setKeepAlive(keepAlive); - } - - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) - { - pollChannelConfig.setPerformancePreferences(connectionTime, latency, bandwidth); - sendChannelConfig.setPerformancePreferences(connectionTime, latency, bandwidth); - } - - public void setReceiveBufferSize(int receiveBufferSize) - { - pollChannelConfig.setReceiveBufferSize(receiveBufferSize); - sendChannelConfig.setReceiveBufferSize(receiveBufferSize); - } - - public void setReuseAddress(boolean reuseAddress) - { - pollChannelConfig.setReuseAddress(reuseAddress); - sendChannelConfig.setReuseAddress(reuseAddress); - } - - public void setSendBufferSize(int sendBufferSize) - { - pollChannelConfig.setSendBufferSize(sendBufferSize); - sendChannelConfig.setSendBufferSize(sendBufferSize); - } - - public void setSoLinger(int soLinger) - { - pollChannelConfig.setSoLinger(soLinger); - sendChannelConfig.setSoLinger(soLinger); - } - - public void setTcpNoDelay(boolean tcpNoDelay) - { - pollChannelConfig.setTcpNoDelay(true); - sendChannelConfig.setTcpNoDelay(true); - } - - public void setTrafficClass(int trafficClass) - { - pollChannelConfig.setTrafficClass(1); - sendChannelConfig.setTrafficClass(1); - } +public class HttpTunnelClientChannelConfig extends HttpTunnelChannelConfig { + + static final String PROXY_ADDRESS_OPTION = "proxyAddress"; + + private final SocketChannelConfig sendChannelConfig; + + private final SocketChannelConfig pollChannelConfig; + + private volatile SocketAddress proxyAddress; + + HttpTunnelClientChannelConfig(SocketChannelConfig sendChannelConfig, + SocketChannelConfig pollChannelConfig) { + this.sendChannelConfig = sendChannelConfig; + this.pollChannelConfig = pollChannelConfig; + } + + /* HTTP TUNNEL SPECIFIC CONFIGURATION */ + // TODO Support all options in the old tunnel (see HttpTunnelingSocketChannelConfig) + // Mostly SSL, virtual host, and URL prefix + @Override + public boolean setOption(String key, Object value) { + if (PROXY_ADDRESS_OPTION.equals(key)) { + setProxyAddress((SocketAddress) value); + } else { + return super.setOption(key, value); + } + + return true; + } + + /** + * @return the address of the http proxy. If this is null, then no proxy + * should be used. + */ + public SocketAddress getProxyAddress() { + return proxyAddress; + } + + /** + * Specify a proxy to be used for the http tunnel. If this is null, then + * no proxy should be used, otherwise this should be a directly accessible IPv4/IPv6 + * address and port. + */ + public void setProxyAddress(SocketAddress proxyAddress) { + this.proxyAddress = proxyAddress; + } + + /* GENERIC SOCKET CHANNEL CONFIGURATION */ + + public int getReceiveBufferSize() { + return pollChannelConfig.getReceiveBufferSize(); + } + + public int getSendBufferSize() { + return pollChannelConfig.getSendBufferSize(); + } + + public int getSoLinger() { + return pollChannelConfig.getSoLinger(); + } + + public int getTrafficClass() { + return pollChannelConfig.getTrafficClass(); + } + + public boolean isKeepAlive() { + return pollChannelConfig.isKeepAlive(); + } + + public boolean isReuseAddress() { + return pollChannelConfig.isReuseAddress(); + } + + public boolean isTcpNoDelay() { + return pollChannelConfig.isTcpNoDelay(); + } + + public void setKeepAlive(boolean keepAlive) { + pollChannelConfig.setKeepAlive(keepAlive); + sendChannelConfig.setKeepAlive(keepAlive); + } + + public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + pollChannelConfig.setPerformancePreferences(connectionTime, latency, + bandwidth); + sendChannelConfig.setPerformancePreferences(connectionTime, latency, + bandwidth); + } + + public void setReceiveBufferSize(int receiveBufferSize) { + pollChannelConfig.setReceiveBufferSize(receiveBufferSize); + sendChannelConfig.setReceiveBufferSize(receiveBufferSize); + } + + public void setReuseAddress(boolean reuseAddress) { + pollChannelConfig.setReuseAddress(reuseAddress); + sendChannelConfig.setReuseAddress(reuseAddress); + } + + public void setSendBufferSize(int sendBufferSize) { + pollChannelConfig.setSendBufferSize(sendBufferSize); + sendChannelConfig.setSendBufferSize(sendBufferSize); + } + + public void setSoLinger(int soLinger) { + pollChannelConfig.setSoLinger(soLinger); + sendChannelConfig.setSoLinger(soLinger); + } + + public void setTcpNoDelay(boolean tcpNoDelay) { + pollChannelConfig.setTcpNoDelay(true); + sendChannelConfig.setTcpNoDelay(true); + } + + public void setTrafficClass(int trafficClass) { + pollChannelConfig.setTrafficClass(1); + sendChannelConfig.setTrafficClass(1); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java index 27b75834bb4..78d2ce8a623 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelFactory.java @@ -27,31 +27,28 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelClientChannelFactory implements ClientSocketChannelFactory -{ - - private final ClientSocketChannelFactory factory; - - private final ChannelGroup realConnections = new DefaultChannelGroup(); - - public HttpTunnelClientChannelFactory(ClientSocketChannelFactory factory) - { - if (factory == null) - { - throw new NullPointerException("factory"); - } - this.factory = factory; - } - - public HttpTunnelClientChannel newChannel(ChannelPipeline pipeline) - { - return new HttpTunnelClientChannel(this, pipeline, new HttpTunnelClientChannelSink(), factory, realConnections); - } - - public void releaseExternalResources() - { - realConnections.close().awaitUninterruptibly(); - factory.releaseExternalResources(); - } +public class HttpTunnelClientChannelFactory implements + ClientSocketChannelFactory { + + private final ClientSocketChannelFactory factory; + + private final ChannelGroup realConnections = new DefaultChannelGroup(); + + public HttpTunnelClientChannelFactory(ClientSocketChannelFactory factory) { + if (factory == null) { + throw new NullPointerException("factory"); + } + this.factory = factory; + } + + public HttpTunnelClientChannel newChannel(ChannelPipeline pipeline) { + return new HttpTunnelClientChannel(this, pipeline, + new HttpTunnelClientChannelSink(), factory, realConnections); + } + + public void releaseExternalResources() { + realConnections.close().awaitUninterruptibly(); + factory.releaseExternalResources(); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java index cf3a6c13614..5284691a830 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelSink.java @@ -31,59 +31,49 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class HttpTunnelClientChannelSink extends AbstractChannelSink -{ +class HttpTunnelClientChannelSink extends AbstractChannelSink { - public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception - { - if (e instanceof ChannelStateEvent) - { - handleChannelStateEvent((ChannelStateEvent) e); - } - else if (e instanceof MessageEvent) - { - handleMessageEvent((MessageEvent) e); - } - } + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) + throws Exception { + if (e instanceof ChannelStateEvent) { + handleChannelStateEvent((ChannelStateEvent) e); + } else if (e instanceof MessageEvent) { + handleMessageEvent((MessageEvent) e); + } + } - private void handleMessageEvent(MessageEvent e) - { - HttpTunnelClientChannel channel = (HttpTunnelClientChannel) e.getChannel(); - channel.sendData(e); - } + private void handleMessageEvent(MessageEvent e) { + HttpTunnelClientChannel channel = + (HttpTunnelClientChannel) e.getChannel(); + channel.sendData(e); + } - private void handleChannelStateEvent(ChannelStateEvent e) - { - HttpTunnelClientChannel channel = (HttpTunnelClientChannel) e.getChannel(); + private void handleChannelStateEvent(ChannelStateEvent e) { + HttpTunnelClientChannel channel = + (HttpTunnelClientChannel) e.getChannel(); - switch (e.getState()) - { - case CONNECTED : - if (e.getValue() != null) - { - channel.onConnectRequest(e.getFuture(), (InetSocketAddress) e.getValue()); - } - else - { - channel.onDisconnectRequest(e.getFuture()); + switch (e.getState()) { + case CONNECTED: + if (e.getValue() != null) { + channel.onConnectRequest(e.getFuture(), + (InetSocketAddress) e.getValue()); + } else { + channel.onDisconnectRequest(e.getFuture()); } break; - case BOUND : - if (e.getValue() != null) - { - channel.onBindRequest((InetSocketAddress) e.getValue(), e.getFuture()); - } - else - { - channel.onUnbindRequest(e.getFuture()); + case BOUND: + if (e.getValue() != null) { + channel.onBindRequest((InetSocketAddress) e.getValue(), + e.getFuture()); + } else { + channel.onUnbindRequest(e.getFuture()); } break; - case OPEN : - if (Boolean.FALSE.equals(e.getValue())) - { - channel.onCloseRequest(e.getFuture()); + case OPEN: + if (Boolean.FALSE.equals(e.getValue())) { + channel.onCloseRequest(e.getFuture()); } break; - } - } + } + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java index fd35dc8fd3a..1ee05563163 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandler.java @@ -32,73 +32,66 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class HttpTunnelClientPollHandler extends SimpleChannelHandler -{ +class HttpTunnelClientPollHandler extends SimpleChannelHandler { - public static final String NAME = "server2client"; + public static final String NAME = "server2client"; - private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HttpTunnelClientPollHandler.class); + private static final InternalLogger LOG = InternalLoggerFactory + .getInstance(HttpTunnelClientPollHandler.class); - private String tunnelId; + private String tunnelId; - private final HttpTunnelClientWorkerOwner tunnelChannel; + private final HttpTunnelClientWorkerOwner tunnelChannel; - private long pollTime; + private long pollTime; - public HttpTunnelClientPollHandler(HttpTunnelClientWorkerOwner tunnelChannel) - { - this.tunnelChannel = tunnelChannel; - } + public HttpTunnelClientPollHandler(HttpTunnelClientWorkerOwner tunnelChannel) { + this.tunnelChannel = tunnelChannel; + } - public void setTunnelId(String tunnelId) - { - this.tunnelId = tunnelId; - } + public void setTunnelId(String tunnelId) { + this.tunnelId = tunnelId; + } - @Override - public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - if (LOG.isDebugEnabled()) - { - LOG.debug("Poll channel for tunnel " + tunnelId + " established"); - } - tunnelChannel.fullyEstablished(); - sendPoll(ctx); - } + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + if (LOG.isDebugEnabled()) { + LOG.debug("Poll channel for tunnel " + tunnelId + " established"); + } + tunnelChannel.fullyEstablished(); + sendPoll(ctx); + } - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - HttpResponse response = (HttpResponse) e.getMessage(); + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + HttpResponse response = (HttpResponse) e.getMessage(); - if (HttpTunnelMessageUtils.isOKResponse(response)) - { - long rtTime = System.nanoTime() - pollTime; - if (LOG.isDebugEnabled()) - { - LOG.debug("OK response received for poll on tunnel " + tunnelId + " after " + rtTime + " ns"); - } - tunnelChannel.onMessageReceived(response.getContent()); - sendPoll(ctx); - } - else - { - if (LOG.isWarnEnabled()) - { - LOG.warn("non-OK response received for poll on tunnel " + tunnelId); - } - } - } + if (HttpTunnelMessageUtils.isOKResponse(response)) { + long rtTime = System.nanoTime() - pollTime; + if (LOG.isDebugEnabled()) { + LOG.debug("OK response received for poll on tunnel " + + tunnelId + " after " + rtTime + " ns"); + } + tunnelChannel.onMessageReceived(response.getContent()); + sendPoll(ctx); + } else { + if (LOG.isWarnEnabled()) { + LOG.warn("non-OK response received for poll on tunnel " + + tunnelId); + } + } + } - private void sendPoll(ChannelHandlerContext ctx) - { - pollTime = System.nanoTime(); - if (LOG.isDebugEnabled()) - { - LOG.debug("sending poll request for tunnel " + tunnelId); - } - HttpRequest request = HttpTunnelMessageUtils - .createReceiveDataRequest(tunnelChannel.getServerHostName(), tunnelId); - Channels.write(ctx, Channels.future(ctx.getChannel()), request); - } + private void sendPoll(ChannelHandlerContext ctx) { + pollTime = System.nanoTime(); + if (LOG.isDebugEnabled()) { + LOG.debug("sending poll request for tunnel " + tunnelId); + } + HttpRequest request = + HttpTunnelMessageUtils.createReceiveDataRequest( + tunnelChannel.getServerHostName(), tunnelId); + Channels.write(ctx, Channels.future(ctx.getChannel()), request); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java index 3347616b758..5a44364e4fa 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandler.java @@ -39,229 +39,207 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class HttpTunnelClientSendHandler extends SimpleChannelHandler -{ +class HttpTunnelClientSendHandler extends SimpleChannelHandler { - public static final String NAME = "client2server"; + public static final String NAME = "client2server"; - private static final InternalLogger LOG = InternalLoggerFactory.getInstance(HttpTunnelClientSendHandler.class); + private static final InternalLogger LOG = InternalLoggerFactory + .getInstance(HttpTunnelClientSendHandler.class); - private final HttpTunnelClientWorkerOwner tunnelChannel; + private final HttpTunnelClientWorkerOwner tunnelChannel; - private String tunnelId = null; + private String tunnelId = null; - private final AtomicBoolean disconnecting; + private final AtomicBoolean disconnecting; - private ChannelStateEvent postShutdownEvent; + private ChannelStateEvent postShutdownEvent; - private final ConcurrentLinkedQueue queuedWrites; + private final ConcurrentLinkedQueue queuedWrites; - private final AtomicInteger pendingRequestCount; + private final AtomicInteger pendingRequestCount; - private long sendRequestTime; + private long sendRequestTime; - public HttpTunnelClientSendHandler(HttpTunnelClientWorkerOwner tunnelChannel) - { - this.tunnelChannel = tunnelChannel; - queuedWrites = new ConcurrentLinkedQueue(); - pendingRequestCount = new AtomicInteger(0); - disconnecting = new AtomicBoolean(false); - } + public HttpTunnelClientSendHandler(HttpTunnelClientWorkerOwner tunnelChannel) { + this.tunnelChannel = tunnelChannel; + queuedWrites = new ConcurrentLinkedQueue(); + pendingRequestCount = new AtomicInteger(0); + disconnecting = new AtomicBoolean(false); + } - @Override - public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - if (tunnelId == null) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("connection to " + e.getValue() + " succeeded - sending open tunnel request"); - } - HttpRequest request = HttpTunnelMessageUtils.createOpenTunnelRequest(tunnelChannel.getServerHostName()); - Channel thisChannel = ctx.getChannel(); - DownstreamMessageEvent event = new DownstreamMessageEvent(thisChannel, Channels.future(thisChannel), request, - thisChannel.getRemoteAddress()); - queuedWrites.offer(event); - pendingRequestCount.incrementAndGet(); - sendQueuedData(ctx); - } - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - HttpResponse response = (HttpResponse) e.getMessage(); - - if (HttpTunnelMessageUtils.isOKResponse(response)) - { - long roundTripTime = System.nanoTime() - sendRequestTime; - if (LOG.isDebugEnabled()) - { - LOG.debug("OK response received for tunnel " + tunnelId + ", after " + roundTripTime + " ns"); - } - sendNextAfterResponse(ctx); - } - else if (HttpTunnelMessageUtils.isTunnelOpenResponse(response)) - { - tunnelId = HttpTunnelMessageUtils.extractCookie(response); - if (LOG.isDebugEnabled()) - { - LOG.debug("tunnel open request accepted - id " + tunnelId); - } - tunnelChannel.onTunnelOpened(tunnelId); - sendNextAfterResponse(ctx); - } - else if (HttpTunnelMessageUtils.isTunnelCloseResponse(response)) - { - if (LOG.isDebugEnabled()) - { - if (disconnecting.get()) - { - LOG.debug("server acknowledged disconnect for tunnel " + tunnelId); + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + if (tunnelId == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("connection to " + e.getValue() + + " succeeded - sending open tunnel request"); } - else - { - LOG.debug("server closed tunnel " + tunnelId); + HttpRequest request = + HttpTunnelMessageUtils + .createOpenTunnelRequest(tunnelChannel + .getServerHostName()); + Channel thisChannel = ctx.getChannel(); + DownstreamMessageEvent event = + new DownstreamMessageEvent(thisChannel, + Channels.future(thisChannel), request, + thisChannel.getRemoteAddress()); + queuedWrites.offer(event); + pendingRequestCount.incrementAndGet(); + sendQueuedData(ctx); + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + HttpResponse response = (HttpResponse) e.getMessage(); + + if (HttpTunnelMessageUtils.isOKResponse(response)) { + long roundTripTime = System.nanoTime() - sendRequestTime; + if (LOG.isDebugEnabled()) { + LOG.debug("OK response received for tunnel " + tunnelId + + ", after " + roundTripTime + " ns"); } - } - ctx.sendDownstream(postShutdownEvent); - } - else - { - // TODO: kill connection - if (LOG.isWarnEnabled()) - { - LOG.warn("unknown response received for tunnel " + tunnelId + ", closing connection"); - } - Channels.close(ctx, ctx.getChannel().getCloseFuture()); - } - } - - private void sendNextAfterResponse(ChannelHandlerContext ctx) - { - if (pendingRequestCount.decrementAndGet() > 0) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("Immediately sending next send request for tunnel " + tunnelId); - } - sendQueuedData(ctx); - } - } - - private synchronized void sendQueuedData(ChannelHandlerContext ctx) - { - if (disconnecting.get()) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("sending close request for tunnel " + tunnelId); - } - HttpRequest closeRequest = HttpTunnelMessageUtils.createCloseTunnelRequest(tunnelChannel.getServerHostName(), - tunnelId); - Channels.write(ctx, Channels.future(ctx.getChannel()), closeRequest); - } - else - { - if (LOG.isDebugEnabled()) - { - LOG.debug("sending next request for tunnel " + tunnelId); - } - MessageEvent nextWrite = queuedWrites.poll(); - sendRequestTime = System.nanoTime(); - ctx.sendDownstream(nextWrite); - } - } - - @Override - public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - if (LOG.isDebugEnabled()) - { - LOG.debug("request to send data for tunnel " + tunnelId); - } - if (disconnecting.get()) - { - if (LOG.isWarnEnabled()) - { - LOG.warn("rejecting write request for tunnel " + tunnelId + " received after disconnect requested"); - } - e.getFuture().setFailure(new IllegalStateException("tunnel is closing")); - return; - } - ChannelBuffer data = (ChannelBuffer) e.getMessage(); - HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(tunnelChannel.getServerHostName(), tunnelId, - data); - DownstreamMessageEvent translatedEvent = new DownstreamMessageEvent(ctx.getChannel(), e.getFuture(), request, ctx - .getChannel().getRemoteAddress()); - queuedWrites.offer(translatedEvent); - if (pendingRequestCount.incrementAndGet() == 1) - { - sendQueuedData(ctx); - } - else - { - if (LOG.isDebugEnabled()) - { - LOG.debug("write request for tunnel " + tunnelId + " queued"); - } - } - } - - @Override - public void closeRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - shutdownTunnel(ctx, e); - } - - @Override - public void disconnectRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - shutdownTunnel(ctx, e); - } - - @Override - public void unbindRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - shutdownTunnel(ctx, e); - } - - private void shutdownTunnel(ChannelHandlerContext ctx, ChannelStateEvent postShutdownEvent) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("tunnel shutdown requested for send channel of tunnel " + tunnelId); - } - if (!ctx.getChannel().isConnected()) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("send channel of tunnel " + tunnelId + " is already disconnected"); - } - ctx.sendDownstream(postShutdownEvent); - return; - } - - if (!disconnecting.compareAndSet(false, true)) - { - if (LOG.isWarnEnabled()) - { - LOG.warn("tunnel shutdown process already initiated for tunnel " + tunnelId); - } - return; - } + sendNextAfterResponse(ctx); + } else if (HttpTunnelMessageUtils.isTunnelOpenResponse(response)) { + tunnelId = HttpTunnelMessageUtils.extractCookie(response); + if (LOG.isDebugEnabled()) { + LOG.debug("tunnel open request accepted - id " + tunnelId); + } + tunnelChannel.onTunnelOpened(tunnelId); + sendNextAfterResponse(ctx); + } else if (HttpTunnelMessageUtils.isTunnelCloseResponse(response)) { + if (LOG.isDebugEnabled()) { + if (disconnecting.get()) { + LOG.debug("server acknowledged disconnect for tunnel " + + tunnelId); + } else { + LOG.debug("server closed tunnel " + tunnelId); + } + } + ctx.sendDownstream(postShutdownEvent); + } else { + // TODO: kill connection + if (LOG.isWarnEnabled()) { + LOG.warn("unknown response received for tunnel " + tunnelId + + ", closing connection"); + } + Channels.close(ctx, ctx.getChannel().getCloseFuture()); + } + } + + private void sendNextAfterResponse(ChannelHandlerContext ctx) { + if (pendingRequestCount.decrementAndGet() > 0) { + if (LOG.isDebugEnabled()) { + LOG.debug("Immediately sending next send request for tunnel " + + tunnelId); + } + sendQueuedData(ctx); + } + } + + private synchronized void sendQueuedData(ChannelHandlerContext ctx) { + if (disconnecting.get()) { + if (LOG.isDebugEnabled()) { + LOG.debug("sending close request for tunnel " + tunnelId); + } + HttpRequest closeRequest = + HttpTunnelMessageUtils.createCloseTunnelRequest( + tunnelChannel.getServerHostName(), tunnelId); + Channels.write(ctx, Channels.future(ctx.getChannel()), closeRequest); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("sending next request for tunnel " + tunnelId); + } + MessageEvent nextWrite = queuedWrites.poll(); + sendRequestTime = System.nanoTime(); + ctx.sendDownstream(nextWrite); + } + } + + @Override + public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + if (LOG.isDebugEnabled()) { + LOG.debug("request to send data for tunnel " + tunnelId); + } + if (disconnecting.get()) { + if (LOG.isWarnEnabled()) { + LOG.warn("rejecting write request for tunnel " + tunnelId + + " received after disconnect requested"); + } + e.getFuture().setFailure( + new IllegalStateException("tunnel is closing")); + return; + } + ChannelBuffer data = (ChannelBuffer) e.getMessage(); + HttpRequest request = + HttpTunnelMessageUtils.createSendDataRequest( + tunnelChannel.getServerHostName(), tunnelId, data); + DownstreamMessageEvent translatedEvent = + new DownstreamMessageEvent(ctx.getChannel(), e.getFuture(), + request, ctx.getChannel().getRemoteAddress()); + queuedWrites.offer(translatedEvent); + if (pendingRequestCount.incrementAndGet() == 1) { + sendQueuedData(ctx); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("write request for tunnel " + tunnelId + " queued"); + } + } + } + + @Override + public void closeRequested(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + shutdownTunnel(ctx, e); + } + + @Override + public void disconnectRequested(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + shutdownTunnel(ctx, e); + } + + @Override + public void unbindRequested(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + shutdownTunnel(ctx, e); + } + + private void shutdownTunnel(ChannelHandlerContext ctx, + ChannelStateEvent postShutdownEvent) { + if (LOG.isDebugEnabled()) { + LOG.debug("tunnel shutdown requested for send channel of tunnel " + + tunnelId); + } + if (!ctx.getChannel().isConnected()) { + if (LOG.isDebugEnabled()) { + LOG.debug("send channel of tunnel " + tunnelId + + " is already disconnected"); + } + ctx.sendDownstream(postShutdownEvent); + return; + } + + if (!disconnecting.compareAndSet(false, true)) { + if (LOG.isWarnEnabled()) { + LOG.warn("tunnel shutdown process already initiated for tunnel " + + tunnelId); + } + return; + } - this.postShutdownEvent = postShutdownEvent; + this.postShutdownEvent = postShutdownEvent; - // if the channel is idle, send a close request immediately - if (pendingRequestCount.incrementAndGet() == 1) - { - sendQueuedData(ctx); - } - } + // if the channel is idle, send a close request immediately + if (pendingRequestCount.incrementAndGet() == 1) { + sendQueuedData(ctx); + } + } - public String getTunnelId() - { - return tunnelId; - } + public String getTunnelId() { + return tunnelId; + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java index 830ab074a38..59f3d161544 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelClientWorkerOwner.java @@ -30,39 +30,39 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -interface HttpTunnelClientWorkerOwner -{ - /** - * The HTTP tunnel client sink invokes this when the application code requests the connection - * of an HTTP tunnel to the specified remote address. - */ - public void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress); +interface HttpTunnelClientWorkerOwner { + /** + * The HTTP tunnel client sink invokes this when the application code requests the connection + * of an HTTP tunnel to the specified remote address. + */ + public void onConnectRequest(ChannelFuture connectFuture, + InetSocketAddress remoteAddress); - /** - * The send channel handler calls this method when the server accepts the open tunnel request, - * returning a unique tunnel ID. - * - * @param tunnelId the server allocated tunnel ID - */ - public void onTunnelOpened(String tunnelId); + /** + * The send channel handler calls this method when the server accepts the open tunnel request, + * returning a unique tunnel ID. + * + * @param tunnelId the server allocated tunnel ID + */ + public void onTunnelOpened(String tunnelId); - /** - * The poll channel handler calls this method when the poll channel is connected, indicating - * that full duplex communications are now possible. - */ - public void fullyEstablished(); + /** + * The poll channel handler calls this method when the poll channel is connected, indicating + * that full duplex communications are now possible. + */ + public void fullyEstablished(); - /** - * The poll handler calls this method when some data is received and decoded from the server. - * @param content the data received from the server - */ - public void onMessageReceived(ChannelBuffer content); + /** + * The poll handler calls this method when some data is received and decoded from the server. + * @param content the data received from the server + */ + public void onMessageReceived(ChannelBuffer content); - /** - * @return the name of the server with whom we are communicating with - this is used within - * the HOST HTTP header for all requests. This is particularly important for operation behind - * a proxy, where the HOST string is used to route the request. - */ - public String getServerHostName(); + /** + * @return the name of the server with whom we are communicating with - this is used within + * the HOST HTTP header for all requests. This is particularly important for operation behind + * a proxy, where the HOST string is used to route the request. + */ + public String getServerHostName(); } \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java index a92b804d166..2d9c45da789 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelMessageUtils.java @@ -44,320 +44,299 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelMessageUtils -{ - - private static final String HTTP_URL_PREFIX = "http://"; - - /** - * An upper bound is enforced on the size of message bodies, so as - * to ensure we do not dump large chunks of data on either peer. - */ - public static final int MAX_BODY_SIZE = 16 * 1024; - - /** - * The tunnel will only accept connections from this specific user agent. This - * allows us to distinguish a legitimate tunnel connection from someone pointing - * a web browser or robot at the tunnel URL. - */ - static final String USER_AGENT = "HttpTunnelClient"; - - static final String OPEN_TUNNEL_REQUEST_URI = "/http-tunnel/open"; - - static final String CLOSE_TUNNEL_REQUEST_URI = "/http-tunnel/close"; - - static final String CLIENT_SEND_REQUEST_URI = "/http-tunnel/send"; - - static final String CLIENT_RECV_REQUEST_URI = "/http-tunnel/poll"; - - static final String CONTENT_TYPE = "application/octet-stream"; - - public static HttpRequest createOpenTunnelRequest(SocketAddress host) - { - return createOpenTunnelRequest(convertToHostString(host)); - } - - public static HttpRequest createOpenTunnelRequest(String host) - { - HttpRequest request = createRequestTemplate(host, null, OPEN_TUNNEL_REQUEST_URI); - setNoData(request); - return request; - } - - public static boolean isOpenTunnelRequest(HttpRequest request) - { - return isRequestTo(request, OPEN_TUNNEL_REQUEST_URI); - } - - public static boolean checkHost(HttpRequest request, SocketAddress expectedHost) - { - String host = request.getHeader(HttpHeaders.Names.HOST); - return expectedHost == null ? host == null : HttpTunnelMessageUtils.convertToHostString(expectedHost) - .equals(host); - } - - public static HttpRequest createSendDataRequest(SocketAddress host, String cookie, ChannelBuffer data) - { - return createSendDataRequest(convertToHostString(host), cookie, data); - } - - public static HttpRequest createSendDataRequest(String host, String cookie, ChannelBuffer data) - { - HttpRequest request = createRequestTemplate(host, cookie, CLIENT_SEND_REQUEST_URI); - request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, Long.toString(data.readableBytes())); - request.setContent(data); - - return request; - } - - public static boolean isSendDataRequest(HttpRequest request) - { - return isRequestTo(request, CLIENT_SEND_REQUEST_URI); - } - - public static HttpRequest createReceiveDataRequest(SocketAddress host, String tunnelId) - { - return createReceiveDataRequest(convertToHostString(host), tunnelId); - } - - public static HttpRequest createReceiveDataRequest(String host, String tunnelId) - { - HttpRequest request = createRequestTemplate(host, tunnelId, CLIENT_RECV_REQUEST_URI); - setNoData(request); - return request; - } - - public static boolean isReceiveDataRequest(HttpRequest request) - { - return isRequestTo(request, CLIENT_RECV_REQUEST_URI); - } - - public static HttpRequest createCloseTunnelRequest(String host, String tunnelId) - { - HttpRequest request = createRequestTemplate(host, tunnelId, CLOSE_TUNNEL_REQUEST_URI); - setNoData(request); - return request; - } - - public static boolean isCloseTunnelRequest(HttpRequest request) - { - return isRequestTo(request, CLOSE_TUNNEL_REQUEST_URI); - } - - public static boolean isServerToClientRequest(HttpRequest request) - { - return isRequestTo(request, CLIENT_RECV_REQUEST_URI); - } - - public static String convertToHostString(SocketAddress hostAddress) - { - StringWriter host = new StringWriter(); - InetSocketAddress inetSocketAddr = (InetSocketAddress) hostAddress; - InetAddress addr = inetSocketAddr.getAddress(); - if (addr instanceof Inet6Address) - { - host.append('['); - host.append(addr.getHostAddress()); - host.append(']'); - } - else if (addr != null) - { - host.append(addr.getHostAddress()); - } - else - { - host.append(inetSocketAddr.getHostName()); - } - - host.append(':'); - host.append(Integer.toString(inetSocketAddr.getPort())); - return host.toString(); - } - - private static HttpRequest createRequestTemplate(String host, String tunnelId, String uri) - { - HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, createCompleteUri(host, uri)); - request.setHeader(HttpHeaders.Names.HOST, host); - request.setHeader(HttpHeaders.Names.USER_AGENT, USER_AGENT); - if (tunnelId != null) - { - request.setHeader(HttpHeaders.Names.COOKIE, tunnelId); - } - - return request; - } - - private static String createCompleteUri(String host, String uri) - { - StringBuilder builder = new StringBuilder(HTTP_URL_PREFIX.length() + host.length() + uri.length()); - builder.append(HTTP_URL_PREFIX); - builder.append(host); - builder.append(uri); - - return builder.toString(); - } - - private static boolean isRequestTo(HttpRequest request, String uri) - { - URI decodedUri; - try - { - decodedUri = new URI(request.getUri()); - } - catch (URISyntaxException e) - { - return false; - } - - return HttpVersion.HTTP_1_1.equals(request.getProtocolVersion()) - && USER_AGENT.equals(request.getHeader(HttpHeaders.Names.USER_AGENT)) - && HttpMethod.POST.equals(request.getMethod()) && uri.equals(decodedUri.getPath()); - } - - private static void setNoData(HttpRequest request) - { - request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0"); - request.setContent(null); - } - - public static String extractTunnelId(HttpRequest request) - { - return request.getHeader(HttpHeaders.Names.COOKIE); - } - - private static byte[] toBytes(String string) - { - try - { - return string.getBytes("UTF-8"); - } - catch (UnsupportedEncodingException e) - { - // UTF-8 is meant to be supported on all platforms - throw new RuntimeException("UTF-8 encoding not supported!"); - } - } - - public static HttpResponse createTunnelOpenResponse(String tunnelId) - { - HttpResponse response = createResponseTemplate(HttpResponseStatus.CREATED, null); - response.setHeader(HttpHeaders.Names.SET_COOKIE, tunnelId); - return response; - } - - public static boolean isTunnelOpenResponse(HttpResponse response) - { - return isResponseWithCode(response, HttpResponseStatus.CREATED); - } - - public static boolean isOKResponse(HttpResponse response) - { - return isResponseWithCode(response, HttpResponseStatus.OK); - } - - public static boolean hasContents(HttpResponse response, byte[] expectedContents) - { - if (response.getContent() != null && HttpHeaders.getContentLength(response) == expectedContents.length - && response.getContent().readableBytes() == expectedContents.length) - { - byte[] compareBytes = new byte[expectedContents.length]; - response.getContent().readBytes(compareBytes); - return Arrays.equals(expectedContents, compareBytes); - } - - return false; - } - - public static HttpResponse createTunnelCloseResponse() - { - HttpResponse response = createResponseTemplate(HttpResponseStatus.RESET_CONTENT, null); - return response; - } - - public static boolean isTunnelCloseResponse(HttpResponse response) - { - return isResponseWithCode(response, HttpResponseStatus.RESET_CONTENT); - } - - public static String extractCookie(HttpResponse response) - { - if (response.containsHeader(HttpHeaders.Names.SET_COOKIE)) - { - return response.getHeader(HttpHeaders.Names.SET_COOKIE); - } - - return null; - } - - public static HttpResponse createSendDataResponse() - { - return createOKResponseTemplate(null); - } - - public static HttpResponse createRecvDataResponse(ChannelBuffer data) - { - return createOKResponseTemplate(data); - } - - public static HttpResponse createRejection(HttpRequest request, String reason) - { - HttpVersion version = request != null ? request.getProtocolVersion() : HttpVersion.HTTP_1_1; - HttpResponse response = new DefaultHttpResponse(version, HttpResponseStatus.BAD_REQUEST); - response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=\"utf-8\""); - ChannelBuffer reasonBuffer = ChannelBuffers.wrappedBuffer(toBytes(reason)); - response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, Integer.toString(reasonBuffer.readableBytes())); - response.setContent(reasonBuffer); - return response; - } - - public static boolean isRejection(HttpResponse response) - { - return !HttpResponseStatus.OK.equals(response.getStatus()); - } - - public static Object extractErrorMessage(HttpResponse response) - { - if (response.getContent() == null || HttpHeaders.getContentLength(response) == 0) - { - return ""; - } - - byte[] bytes = new byte[response.getContent().readableBytes()]; - response.getContent().readBytes(bytes); - try - { - return new String(bytes, "UTF-8"); - } - catch (UnsupportedEncodingException e) - { - return ""; - } - } - - private static boolean isResponseWithCode(HttpResponse response, HttpResponseStatus status) - { - return HttpVersion.HTTP_1_1.equals(response.getProtocolVersion()) && status.equals(response.getStatus()); - } - - private static HttpResponse createOKResponseTemplate(ChannelBuffer data) - { - return createResponseTemplate(HttpResponseStatus.OK, data); - } - - private static HttpResponse createResponseTemplate(HttpResponseStatus status, ChannelBuffer data) - { - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status); - if (data != null) - { - response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, Integer.toString(data.readableBytes())); - response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream"); - response.setContent(data); - } - else - { - response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0"); - response.setContent(null); - } - return response; - } +public class HttpTunnelMessageUtils { + + private static final String HTTP_URL_PREFIX = "http://"; + + /** + * An upper bound is enforced on the size of message bodies, so as + * to ensure we do not dump large chunks of data on either peer. + */ + public static final int MAX_BODY_SIZE = 16 * 1024; + + /** + * The tunnel will only accept connections from this specific user agent. This + * allows us to distinguish a legitimate tunnel connection from someone pointing + * a web browser or robot at the tunnel URL. + */ + static final String USER_AGENT = "HttpTunnelClient"; + + static final String OPEN_TUNNEL_REQUEST_URI = "/http-tunnel/open"; + + static final String CLOSE_TUNNEL_REQUEST_URI = "/http-tunnel/close"; + + static final String CLIENT_SEND_REQUEST_URI = "/http-tunnel/send"; + + static final String CLIENT_RECV_REQUEST_URI = "/http-tunnel/poll"; + + static final String CONTENT_TYPE = "application/octet-stream"; + + public static HttpRequest createOpenTunnelRequest(SocketAddress host) { + return createOpenTunnelRequest(convertToHostString(host)); + } + + public static HttpRequest createOpenTunnelRequest(String host) { + HttpRequest request = + createRequestTemplate(host, null, OPEN_TUNNEL_REQUEST_URI); + setNoData(request); + return request; + } + + public static boolean isOpenTunnelRequest(HttpRequest request) { + return isRequestTo(request, OPEN_TUNNEL_REQUEST_URI); + } + + public static boolean checkHost(HttpRequest request, + SocketAddress expectedHost) { + String host = request.getHeader(HttpHeaders.Names.HOST); + return expectedHost == null? host == null : HttpTunnelMessageUtils + .convertToHostString(expectedHost).equals(host); + } + + public static HttpRequest createSendDataRequest(SocketAddress host, + String cookie, ChannelBuffer data) { + return createSendDataRequest(convertToHostString(host), cookie, data); + } + + public static HttpRequest createSendDataRequest(String host, String cookie, + ChannelBuffer data) { + HttpRequest request = + createRequestTemplate(host, cookie, CLIENT_SEND_REQUEST_URI); + request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, + Long.toString(data.readableBytes())); + request.setContent(data); + + return request; + } + + public static boolean isSendDataRequest(HttpRequest request) { + return isRequestTo(request, CLIENT_SEND_REQUEST_URI); + } + + public static HttpRequest createReceiveDataRequest(SocketAddress host, + String tunnelId) { + return createReceiveDataRequest(convertToHostString(host), tunnelId); + } + + public static HttpRequest createReceiveDataRequest(String host, + String tunnelId) { + HttpRequest request = + createRequestTemplate(host, tunnelId, CLIENT_RECV_REQUEST_URI); + setNoData(request); + return request; + } + + public static boolean isReceiveDataRequest(HttpRequest request) { + return isRequestTo(request, CLIENT_RECV_REQUEST_URI); + } + + public static HttpRequest createCloseTunnelRequest(String host, + String tunnelId) { + HttpRequest request = + createRequestTemplate(host, tunnelId, CLOSE_TUNNEL_REQUEST_URI); + setNoData(request); + return request; + } + + public static boolean isCloseTunnelRequest(HttpRequest request) { + return isRequestTo(request, CLOSE_TUNNEL_REQUEST_URI); + } + + public static boolean isServerToClientRequest(HttpRequest request) { + return isRequestTo(request, CLIENT_RECV_REQUEST_URI); + } + + public static String convertToHostString(SocketAddress hostAddress) { + StringWriter host = new StringWriter(); + InetSocketAddress inetSocketAddr = (InetSocketAddress) hostAddress; + InetAddress addr = inetSocketAddr.getAddress(); + if (addr instanceof Inet6Address) { + host.append('['); + host.append(addr.getHostAddress()); + host.append(']'); + } else if (addr != null) { + host.append(addr.getHostAddress()); + } else { + host.append(inetSocketAddr.getHostName()); + } + + host.append(':'); + host.append(Integer.toString(inetSocketAddr.getPort())); + return host.toString(); + } + + private static HttpRequest createRequestTemplate(String host, + String tunnelId, String uri) { + HttpRequest request = + new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, + createCompleteUri(host, uri)); + request.setHeader(HttpHeaders.Names.HOST, host); + request.setHeader(HttpHeaders.Names.USER_AGENT, USER_AGENT); + if (tunnelId != null) { + request.setHeader(HttpHeaders.Names.COOKIE, tunnelId); + } + + return request; + } + + private static String createCompleteUri(String host, String uri) { + StringBuilder builder = + new StringBuilder(HTTP_URL_PREFIX.length() + host.length() + + uri.length()); + builder.append(HTTP_URL_PREFIX); + builder.append(host); + builder.append(uri); + + return builder.toString(); + } + + private static boolean isRequestTo(HttpRequest request, String uri) { + URI decodedUri; + try { + decodedUri = new URI(request.getUri()); + } catch (URISyntaxException e) { + return false; + } + + return HttpVersion.HTTP_1_1.equals(request.getProtocolVersion()) && + USER_AGENT.equals(request + .getHeader(HttpHeaders.Names.USER_AGENT)) && + HttpMethod.POST.equals(request.getMethod()) && + uri.equals(decodedUri.getPath()); + } + + private static void setNoData(HttpRequest request) { + request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0"); + request.setContent(null); + } + + public static String extractTunnelId(HttpRequest request) { + return request.getHeader(HttpHeaders.Names.COOKIE); + } + + private static byte[] toBytes(String string) { + try { + return string.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + // UTF-8 is meant to be supported on all platforms + throw new RuntimeException("UTF-8 encoding not supported!"); + } + } + + public static HttpResponse createTunnelOpenResponse(String tunnelId) { + HttpResponse response = + createResponseTemplate(HttpResponseStatus.CREATED, null); + response.setHeader(HttpHeaders.Names.SET_COOKIE, tunnelId); + return response; + } + + public static boolean isTunnelOpenResponse(HttpResponse response) { + return isResponseWithCode(response, HttpResponseStatus.CREATED); + } + + public static boolean isOKResponse(HttpResponse response) { + return isResponseWithCode(response, HttpResponseStatus.OK); + } + + public static boolean hasContents(HttpResponse response, + byte[] expectedContents) { + if (response.getContent() != null && + HttpHeaders.getContentLength(response) == expectedContents.length && + response.getContent().readableBytes() == expectedContents.length) { + byte[] compareBytes = new byte[expectedContents.length]; + response.getContent().readBytes(compareBytes); + return Arrays.equals(expectedContents, compareBytes); + } + + return false; + } + + public static HttpResponse createTunnelCloseResponse() { + HttpResponse response = + createResponseTemplate(HttpResponseStatus.RESET_CONTENT, null); + return response; + } + + public static boolean isTunnelCloseResponse(HttpResponse response) { + return isResponseWithCode(response, HttpResponseStatus.RESET_CONTENT); + } + + public static String extractCookie(HttpResponse response) { + if (response.containsHeader(HttpHeaders.Names.SET_COOKIE)) { + return response.getHeader(HttpHeaders.Names.SET_COOKIE); + } + + return null; + } + + public static HttpResponse createSendDataResponse() { + return createOKResponseTemplate(null); + } + + public static HttpResponse createRecvDataResponse(ChannelBuffer data) { + return createOKResponseTemplate(data); + } + + public static HttpResponse createRejection(HttpRequest request, + String reason) { + HttpVersion version = + request != null? request.getProtocolVersion() + : HttpVersion.HTTP_1_1; + HttpResponse response = + new DefaultHttpResponse(version, HttpResponseStatus.BAD_REQUEST); + response.setHeader(HttpHeaders.Names.CONTENT_TYPE, + "text/plain; charset=\"utf-8\""); + ChannelBuffer reasonBuffer = + ChannelBuffers.wrappedBuffer(toBytes(reason)); + response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, + Integer.toString(reasonBuffer.readableBytes())); + response.setContent(reasonBuffer); + return response; + } + + public static boolean isRejection(HttpResponse response) { + return !HttpResponseStatus.OK.equals(response.getStatus()); + } + + public static Object extractErrorMessage(HttpResponse response) { + if (response.getContent() == null || + HttpHeaders.getContentLength(response) == 0) { + return ""; + } + + byte[] bytes = new byte[response.getContent().readableBytes()]; + response.getContent().readBytes(bytes); + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + private static boolean isResponseWithCode(HttpResponse response, + HttpResponseStatus status) { + return HttpVersion.HTTP_1_1.equals(response.getProtocolVersion()) && + status.equals(response.getStatus()); + } + + private static HttpResponse createOKResponseTemplate(ChannelBuffer data) { + return createResponseTemplate(HttpResponseStatus.OK, data); + } + + private static HttpResponse createResponseTemplate( + HttpResponseStatus status, ChannelBuffer data) { + HttpResponse response = + new DefaultHttpResponse(HttpVersion.HTTP_1_1, status); + if (data != null) { + response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, + Integer.toString(data.readableBytes())); + response.setHeader(HttpHeaders.Names.CONTENT_TYPE, + "application/octet-stream"); + response.setContent(data); + } else { + response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0"); + response.setContent(null); + } + return response; + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java index 8eae5adc970..53764759e17 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannel.java @@ -31,83 +31,80 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelServerChannel extends AbstractServerChannel implements ServerSocketChannel -{ - - private final ServerSocketChannel realChannel; - - private final HttpTunnelServerChannelConfig config; - - private final ServerMessageSwitch messageSwitch; - - private final ChannelFutureListener CLOSE_FUTURE_PROXY = new ChannelFutureListener() - { - public void operationComplete(ChannelFuture future) throws Exception - { - HttpTunnelServerChannel.this.setClosed(); - } - }; - - protected HttpTunnelServerChannel(HttpTunnelServerChannelFactory factory, ChannelPipeline pipeline) - { - super(factory, pipeline, new HttpTunnelServerChannelSink()); - - messageSwitch = new ServerMessageSwitch(new TunnelCreator()); - realChannel = factory.createRealChannel(this, messageSwitch); - HttpTunnelServerChannelSink sink = (HttpTunnelServerChannelSink) getPipeline().getSink(); - sink.setRealChannel(realChannel); - sink.setCloseListener(CLOSE_FUTURE_PROXY); - config = new HttpTunnelServerChannelConfig(realChannel); - Channels.fireChannelOpen(this); - } - - public ServerSocketChannelConfig getConfig() - { - return config; - } - - public InetSocketAddress getLocalAddress() - { - return realChannel.getLocalAddress(); - } - - public InetSocketAddress getRemoteAddress() - { - // server channels never have a remote address - return null; - } - - public boolean isBound() - { - return realChannel.isBound(); - } - - /** - * Used to hide the newChannel method from the public API. - */ - private final class TunnelCreator implements HttpTunnelAcceptedChannelFactory - { - - public HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId, InetSocketAddress remoteAddress) - { - ChannelPipeline childPipeline = null; - try - { - childPipeline = getConfig().getPipelineFactory().getPipeline(); - } - catch (Exception e) - { - throw new ChannelPipelineException("Failed to initialize a pipeline.", e); - } - HttpTunnelAcceptedChannelConfig config = new HttpTunnelAcceptedChannelConfig(); - HttpTunnelAcceptedChannelSink sink = new HttpTunnelAcceptedChannelSink(messageSwitch, newTunnelId, config); - return new HttpTunnelAcceptedChannel(HttpTunnelServerChannel.this, getFactory(), childPipeline, sink, - remoteAddress, config); - } - - public String generateTunnelId() - { - return config.getTunnelIdGenerator().generateId(); - } - } +public class HttpTunnelServerChannel extends AbstractServerChannel implements + ServerSocketChannel { + + private final ServerSocketChannel realChannel; + + private final HttpTunnelServerChannelConfig config; + + private final ServerMessageSwitch messageSwitch; + + private final ChannelFutureListener CLOSE_FUTURE_PROXY = + new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) + throws Exception { + HttpTunnelServerChannel.this.setClosed(); + } + }; + + protected HttpTunnelServerChannel(HttpTunnelServerChannelFactory factory, + ChannelPipeline pipeline) { + super(factory, pipeline, new HttpTunnelServerChannelSink()); + + messageSwitch = new ServerMessageSwitch(new TunnelCreator()); + realChannel = factory.createRealChannel(this, messageSwitch); + HttpTunnelServerChannelSink sink = + (HttpTunnelServerChannelSink) getPipeline().getSink(); + sink.setRealChannel(realChannel); + sink.setCloseListener(CLOSE_FUTURE_PROXY); + config = new HttpTunnelServerChannelConfig(realChannel); + Channels.fireChannelOpen(this); + } + + public ServerSocketChannelConfig getConfig() { + return config; + } + + public InetSocketAddress getLocalAddress() { + return realChannel.getLocalAddress(); + } + + public InetSocketAddress getRemoteAddress() { + // server channels never have a remote address + return null; + } + + public boolean isBound() { + return realChannel.isBound(); + } + + /** + * Used to hide the newChannel method from the public API. + */ + private final class TunnelCreator implements + HttpTunnelAcceptedChannelFactory { + + public HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId, + InetSocketAddress remoteAddress) { + ChannelPipeline childPipeline = null; + try { + childPipeline = getConfig().getPipelineFactory().getPipeline(); + } catch (Exception e) { + throw new ChannelPipelineException( + "Failed to initialize a pipeline.", e); + } + HttpTunnelAcceptedChannelConfig config = + new HttpTunnelAcceptedChannelConfig(); + HttpTunnelAcceptedChannelSink sink = + new HttpTunnelAcceptedChannelSink(messageSwitch, + newTunnelId, config); + return new HttpTunnelAcceptedChannel(HttpTunnelServerChannel.this, + getFactory(), childPipeline, sink, remoteAddress, config); + } + + public String generateTunnelId() { + return config.getTunnelIdGenerator().generateId(); + } + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java index f77362dc1ef..db54ef039ad 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelConfig.java @@ -28,123 +28,100 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelServerChannelConfig implements ServerSocketChannelConfig -{ - - private ChannelPipelineFactory pipelineFactory; - - private final ServerSocketChannel realChannel; - - private TunnelIdGenerator tunnelIdGenerator = new DefaultTunnelIdGenerator(); - - public HttpTunnelServerChannelConfig(ServerSocketChannel realChannel) - { - this.realChannel = realChannel; - } - - private ServerSocketChannelConfig getWrappedConfig() - { - return realChannel.getConfig(); - } - - public int getBacklog() - { - return getWrappedConfig().getBacklog(); - } - - public int getReceiveBufferSize() - { - return getWrappedConfig().getReceiveBufferSize(); - } - - public boolean isReuseAddress() - { - return getWrappedConfig().isReuseAddress(); - } - - public void setBacklog(int backlog) - { - getWrappedConfig().setBacklog(backlog); - } - - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) - { - getWrappedConfig().setPerformancePreferences(connectionTime, latency, bandwidth); - } - - public void setReceiveBufferSize(int receiveBufferSize) - { - getWrappedConfig().setReceiveBufferSize(receiveBufferSize); - } - - public void setReuseAddress(boolean reuseAddress) - { - getWrappedConfig().setReuseAddress(reuseAddress); - } - - public ChannelBufferFactory getBufferFactory() - { - return getWrappedConfig().getBufferFactory(); - } - - public int getConnectTimeoutMillis() - { - return getWrappedConfig().getConnectTimeoutMillis(); - } - - public ChannelPipelineFactory getPipelineFactory() - { - return pipelineFactory; - } - - public void setBufferFactory(ChannelBufferFactory bufferFactory) - { - getWrappedConfig().setBufferFactory(bufferFactory); - } - - public void setConnectTimeoutMillis(int connectTimeoutMillis) - { - getWrappedConfig().setConnectTimeoutMillis(connectTimeoutMillis); - } - - public boolean setOption(String name, Object value) - { - if (name.equals("pipelineFactory")) - { - setPipelineFactory((ChannelPipelineFactory) value); - return true; - } - else if (name.equals("tunnelIdGenerator")) - { - setTunnelIdGenerator((TunnelIdGenerator) value); - return true; - } - else - { - return getWrappedConfig().setOption(name, value); - } - } - - public void setOptions(Map options) - { - for (Entry e : options.entrySet()) - { - setOption(e.getKey(), e.getValue()); - } - } - - public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) - { - this.pipelineFactory = pipelineFactory; - } - - public void setTunnelIdGenerator(TunnelIdGenerator tunnelIdGenerator) - { - this.tunnelIdGenerator = tunnelIdGenerator; - } - - public TunnelIdGenerator getTunnelIdGenerator() - { - return tunnelIdGenerator; - } +public class HttpTunnelServerChannelConfig implements ServerSocketChannelConfig { + + private ChannelPipelineFactory pipelineFactory; + + private final ServerSocketChannel realChannel; + + private TunnelIdGenerator tunnelIdGenerator = + new DefaultTunnelIdGenerator(); + + public HttpTunnelServerChannelConfig(ServerSocketChannel realChannel) { + this.realChannel = realChannel; + } + + private ServerSocketChannelConfig getWrappedConfig() { + return realChannel.getConfig(); + } + + public int getBacklog() { + return getWrappedConfig().getBacklog(); + } + + public int getReceiveBufferSize() { + return getWrappedConfig().getReceiveBufferSize(); + } + + public boolean isReuseAddress() { + return getWrappedConfig().isReuseAddress(); + } + + public void setBacklog(int backlog) { + getWrappedConfig().setBacklog(backlog); + } + + public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + getWrappedConfig().setPerformancePreferences(connectionTime, latency, + bandwidth); + } + + public void setReceiveBufferSize(int receiveBufferSize) { + getWrappedConfig().setReceiveBufferSize(receiveBufferSize); + } + + public void setReuseAddress(boolean reuseAddress) { + getWrappedConfig().setReuseAddress(reuseAddress); + } + + public ChannelBufferFactory getBufferFactory() { + return getWrappedConfig().getBufferFactory(); + } + + public int getConnectTimeoutMillis() { + return getWrappedConfig().getConnectTimeoutMillis(); + } + + public ChannelPipelineFactory getPipelineFactory() { + return pipelineFactory; + } + + public void setBufferFactory(ChannelBufferFactory bufferFactory) { + getWrappedConfig().setBufferFactory(bufferFactory); + } + + public void setConnectTimeoutMillis(int connectTimeoutMillis) { + getWrappedConfig().setConnectTimeoutMillis(connectTimeoutMillis); + } + + public boolean setOption(String name, Object value) { + if (name.equals("pipelineFactory")) { + setPipelineFactory((ChannelPipelineFactory) value); + return true; + } else if (name.equals("tunnelIdGenerator")) { + setTunnelIdGenerator((TunnelIdGenerator) value); + return true; + } else { + return getWrappedConfig().setOption(name, value); + } + } + + public void setOptions(Map options) { + for (Entry e: options.entrySet()) { + setOption(e.getKey(), e.getValue()); + } + } + + public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) { + this.pipelineFactory = pipelineFactory; + } + + public void setTunnelIdGenerator(TunnelIdGenerator tunnelIdGenerator) { + this.tunnelIdGenerator = tunnelIdGenerator; + } + + public TunnelIdGenerator getTunnelIdGenerator() { + return tunnelIdGenerator; + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java index 61ef849ca99..616f8e58e8e 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactory.java @@ -27,39 +27,40 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class HttpTunnelServerChannelFactory implements ServerSocketChannelFactory -{ +public class HttpTunnelServerChannelFactory implements + ServerSocketChannelFactory { - private final ServerSocketChannelFactory realConnectionFactory; + private final ServerSocketChannelFactory realConnectionFactory; - private final ChannelGroup realConnections; + private final ChannelGroup realConnections; - public HttpTunnelServerChannelFactory(ServerSocketChannelFactory realConnectionFactory) - { - this.realConnectionFactory = realConnectionFactory; - realConnections = new DefaultChannelGroup(); - } + public HttpTunnelServerChannelFactory( + ServerSocketChannelFactory realConnectionFactory) { + this.realConnectionFactory = realConnectionFactory; + realConnections = new DefaultChannelGroup(); + } - public HttpTunnelServerChannel newChannel(ChannelPipeline pipeline) - { - return new HttpTunnelServerChannel(this, pipeline); - } + public HttpTunnelServerChannel newChannel(ChannelPipeline pipeline) { + return new HttpTunnelServerChannel(this, pipeline); + } - ServerSocketChannel createRealChannel(HttpTunnelServerChannel channel, ServerMessageSwitch messageSwitch) - { - ChannelPipeline realChannelPipeline = Channels.pipeline(); - AcceptedServerChannelPipelineFactory realPipelineFactory = new AcceptedServerChannelPipelineFactory(messageSwitch); - realChannelPipeline.addFirst(TunnelWrappedServerChannelHandler.NAME, new TunnelWrappedServerChannelHandler( - channel, realPipelineFactory, realConnections)); - ServerSocketChannel newChannel = realConnectionFactory.newChannel(realChannelPipeline); - realConnections.add(newChannel); - return newChannel; - } + ServerSocketChannel createRealChannel(HttpTunnelServerChannel channel, + ServerMessageSwitch messageSwitch) { + ChannelPipeline realChannelPipeline = Channels.pipeline(); + AcceptedServerChannelPipelineFactory realPipelineFactory = + new AcceptedServerChannelPipelineFactory(messageSwitch); + realChannelPipeline.addFirst(TunnelWrappedServerChannelHandler.NAME, + new TunnelWrappedServerChannelHandler(channel, + realPipelineFactory, realConnections)); + ServerSocketChannel newChannel = + realConnectionFactory.newChannel(realChannelPipeline); + realConnections.add(newChannel); + return newChannel; + } - public void releaseExternalResources() - { - realConnections.close().awaitUninterruptibly(); - realConnectionFactory.releaseExternalResources(); - } + public void releaseExternalResources() { + realConnections.close().awaitUninterruptibly(); + realConnectionFactory.releaseExternalResources(); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java index 74454617ef4..d00b1c56de8 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSink.java @@ -30,70 +30,57 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class HttpTunnelServerChannelSink extends AbstractChannelSink -{ +class HttpTunnelServerChannelSink extends AbstractChannelSink { - private ChannelFutureListener closeHook; + private ChannelFutureListener closeHook; - private ServerSocketChannel realChannel; + private ServerSocketChannel realChannel; - public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception - { + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) + throws Exception { - if (e instanceof ChannelStateEvent) - { - ChannelStateEvent ev = (ChannelStateEvent) e; - switch (ev.getState()) - { - case OPEN : - if (Boolean.FALSE.equals(ev.getValue())) - { - realChannel.close().addListener(closeHook); - } - break; - case BOUND : - if (ev.getValue() != null) - { - realChannel.bind((SocketAddress) ev.getValue()).addListener(new ChannelFutureProxy(e.getFuture())); - } - else - { - realChannel.unbind().addListener(new ChannelFutureProxy(e.getFuture())); - } - break; - } - } - } + if (e instanceof ChannelStateEvent) { + ChannelStateEvent ev = (ChannelStateEvent) e; + switch (ev.getState()) { + case OPEN: + if (Boolean.FALSE.equals(ev.getValue())) { + realChannel.close().addListener(closeHook); + } + break; + case BOUND: + if (ev.getValue() != null) { + realChannel.bind((SocketAddress) ev.getValue()) + .addListener(new ChannelFutureProxy(e.getFuture())); + } else { + realChannel.unbind().addListener( + new ChannelFutureProxy(e.getFuture())); + } + break; + } + } + } - private final class ChannelFutureProxy implements ChannelFutureListener - { - private final ChannelFuture upstreamFuture; + private final class ChannelFutureProxy implements ChannelFutureListener { + private final ChannelFuture upstreamFuture; - ChannelFutureProxy(ChannelFuture upstreamFuture) - { - this.upstreamFuture = upstreamFuture; - } + ChannelFutureProxy(ChannelFuture upstreamFuture) { + this.upstreamFuture = upstreamFuture; + } - public void operationComplete(ChannelFuture future) throws Exception - { - if (future.isSuccess()) - { - upstreamFuture.setSuccess(); - } - else - { - upstreamFuture.setFailure(future.getCause()); - } - } - } + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + upstreamFuture.setSuccess(); + } else { + upstreamFuture.setFailure(future.getCause()); + } + } + } - public void setRealChannel(ServerSocketChannel realChannel) - { - this.realChannel = realChannel; - } + public void setRealChannel(ServerSocketChannel realChannel) { + this.realChannel = realChannel; + } - public void setCloseListener(ChannelFutureListener closeHook) - { - this.closeHook = closeHook; - } + public void setCloseListener(ChannelFutureListener closeHook) { + this.closeHook = closeHook; + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java b/src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java index 603d43008f1..a9be1df55c8 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/SaturationManager.java @@ -31,38 +31,39 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class SaturationManager -{ - private AtomicLong desaturationPoint; - private AtomicLong saturationPoint; - private final AtomicLong queueSize; - private final AtomicBoolean saturated; - - public SaturationManager(long desaturationPoint, long saturationPoint) - { - this.desaturationPoint = new AtomicLong(desaturationPoint); - this.saturationPoint = new AtomicLong(saturationPoint); - queueSize = new AtomicLong(0); - saturated = new AtomicBoolean(false); - } - - public SaturationStateChange queueSizeChanged(long sizeDelta) { - long newQueueSize = queueSize.addAndGet(sizeDelta); - if(newQueueSize <= desaturationPoint.get()) { - if(saturated.compareAndSet(true, false)) { - return DESATURATED; - } - } else if(newQueueSize > saturationPoint.get()) { - if(saturated.compareAndSet(false, true)) { - return SATURATED; - } - } - - return NO_CHANGE; - } - - public void updateThresholds(long desaturationPoint, long saturationPoint) { - this.desaturationPoint.set(desaturationPoint); - this.saturationPoint.set(saturationPoint); - } +class SaturationManager { + private AtomicLong desaturationPoint; + + private AtomicLong saturationPoint; + + private final AtomicLong queueSize; + + private final AtomicBoolean saturated; + + public SaturationManager(long desaturationPoint, long saturationPoint) { + this.desaturationPoint = new AtomicLong(desaturationPoint); + this.saturationPoint = new AtomicLong(saturationPoint); + queueSize = new AtomicLong(0); + saturated = new AtomicBoolean(false); + } + + public SaturationStateChange queueSizeChanged(long sizeDelta) { + long newQueueSize = queueSize.addAndGet(sizeDelta); + if (newQueueSize <= desaturationPoint.get()) { + if (saturated.compareAndSet(true, false)) { + return DESATURATED; + } + } else if (newQueueSize > saturationPoint.get()) { + if (saturated.compareAndSet(false, true)) { + return SATURATED; + } + } + + return NO_CHANGE; + } + + public void updateThresholds(long desaturationPoint, long saturationPoint) { + this.desaturationPoint.set(desaturationPoint); + this.saturationPoint.set(saturationPoint); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java b/src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java index f5eb8c8d4b6..bef8741d6cd 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/SaturationStateChange.java @@ -25,7 +25,5 @@ * @author OneDrum Ltd. */ enum SaturationStateChange { - NO_CHANGE, - DESATURATED, - SATURATED + NO_CHANGE, DESATURATED, SATURATED } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java index bc935e510dc..36fa17e2f6a 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitch.java @@ -43,226 +43,215 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class ServerMessageSwitch implements ServerMessageSwitchUpstreamInterface, ServerMessageSwitchDownstreamInterface -{ - - private static final InternalLogger LOG = InternalLoggerFactory.getInstance(ServerMessageSwitch.class.getName()); - - private final String tunnelIdPrefix; - - private final HttpTunnelAcceptedChannelFactory newChannelFactory; - - private final ConcurrentHashMap tunnelsById; - - public ServerMessageSwitch(HttpTunnelAcceptedChannelFactory newChannelFactory) - { - this.newChannelFactory = newChannelFactory; - tunnelIdPrefix = Long.toHexString(new Random().nextLong()); - tunnelsById = new ConcurrentHashMap(); - } - - public String createTunnel(InetSocketAddress remoteAddress) - { - String newTunnelId = String.format("%s_%s", tunnelIdPrefix, newChannelFactory.generateTunnelId()); - TunnelInfo newTunnel = new TunnelInfo(); - newTunnel.tunnelId = newTunnelId; - tunnelsById.put(newTunnelId, newTunnel); - newTunnel.localChannel = newChannelFactory.newChannel(newTunnelId, remoteAddress); - return newTunnelId; - } - - public boolean isOpenTunnel(String tunnelId) - { - TunnelInfo tunnel = tunnelsById.get(tunnelId); - return tunnel != null; - } - - public void pollOutboundData(String tunnelId, Channel channel) - { - TunnelInfo tunnel = tunnelsById.get(tunnelId); - if (tunnel == null) - { - if (LOG.isWarnEnabled()) - { - LOG.warn("Poll request for tunnel " + tunnelId + " which does not exist or already closed"); - } - respondAndClose(channel, - HttpTunnelMessageUtils.createRejection(null, "Unknown tunnel, possibly already closed")); - return; - } - - if (!tunnel.responseChannel.compareAndSet(null, channel)) - { - if (LOG.isWarnEnabled()) - { - LOG.warn("Duplicate poll request detected for tunnel " + tunnelId); - } - respondAndClose(channel, - HttpTunnelMessageUtils.createRejection(null, "Only one poll request at a time per tunnel allowed")); - return; - } - - sendQueuedData(tunnel); - } - - private void respondAndClose(Channel channel, HttpResponse response) - { - Channels.write(channel, response).addListener(ChannelFutureListener.CLOSE); - } - - private void sendQueuedData(TunnelInfo state) - { - Queue queuedData = state.queuedResponses; - Channel responseChannel = state.responseChannel.getAndSet(null); - if (responseChannel == null) - { - // no response channel, or another thread has already used it - return; - } - - if (LOG.isDebugEnabled()) - { - LOG.debug("sending response for tunnel id " + state.tunnelId + " to " + responseChannel.getRemoteAddress()); - } - QueuedResponse messageToSend = queuedData.poll(); - if (messageToSend == null) - { - // no data to send, restore the response channel and bail out - state.responseChannel.set(responseChannel); - return; - } - - HttpResponse response = HttpTunnelMessageUtils.createRecvDataResponse(messageToSend.data); - final ChannelFuture originalFuture = messageToSend.writeFuture; - Channels.write(responseChannel, response).addListener(new RelayedChannelFutureListener(originalFuture)); - } - - public TunnelStatus routeInboundData(String tunnelId, ChannelBuffer inboundData) - { - TunnelInfo tunnel = tunnelsById.get(tunnelId); - if (tunnel == null) - { - return TunnelStatus.CLOSED; - } - - if (tunnel.closing.get()) - { - // client has now been notified, forget the tunnel - tunnelsById.remove(tunnel); - return TunnelStatus.CLOSED; - } - - if (LOG.isDebugEnabled()) - { - LOG.debug("routing inbound data for tunnel " + tunnelId); - } - tunnel.localChannel.dataReceived(inboundData); - return TunnelStatus.ALIVE; - } - - public void clientCloseTunnel(String tunnelId) - { - TunnelInfo tunnel = tunnelsById.get(tunnelId); - tunnel.localChannel.clientClosed(); - tunnelsById.remove(tunnelId); - } - - public void serverCloseTunnel(String tunnelId) - { - TunnelInfo tunnel = tunnelsById.get(tunnelId); - tunnel.closing.set(true); - - Channel responseChannel = tunnel.responseChannel.getAndSet(null); - if (responseChannel == null) - { - // response channel is already in use, client will be notified - // of close at next opportunity - return; - } - - respondAndClose(responseChannel, HttpTunnelMessageUtils.createTunnelCloseResponse()); - // client has been notified, forget the tunnel - tunnelsById.remove(tunnelId); - } - - public void routeOutboundData(String tunnelId, ChannelBuffer data, ChannelFuture writeFuture) - { - TunnelInfo tunnel = tunnelsById.get(tunnelId); - if (tunnel == null) - { - // tunnel is closed - if (LOG.isWarnEnabled()) - { - LOG.warn("attempt made to send data out on tunnel id " + tunnelId + " which is unknown or closed"); - } - return; - } - - ChannelFutureAggregator aggregator = new ChannelFutureAggregator(writeFuture); - List fragments = WriteSplitter.split(data, HttpTunnelMessageUtils.MAX_BODY_SIZE); - - if (LOG.isDebugEnabled()) - { - LOG.debug("routing outbound data for tunnel " + tunnelId); - } - for (ChannelBuffer fragment : fragments) - { - ChannelFuture fragmentFuture = Channels.future(writeFuture.getChannel()); - aggregator.addFuture(fragmentFuture); - tunnel.queuedResponses.offer(new QueuedResponse(fragment, fragmentFuture)); - } - - sendQueuedData(tunnel); - } - - /** - * Used to pass the result received from one ChannelFutureListener to another verbatim. - */ - private final class RelayedChannelFutureListener implements ChannelFutureListener - { - private final ChannelFuture originalFuture; - - private RelayedChannelFutureListener(ChannelFuture originalFuture) - { - this.originalFuture = originalFuture; - } - - public void operationComplete(ChannelFuture future) throws Exception - { - if (future.isSuccess()) - { - originalFuture.setSuccess(); - } - else - { - originalFuture.setFailure(future.getCause()); - } - } - } - - private static final class TunnelInfo - { - public String tunnelId; - - public HttpTunnelAcceptedChannelReceiver localChannel; - - public final AtomicReference responseChannel = new AtomicReference(null); - - public final Queue queuedResponses = new ConcurrentLinkedQueue(); - - public final AtomicBoolean closing = new AtomicBoolean(false); - } - - private static final class QueuedResponse - { - public ChannelBuffer data; - - public ChannelFuture writeFuture; - - QueuedResponse(ChannelBuffer data, ChannelFuture writeFuture) - { - this.data = data; - this.writeFuture = writeFuture; - } - } +class ServerMessageSwitch implements ServerMessageSwitchUpstreamInterface, + ServerMessageSwitchDownstreamInterface { + + private static final InternalLogger LOG = InternalLoggerFactory + .getInstance(ServerMessageSwitch.class.getName()); + + private final String tunnelIdPrefix; + + private final HttpTunnelAcceptedChannelFactory newChannelFactory; + + private final ConcurrentHashMap tunnelsById; + + public ServerMessageSwitch( + HttpTunnelAcceptedChannelFactory newChannelFactory) { + this.newChannelFactory = newChannelFactory; + tunnelIdPrefix = Long.toHexString(new Random().nextLong()); + tunnelsById = new ConcurrentHashMap(); + } + + public String createTunnel(InetSocketAddress remoteAddress) { + String newTunnelId = + String.format("%s_%s", tunnelIdPrefix, + newChannelFactory.generateTunnelId()); + TunnelInfo newTunnel = new TunnelInfo(); + newTunnel.tunnelId = newTunnelId; + tunnelsById.put(newTunnelId, newTunnel); + newTunnel.localChannel = + newChannelFactory.newChannel(newTunnelId, remoteAddress); + return newTunnelId; + } + + public boolean isOpenTunnel(String tunnelId) { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + return tunnel != null; + } + + public void pollOutboundData(String tunnelId, Channel channel) { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + if (tunnel == null) { + if (LOG.isWarnEnabled()) { + LOG.warn("Poll request for tunnel " + tunnelId + + " which does not exist or already closed"); + } + respondAndClose(channel, HttpTunnelMessageUtils.createRejection( + null, "Unknown tunnel, possibly already closed")); + return; + } + + if (!tunnel.responseChannel.compareAndSet(null, channel)) { + if (LOG.isWarnEnabled()) { + LOG.warn("Duplicate poll request detected for tunnel " + + tunnelId); + } + respondAndClose(channel, HttpTunnelMessageUtils.createRejection( + null, "Only one poll request at a time per tunnel allowed")); + return; + } + + sendQueuedData(tunnel); + } + + private void respondAndClose(Channel channel, HttpResponse response) { + Channels.write(channel, response).addListener( + ChannelFutureListener.CLOSE); + } + + private void sendQueuedData(TunnelInfo state) { + Queue queuedData = state.queuedResponses; + Channel responseChannel = state.responseChannel.getAndSet(null); + if (responseChannel == null) { + // no response channel, or another thread has already used it + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("sending response for tunnel id " + state.tunnelId + + " to " + responseChannel.getRemoteAddress()); + } + QueuedResponse messageToSend = queuedData.poll(); + if (messageToSend == null) { + // no data to send, restore the response channel and bail out + state.responseChannel.set(responseChannel); + return; + } + + HttpResponse response = + HttpTunnelMessageUtils + .createRecvDataResponse(messageToSend.data); + final ChannelFuture originalFuture = messageToSend.writeFuture; + Channels.write(responseChannel, response).addListener( + new RelayedChannelFutureListener(originalFuture)); + } + + public TunnelStatus routeInboundData(String tunnelId, + ChannelBuffer inboundData) { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + if (tunnel == null) { + return TunnelStatus.CLOSED; + } + + if (tunnel.closing.get()) { + // client has now been notified, forget the tunnel + tunnelsById.remove(tunnel); + return TunnelStatus.CLOSED; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("routing inbound data for tunnel " + tunnelId); + } + tunnel.localChannel.dataReceived(inboundData); + return TunnelStatus.ALIVE; + } + + public void clientCloseTunnel(String tunnelId) { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + tunnel.localChannel.clientClosed(); + tunnelsById.remove(tunnelId); + } + + public void serverCloseTunnel(String tunnelId) { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + tunnel.closing.set(true); + + Channel responseChannel = tunnel.responseChannel.getAndSet(null); + if (responseChannel == null) { + // response channel is already in use, client will be notified + // of close at next opportunity + return; + } + + respondAndClose(responseChannel, + HttpTunnelMessageUtils.createTunnelCloseResponse()); + // client has been notified, forget the tunnel + tunnelsById.remove(tunnelId); + } + + public void routeOutboundData(String tunnelId, ChannelBuffer data, + ChannelFuture writeFuture) { + TunnelInfo tunnel = tunnelsById.get(tunnelId); + if (tunnel == null) { + // tunnel is closed + if (LOG.isWarnEnabled()) { + LOG.warn("attempt made to send data out on tunnel id " + + tunnelId + " which is unknown or closed"); + } + return; + } + + ChannelFutureAggregator aggregator = + new ChannelFutureAggregator(writeFuture); + List fragments = + WriteSplitter.split(data, HttpTunnelMessageUtils.MAX_BODY_SIZE); + + if (LOG.isDebugEnabled()) { + LOG.debug("routing outbound data for tunnel " + tunnelId); + } + for (ChannelBuffer fragment: fragments) { + ChannelFuture fragmentFuture = + Channels.future(writeFuture.getChannel()); + aggregator.addFuture(fragmentFuture); + tunnel.queuedResponses.offer(new QueuedResponse(fragment, + fragmentFuture)); + } + + sendQueuedData(tunnel); + } + + /** + * Used to pass the result received from one ChannelFutureListener to another verbatim. + */ + private final class RelayedChannelFutureListener implements + ChannelFutureListener { + private final ChannelFuture originalFuture; + + private RelayedChannelFutureListener(ChannelFuture originalFuture) { + this.originalFuture = originalFuture; + } + + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + originalFuture.setSuccess(); + } else { + originalFuture.setFailure(future.getCause()); + } + } + } + + private static final class TunnelInfo { + public String tunnelId; + + public HttpTunnelAcceptedChannelReceiver localChannel; + + public final AtomicReference responseChannel = + new AtomicReference(null); + + public final Queue queuedResponses = + new ConcurrentLinkedQueue(); + + public final AtomicBoolean closing = new AtomicBoolean(false); + } + + private static final class QueuedResponse { + public ChannelBuffer data; + + public ChannelFuture writeFuture; + + QueuedResponse(ChannelBuffer data, ChannelFuture writeFuture) { + this.data = data; + this.writeFuture = writeFuture; + } + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java index 45d4f831677..cc29a3c2868 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchDownstreamInterface.java @@ -26,11 +26,11 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -interface ServerMessageSwitchDownstreamInterface -{ +interface ServerMessageSwitchDownstreamInterface { - public void serverCloseTunnel(String tunnelId); + public void serverCloseTunnel(String tunnelId); - public void routeOutboundData(String tunnelId, ChannelBuffer data, ChannelFuture writeFuture); + public void routeOutboundData(String tunnelId, ChannelBuffer data, + ChannelFuture writeFuture); } \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java index 8af74a217d5..436f68ecf06 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchUpstreamInterface.java @@ -30,28 +30,28 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -interface ServerMessageSwitchUpstreamInterface -{ +interface ServerMessageSwitchUpstreamInterface { - public String createTunnel(InetSocketAddress remoteAddress); + public String createTunnel(InetSocketAddress remoteAddress); - public boolean isOpenTunnel(String tunnelId); + public boolean isOpenTunnel(String tunnelId); - public void clientCloseTunnel(String tunnelId); + public void clientCloseTunnel(String tunnelId); - /** - * Passes some received data from a client for forwarding to the server's view - * of the tunnel. - * @return the current status of the tunnel. ALIVE indicates the tunnel is still - * functional, CLOSED indicates it is closed and the client should be notified - * of this (and will be forgotten after this notification). - */ - public TunnelStatus routeInboundData(String tunnelId, ChannelBuffer inboundData); + /** + * Passes some received data from a client for forwarding to the server's view + * of the tunnel. + * @return the current status of the tunnel. ALIVE indicates the tunnel is still + * functional, CLOSED indicates it is closed and the client should be notified + * of this (and will be forgotten after this notification). + */ + public TunnelStatus routeInboundData(String tunnelId, + ChannelBuffer inboundData); - public void pollOutboundData(String tunnelId, Channel responseChannel); + public void pollOutboundData(String tunnelId, Channel responseChannel); - public static enum TunnelStatus { - ALIVE, CLOSED - } + public static enum TunnelStatus { + ALIVE, CLOSED + } } \ No newline at end of file diff --git a/src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java b/src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java index 91c5d4e3568..9eafdf5cceb 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/TunnelIdGenerator.java @@ -24,15 +24,14 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public interface TunnelIdGenerator -{ +public interface TunnelIdGenerator { - /** - * Generates the next tunnel ID to be used, which must be unique - * (i.e. ensure with high probability that it will not clash with - * an existing tunnel ID). This method must be thread safe, and - * preferably lock free. - */ - public String generateId(); + /** + * Generates the next tunnel ID to be used, which must be unique + * (i.e. ensure with high probability that it will not clash with + * an existing tunnel ID). This method must be thread safe, and + * preferably lock free. + */ + public String generateId(); } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java b/src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java index 963dbe421a8..37943a7e4a6 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/TunnelWrappedServerChannelHandler.java @@ -29,56 +29,56 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -class TunnelWrappedServerChannelHandler extends SimpleChannelUpstreamHandler -{ +class TunnelWrappedServerChannelHandler extends SimpleChannelUpstreamHandler { - public static final String NAME = "TunnelWrappedServerChannelHandler"; + public static final String NAME = "TunnelWrappedServerChannelHandler"; - private final HttpTunnelServerChannel tunnelChannel; + private final HttpTunnelServerChannel tunnelChannel; - private final AcceptedServerChannelPipelineFactory pipelineFactory; + private final AcceptedServerChannelPipelineFactory pipelineFactory; - private final ChannelGroup allChannels; + private final ChannelGroup allChannels; - public TunnelWrappedServerChannelHandler(HttpTunnelServerChannel tunnelChannel, - AcceptedServerChannelPipelineFactory pipelineFactory, ChannelGroup allChannels) - { - this.tunnelChannel = tunnelChannel; - this.pipelineFactory = pipelineFactory; - this.allChannels = allChannels; - } + public TunnelWrappedServerChannelHandler( + HttpTunnelServerChannel tunnelChannel, + AcceptedServerChannelPipelineFactory pipelineFactory, + ChannelGroup allChannels) { + this.tunnelChannel = tunnelChannel; + this.pipelineFactory = pipelineFactory; + this.allChannels = allChannels; + } - @Override - public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - e.getChannel().getConfig().setPipelineFactory(pipelineFactory); - super.channelOpen(ctx, e); - } + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + e.getChannel().getConfig().setPipelineFactory(pipelineFactory); + super.channelOpen(ctx, e); + } - @Override - public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - Channels.fireChannelBound(tunnelChannel, (SocketAddress) e.getValue()); - super.channelBound(ctx, e); - } + @Override + public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + Channels.fireChannelBound(tunnelChannel, (SocketAddress) e.getValue()); + super.channelBound(ctx, e); + } - @Override - public void channelUnbound(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - Channels.fireChannelUnbound(tunnelChannel); - super.channelUnbound(ctx, e); - } + @Override + public void channelUnbound(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + Channels.fireChannelUnbound(tunnelChannel); + super.channelUnbound(ctx, e); + } - @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - Channels.fireChannelClosed(tunnelChannel); - super.channelClosed(ctx, e); - } + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + Channels.fireChannelClosed(tunnelChannel); + super.channelClosed(ctx, e); + } - @Override - public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception - { - allChannels.add(e.getChildChannel()); - } + @Override + public void childChannelOpen(ChannelHandlerContext ctx, + ChildChannelStateEvent e) throws Exception { + allChannels.add(e.getChildChannel()); + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java b/src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java index f0fd2c820c6..94380e96cba 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/WriteFragmenter.java @@ -37,42 +37,38 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public class WriteFragmenter extends SimpleChannelDownstreamHandler -{ +public class WriteFragmenter extends SimpleChannelDownstreamHandler { - public static final String NAME = "writeFragmenter"; + public static final String NAME = "writeFragmenter"; - private int splitThreshold; + private int splitThreshold; - public WriteFragmenter(int splitThreshold) - { - this.splitThreshold = splitThreshold; - } + public WriteFragmenter(int splitThreshold) { + this.splitThreshold = splitThreshold; + } - public void setSplitThreshold(int splitThreshold) - { - this.splitThreshold = splitThreshold; - } + public void setSplitThreshold(int splitThreshold) { + this.splitThreshold = splitThreshold; + } - @Override - public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - ChannelBuffer data = (ChannelBuffer) e.getMessage(); + @Override + public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + ChannelBuffer data = (ChannelBuffer) e.getMessage(); - if (data.readableBytes() <= splitThreshold) - { - super.writeRequested(ctx, e); - } - else - { - List fragments = WriteSplitter.split(data, splitThreshold); - ChannelFutureAggregator aggregator = new ChannelFutureAggregator(e.getFuture()); - for (ChannelBuffer fragment : fragments) - { - ChannelFuture fragmentFuture = Channels.future(ctx.getChannel(), true); - aggregator.addFuture(fragmentFuture); - Channels.write(ctx, fragmentFuture, fragment); - } - } - } + if (data.readableBytes() <= splitThreshold) { + super.writeRequested(ctx, e); + } else { + List fragments = + WriteSplitter.split(data, splitThreshold); + ChannelFutureAggregator aggregator = + new ChannelFutureAggregator(e.getFuture()); + for (ChannelBuffer fragment: fragments) { + ChannelFuture fragmentFuture = + Channels.future(ctx.getChannel(), true); + aggregator.addFuture(fragmentFuture); + Channels.write(ctx, fragmentFuture, fragment); + } + } + } } diff --git a/src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java b/src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java index 42657373911..37a9f772e5b 100644 --- a/src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java +++ b/src/main/java/org/jboss/netty/channel/socket/http/WriteSplitter.java @@ -29,31 +29,29 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) * @author OneDrum Ltd. */ -public final class WriteSplitter -{ +public final class WriteSplitter { - public static List split(ChannelBuffer buffer, int splitThreshold) - { - int listSize = (int) ((float) buffer.readableBytes() / splitThreshold); - ArrayList fragmentList = new ArrayList(listSize); + public static List split(ChannelBuffer buffer, + int splitThreshold) { + int listSize = (int) ((float) buffer.readableBytes() / splitThreshold); + ArrayList fragmentList = + new ArrayList(listSize); - if (buffer.readableBytes() > splitThreshold) - { - int slicePosition = buffer.readerIndex(); - while (slicePosition < buffer.writerIndex()) - { - int chunkSize = Math.min(splitThreshold, buffer.writerIndex() - slicePosition); - ChannelBuffer chunk = buffer.slice(slicePosition, chunkSize); - fragmentList.add(chunk); - slicePosition += chunkSize; - } - } - else - { - fragmentList.add(ChannelBuffers.wrappedBuffer(buffer)); - } + if (buffer.readableBytes() > splitThreshold) { + int slicePosition = buffer.readerIndex(); + while (slicePosition < buffer.writerIndex()) { + int chunkSize = + Math.min(splitThreshold, buffer.writerIndex() - + slicePosition); + ChannelBuffer chunk = buffer.slice(slicePosition, chunkSize); + fragmentList.add(chunk); + slicePosition += chunkSize; + } + } else { + fragmentList.add(ChannelBuffers.wrappedBuffer(buffer)); + } - return fragmentList; - } + return fragmentList; + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java b/src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java index 31664d4e007..f5114c42aca 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/AcceptedServerChannelRequestDispatchTest.java @@ -41,207 +41,216 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class AcceptedServerChannelRequestDispatchTest -{ - - private static final String HOST = "test.server.com"; - - private static final String KNOWN_TUNNEL_ID = "1"; - - protected static final String UNKNOWN_TUNNEL_ID = "unknownTunnel"; - - JUnit4Mockery mockContext = new JUnit4Mockery(); - - private AcceptedServerChannelRequestDispatch handler; - - FakeSocketChannel channel; - - private FakeChannelSink sink; - - ServerMessageSwitchUpstreamInterface messageSwitch; - - @Before - public void setUp() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); - messageSwitch = mockContext.mock(ServerMessageSwitchUpstreamInterface.class); - handler = new AcceptedServerChannelRequestDispatch(messageSwitch); - pipeline.addLast(AcceptedServerChannelRequestDispatch.NAME, handler); - sink = new FakeChannelSink(); - channel = new FakeSocketChannel(null, null, pipeline, sink); - channel.remoteAddress = InetSocketAddress.createUnresolved("test.client.com", 51231); - - mockContext.checking(new Expectations() - { - { - ignoring(messageSwitch).isOpenTunnel(KNOWN_TUNNEL_ID); - will(returnValue(true)); - } - }); - } - - @Test - public void testTunnelOpenRequest() - { - mockContext.checking(new Expectations() - { - { - one(messageSwitch).createTunnel(channel.remoteAddress); - will(returnValue(KNOWN_TUNNEL_ID)); - } - }); - - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createOpenTunnelRequest(HOST)); - assertEquals(1, sink.events.size()); - HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpResponse.class); - assertTrue(HttpTunnelMessageUtils.isTunnelOpenResponse(response)); - } - - @Test - public void testTunnelCloseRequest() - { - mockContext.checking(new Expectations() - { - { - one(messageSwitch).clientCloseTunnel(KNOWN_TUNNEL_ID); - } - }); - - HttpRequest request = HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, KNOWN_TUNNEL_ID); - Channels.fireMessageReceived(channel, request); - assertEquals(1, sink.events.size()); - ChannelEvent responseEvent = sink.events.poll(); - HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseEvent, HttpResponse.class); - assertTrue(HttpTunnelMessageUtils.isTunnelCloseResponse(response)); - checkClosesAfterWrite(responseEvent); - } - - @Test - public void testTunnelCloseRequestWithoutTunnelIdRejected() - { - HttpRequest request = HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, null); - checkRequestWithoutTunnelIdIsRejected(request); - } - - @Test - public void testTunnelCloseRequestWithUnknownTunnelId() - { - HttpRequest request = HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, UNKNOWN_TUNNEL_ID); - checkRequestWithUnknownTunnelIdIsRejected(request); - } - - @Test - public void testSendDataRequest() - { - final ChannelBuffer expectedData = ChannelBuffers.dynamicBuffer(); - expectedData.writeLong(1234L); - mockContext.checking(new Expectations() - { - { - one(messageSwitch).routeInboundData(KNOWN_TUNNEL_ID, expectedData); - } - }); - - HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, KNOWN_TUNNEL_ID, expectedData); - Channels.fireMessageReceived(channel, request); - - assertEquals(1, sink.events.size()); - HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpResponse.class); - assertTrue(HttpTunnelMessageUtils.isOKResponse(response)); - } - - @Test - public void testSendDataRequestWithNoContentRejected() - { - HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, KNOWN_TUNNEL_ID, - ChannelBuffers.dynamicBuffer()); - Channels.fireMessageReceived(channel, request); - - assertEquals(1, sink.events.size()); - checkResponseIsRejection("Send data requests must contain data"); - } - - @Test - public void testSendDataRequestForUnknownTunnelIdRejected() - { - HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, UNKNOWN_TUNNEL_ID, - ChannelBuffers.dynamicBuffer()); - checkRequestWithUnknownTunnelIdIsRejected(request); - } - - @Test - public void testSendDataRequestWithoutTunnelIdRejected() - { - HttpRequest request = HttpTunnelMessageUtils.createSendDataRequest(HOST, null, ChannelBuffers.dynamicBuffer()); - checkRequestWithoutTunnelIdIsRejected(request); - } - - @Test - public void testReceiveDataRequest() - { - mockContext.checking(new Expectations() - { - { - one(messageSwitch).pollOutboundData(KNOWN_TUNNEL_ID, channel); - } - }); - HttpRequest request = HttpTunnelMessageUtils.createReceiveDataRequest(HOST, KNOWN_TUNNEL_ID); - Channels.fireMessageReceived(channel, request); - } - - @Test - public void testReceiveDataRequestWithoutTunnelIdRejected() - { - HttpRequest request = HttpTunnelMessageUtils.createReceiveDataRequest(HOST, null); - checkRequestWithoutTunnelIdIsRejected(request); - } - - @Test - public void testReceiveDataRequestForUnknownTunnelIdRejected() - { - HttpRequest request = HttpTunnelMessageUtils.createReceiveDataRequest(HOST, UNKNOWN_TUNNEL_ID); - checkRequestWithUnknownTunnelIdIsRejected(request); - } - - private void checkRequestWithoutTunnelIdIsRejected(HttpRequest request) - { - Channels.fireMessageReceived(channel, request); - assertEquals(1, sink.events.size()); - ChannelEvent responseEvent = checkResponseIsRejection("no tunnel id specified in request"); - checkClosesAfterWrite(responseEvent); - } - - private void checkRequestWithUnknownTunnelIdIsRejected(HttpRequest request) - { - mockContext.checking(new Expectations() - { - { - one(messageSwitch).isOpenTunnel(UNKNOWN_TUNNEL_ID); - will(returnValue(false)); - } - }); - - Channels.fireMessageReceived(channel, request); - assertEquals(1, sink.events.size()); - ChannelEvent responseEvent = checkResponseIsRejection("specified tunnel is either closed or does not exist"); - checkClosesAfterWrite(responseEvent); - } - - private ChannelEvent checkResponseIsRejection(String errorMessage) - { - ChannelEvent responseEvent = sink.events.poll(); - - HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseEvent, HttpResponse.class); - assertTrue(HttpTunnelMessageUtils.isRejection(response)); - assertEquals(errorMessage, HttpTunnelMessageUtils.extractErrorMessage(response)); - - return responseEvent; - } - - private void checkClosesAfterWrite(ChannelEvent responseEvent) - { - responseEvent.getFuture().setSuccess(); - assertEquals(1, sink.events.size()); - NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.OPEN, false); - } +public class AcceptedServerChannelRequestDispatchTest { + + private static final String HOST = "test.server.com"; + + private static final String KNOWN_TUNNEL_ID = "1"; + + protected static final String UNKNOWN_TUNNEL_ID = "unknownTunnel"; + + JUnit4Mockery mockContext = new JUnit4Mockery(); + + private AcceptedServerChannelRequestDispatch handler; + + FakeSocketChannel channel; + + private FakeChannelSink sink; + + ServerMessageSwitchUpstreamInterface messageSwitch; + + @Before + public void setUp() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + messageSwitch = + mockContext.mock(ServerMessageSwitchUpstreamInterface.class); + handler = new AcceptedServerChannelRequestDispatch(messageSwitch); + pipeline.addLast(AcceptedServerChannelRequestDispatch.NAME, handler); + sink = new FakeChannelSink(); + channel = new FakeSocketChannel(null, null, pipeline, sink); + channel.remoteAddress = + InetSocketAddress.createUnresolved("test.client.com", 51231); + + mockContext.checking(new Expectations() { + { + ignoring(messageSwitch).isOpenTunnel(KNOWN_TUNNEL_ID); + will(returnValue(true)); + } + }); + } + + @Test + public void testTunnelOpenRequest() { + mockContext.checking(new Expectations() { + { + one(messageSwitch).createTunnel(channel.remoteAddress); + will(returnValue(KNOWN_TUNNEL_ID)); + } + }); + + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createOpenTunnelRequest(HOST)); + assertEquals(1, sink.events.size()); + HttpResponse response = + NettyTestUtils.checkIsDownstreamMessageEvent( + sink.events.poll(), HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isTunnelOpenResponse(response)); + } + + @Test + public void testTunnelCloseRequest() { + mockContext.checking(new Expectations() { + { + one(messageSwitch).clientCloseTunnel(KNOWN_TUNNEL_ID); + } + }); + + HttpRequest request = + HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, + KNOWN_TUNNEL_ID); + Channels.fireMessageReceived(channel, request); + assertEquals(1, sink.events.size()); + ChannelEvent responseEvent = sink.events.poll(); + HttpResponse response = + NettyTestUtils.checkIsDownstreamMessageEvent(responseEvent, + HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isTunnelCloseResponse(response)); + checkClosesAfterWrite(responseEvent); + } + + @Test + public void testTunnelCloseRequestWithoutTunnelIdRejected() { + HttpRequest request = + HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, null); + checkRequestWithoutTunnelIdIsRejected(request); + } + + @Test + public void testTunnelCloseRequestWithUnknownTunnelId() { + HttpRequest request = + HttpTunnelMessageUtils.createCloseTunnelRequest(HOST, + UNKNOWN_TUNNEL_ID); + checkRequestWithUnknownTunnelIdIsRejected(request); + } + + @Test + public void testSendDataRequest() { + final ChannelBuffer expectedData = ChannelBuffers.dynamicBuffer(); + expectedData.writeLong(1234L); + mockContext.checking(new Expectations() { + { + one(messageSwitch).routeInboundData(KNOWN_TUNNEL_ID, + expectedData); + } + }); + + HttpRequest request = + HttpTunnelMessageUtils.createSendDataRequest(HOST, + KNOWN_TUNNEL_ID, expectedData); + Channels.fireMessageReceived(channel, request); + + assertEquals(1, sink.events.size()); + HttpResponse response = + NettyTestUtils.checkIsDownstreamMessageEvent( + sink.events.poll(), HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isOKResponse(response)); + } + + @Test + public void testSendDataRequestWithNoContentRejected() { + HttpRequest request = + HttpTunnelMessageUtils.createSendDataRequest(HOST, + KNOWN_TUNNEL_ID, ChannelBuffers.dynamicBuffer()); + Channels.fireMessageReceived(channel, request); + + assertEquals(1, sink.events.size()); + checkResponseIsRejection("Send data requests must contain data"); + } + + @Test + public void testSendDataRequestForUnknownTunnelIdRejected() { + HttpRequest request = + HttpTunnelMessageUtils.createSendDataRequest(HOST, + UNKNOWN_TUNNEL_ID, ChannelBuffers.dynamicBuffer()); + checkRequestWithUnknownTunnelIdIsRejected(request); + } + + @Test + public void testSendDataRequestWithoutTunnelIdRejected() { + HttpRequest request = + HttpTunnelMessageUtils.createSendDataRequest(HOST, null, + ChannelBuffers.dynamicBuffer()); + checkRequestWithoutTunnelIdIsRejected(request); + } + + @Test + public void testReceiveDataRequest() { + mockContext.checking(new Expectations() { + { + one(messageSwitch).pollOutboundData(KNOWN_TUNNEL_ID, channel); + } + }); + HttpRequest request = + HttpTunnelMessageUtils.createReceiveDataRequest(HOST, + KNOWN_TUNNEL_ID); + Channels.fireMessageReceived(channel, request); + } + + @Test + public void testReceiveDataRequestWithoutTunnelIdRejected() { + HttpRequest request = + HttpTunnelMessageUtils.createReceiveDataRequest(HOST, null); + checkRequestWithoutTunnelIdIsRejected(request); + } + + @Test + public void testReceiveDataRequestForUnknownTunnelIdRejected() { + HttpRequest request = + HttpTunnelMessageUtils.createReceiveDataRequest(HOST, + UNKNOWN_TUNNEL_ID); + checkRequestWithUnknownTunnelIdIsRejected(request); + } + + private void checkRequestWithoutTunnelIdIsRejected(HttpRequest request) { + Channels.fireMessageReceived(channel, request); + assertEquals(1, sink.events.size()); + ChannelEvent responseEvent = + checkResponseIsRejection("no tunnel id specified in request"); + checkClosesAfterWrite(responseEvent); + } + + private void checkRequestWithUnknownTunnelIdIsRejected(HttpRequest request) { + mockContext.checking(new Expectations() { + { + one(messageSwitch).isOpenTunnel(UNKNOWN_TUNNEL_ID); + will(returnValue(false)); + } + }); + + Channels.fireMessageReceived(channel, request); + assertEquals(1, sink.events.size()); + ChannelEvent responseEvent = + checkResponseIsRejection("specified tunnel is either closed or does not exist"); + checkClosesAfterWrite(responseEvent); + } + + private ChannelEvent checkResponseIsRejection(String errorMessage) { + ChannelEvent responseEvent = sink.events.poll(); + + HttpResponse response = + NettyTestUtils.checkIsDownstreamMessageEvent(responseEvent, + HttpResponse.class); + assertTrue(HttpTunnelMessageUtils.isRejection(response)); + assertEquals(errorMessage, + HttpTunnelMessageUtils.extractErrorMessage(response)); + + return responseEvent; + } + + private void checkClosesAfterWrite(ChannelEvent responseEvent) { + responseEvent.getFuture().setSuccess(); + assertEquals(1, sink.events.size()); + NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.OPEN, + false); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java index 1372539af16..b56b9b781a9 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelConfig.java @@ -31,207 +31,159 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeChannelConfig implements SocketChannelConfig -{ +public class FakeChannelConfig implements SocketChannelConfig { - private int receiveBufferSize = 1024; + private int receiveBufferSize = 1024; - private int sendBufferSize = 1024; + private int sendBufferSize = 1024; - private int soLinger = 500; + private int soLinger = 500; - private int trafficClass = 0; + private int trafficClass = 0; - private boolean keepAlive = true; + private boolean keepAlive = true; - private boolean reuseAddress = true; + private boolean reuseAddress = true; - private boolean tcpNoDelay = false; + private boolean tcpNoDelay = false; - private ChannelBufferFactory bufferFactory = new HeapChannelBufferFactory(); + private ChannelBufferFactory bufferFactory = new HeapChannelBufferFactory(); - private int connectTimeout = 5000; + private int connectTimeout = 5000; - private ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() - { - public ChannelPipeline getPipeline() throws Exception - { - return Channels.pipeline(); - } - }; - - private int writeTimeout = 3000; + private ChannelPipelineFactory pipelineFactory = + new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(); + } + }; - public int getReceiveBufferSize() - { - return receiveBufferSize; - } - - public void setReceiveBufferSize(int receiveBufferSize) - { - this.receiveBufferSize = receiveBufferSize; - } - - public int getSendBufferSize() - { - return sendBufferSize; - } - - public void setSendBufferSize(int sendBufferSize) - { - this.sendBufferSize = sendBufferSize; - } - - public int getSoLinger() - { - return soLinger; - } - - public void setSoLinger(int soLinger) - { - this.soLinger = soLinger; - } - - public int getTrafficClass() - { - return trafficClass; - } - - public void setTrafficClass(int trafficClass) - { - this.trafficClass = trafficClass; - } - - public boolean isKeepAlive() - { - return keepAlive; - } - - public void setKeepAlive(boolean keepAlive) - { - this.keepAlive = keepAlive; - } - - public boolean isReuseAddress() - { - return reuseAddress; - } - - public void setReuseAddress(boolean reuseAddress) - { - this.reuseAddress = reuseAddress; - } - - public boolean isTcpNoDelay() - { - return tcpNoDelay; - } - - public void setTcpNoDelay(boolean tcpNoDelay) - { - this.tcpNoDelay = tcpNoDelay; - } - - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) - { - // do nothing - } - - public ChannelBufferFactory getBufferFactory() - { - return bufferFactory; - } - - public void setBufferFactory(ChannelBufferFactory bufferFactory) - { - this.bufferFactory = bufferFactory; - } - - public int getConnectTimeoutMillis() - { - return connectTimeout; - } - - public void setConnectTimeoutMillis(int connectTimeoutMillis) - { - connectTimeout = connectTimeoutMillis; - } - - public ChannelPipelineFactory getPipelineFactory() - { - return pipelineFactory; - } - - public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) - { - this.pipelineFactory = pipelineFactory; - } - - public int getWriteTimeoutMillis() - { - return writeTimeout; - } - - public void setWriteTimeoutMillis(int writeTimeoutMillis) - { - writeTimeout = writeTimeoutMillis; - } - - public boolean setOption(String key, Object value) - { - if (key.equals("pipelineFactory")) - { - setPipelineFactory((ChannelPipelineFactory) value); - } - else if (key.equals("connectTimeoutMillis")) - { - setConnectTimeoutMillis(ConversionUtil.toInt(value)); - } - else if (key.equals("bufferFactory")) - { - setBufferFactory((ChannelBufferFactory) value); - } - else if (key.equals("receiveBufferSize")) - { - setReceiveBufferSize(ConversionUtil.toInt(value)); - } - else if (key.equals("sendBufferSize")) - { - setSendBufferSize(ConversionUtil.toInt(value)); - } - else if (key.equals("tcpNoDelay")) - { - setTcpNoDelay(ConversionUtil.toBoolean(value)); - } - else if (key.equals("keepAlive")) - { - setKeepAlive(ConversionUtil.toBoolean(value)); - } - else if (key.equals("reuseAddress")) - { - setReuseAddress(ConversionUtil.toBoolean(value)); - } - else if (key.equals("soLinger")) - { - setSoLinger(ConversionUtil.toInt(value)); - } - else if (key.equals("trafficClass")) - { - setTrafficClass(ConversionUtil.toInt(value)); - } - else - { - return false; - } - return true; - } - - public void setOptions(Map options) - { - for (Entry e : options.entrySet()) - { - setOption(e.getKey(), e.getValue()); - } - } + private int writeTimeout = 3000; + + public int getReceiveBufferSize() { + return receiveBufferSize; + } + + public void setReceiveBufferSize(int receiveBufferSize) { + this.receiveBufferSize = receiveBufferSize; + } + + public int getSendBufferSize() { + return sendBufferSize; + } + + public void setSendBufferSize(int sendBufferSize) { + this.sendBufferSize = sendBufferSize; + } + + public int getSoLinger() { + return soLinger; + } + + public void setSoLinger(int soLinger) { + this.soLinger = soLinger; + } + + public int getTrafficClass() { + return trafficClass; + } + + public void setTrafficClass(int trafficClass) { + this.trafficClass = trafficClass; + } + + public boolean isKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public boolean isReuseAddress() { + return reuseAddress; + } + + public void setReuseAddress(boolean reuseAddress) { + this.reuseAddress = reuseAddress; + } + + public boolean isTcpNoDelay() { + return tcpNoDelay; + } + + public void setTcpNoDelay(boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + } + + public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + // do nothing + } + + public ChannelBufferFactory getBufferFactory() { + return bufferFactory; + } + + public void setBufferFactory(ChannelBufferFactory bufferFactory) { + this.bufferFactory = bufferFactory; + } + + public int getConnectTimeoutMillis() { + return connectTimeout; + } + + public void setConnectTimeoutMillis(int connectTimeoutMillis) { + connectTimeout = connectTimeoutMillis; + } + + public ChannelPipelineFactory getPipelineFactory() { + return pipelineFactory; + } + + public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) { + this.pipelineFactory = pipelineFactory; + } + + public int getWriteTimeoutMillis() { + return writeTimeout; + } + + public void setWriteTimeoutMillis(int writeTimeoutMillis) { + writeTimeout = writeTimeoutMillis; + } + + public boolean setOption(String key, Object value) { + if (key.equals("pipelineFactory")) { + setPipelineFactory((ChannelPipelineFactory) value); + } else if (key.equals("connectTimeoutMillis")) { + setConnectTimeoutMillis(ConversionUtil.toInt(value)); + } else if (key.equals("bufferFactory")) { + setBufferFactory((ChannelBufferFactory) value); + } else if (key.equals("receiveBufferSize")) { + setReceiveBufferSize(ConversionUtil.toInt(value)); + } else if (key.equals("sendBufferSize")) { + setSendBufferSize(ConversionUtil.toInt(value)); + } else if (key.equals("tcpNoDelay")) { + setTcpNoDelay(ConversionUtil.toBoolean(value)); + } else if (key.equals("keepAlive")) { + setKeepAlive(ConversionUtil.toBoolean(value)); + } else if (key.equals("reuseAddress")) { + setReuseAddress(ConversionUtil.toBoolean(value)); + } else if (key.equals("soLinger")) { + setSoLinger(ConversionUtil.toInt(value)); + } else if (key.equals("trafficClass")) { + setTrafficClass(ConversionUtil.toInt(value)); + } else { + return false; + } + return true; + } + + public void setOptions(Map options) { + for (Entry e: options.entrySet()) { + setOption(e.getKey(), e.getValue()); + } + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java index d439bcbd7ba..aa5b1254f52 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeChannelSink.java @@ -27,14 +27,13 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeChannelSink extends AbstractChannelSink -{ +public class FakeChannelSink extends AbstractChannelSink { - public Queue events = new LinkedList(); + public Queue events = new LinkedList(); - public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception - { - events.add(e); - } + public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) + throws Exception { + events.add(e); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java index f5465f7013b..31799022522 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeClientSocketChannelFactory.java @@ -27,26 +27,25 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeClientSocketChannelFactory implements ClientSocketChannelFactory -{ - - public List createdChannels; - - public FakeClientSocketChannelFactory() - { - createdChannels = new ArrayList(); - } - - public SocketChannel newChannel(ChannelPipeline pipeline) - { - FakeSocketChannel channel = new FakeSocketChannel(null, this, pipeline, new FakeChannelSink()); - createdChannels.add(channel); - return channel; - } - - public void releaseExternalResources() - { - // nothing to do - } +public class FakeClientSocketChannelFactory implements + ClientSocketChannelFactory { + + public List createdChannels; + + public FakeClientSocketChannelFactory() { + createdChannels = new ArrayList(); + } + + public SocketChannel newChannel(ChannelPipeline pipeline) { + FakeSocketChannel channel = + new FakeSocketChannel(null, this, pipeline, + new FakeChannelSink()); + createdChannels.add(channel); + return channel; + } + + public void releaseExternalResources() { + // nothing to do + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java index 59f0a50948c..fe7fc82ee62 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannel.java @@ -33,60 +33,58 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeServerSocketChannel extends AbstractChannel implements ServerSocketChannel -{ +public class FakeServerSocketChannel extends AbstractChannel implements + ServerSocketChannel { - public boolean bound; + public boolean bound; - public boolean connected; + public boolean connected; - public InetSocketAddress remoteAddress; + public InetSocketAddress remoteAddress; - public InetSocketAddress localAddress; + public InetSocketAddress localAddress; - public ServerSocketChannelConfig config = new FakeServerSocketChannelConfig(); + public ServerSocketChannelConfig config = + new FakeServerSocketChannelConfig(); - public FakeServerSocketChannel(ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink) - { - super(null, factory, pipeline, sink); - } + public FakeServerSocketChannel(ChannelFactory factory, + ChannelPipeline pipeline, ChannelSink sink) { + super(null, factory, pipeline, sink); + } - public ServerSocketChannelConfig getConfig() - { - return config; - } + public ServerSocketChannelConfig getConfig() { + return config; + } - public InetSocketAddress getLocalAddress() - { - return localAddress; - } + public InetSocketAddress getLocalAddress() { + return localAddress; + } - public InetSocketAddress getRemoteAddress() - { - return remoteAddress; - } + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } - public boolean isBound() - { - return bound; - } + public boolean isBound() { + return bound; + } - public boolean isConnected() - { - return connected; - } + public boolean isConnected() { + return connected; + } - public FakeSocketChannel acceptNewConnection(InetSocketAddress remoteAddress, ChannelSink sink) throws Exception - { - ChannelPipeline newPipeline = getConfig().getPipelineFactory().getPipeline(); - FakeSocketChannel newChannel = new FakeSocketChannel(this, getFactory(), newPipeline, sink); - newChannel.localAddress = localAddress; - newChannel.remoteAddress = remoteAddress; - fireChannelOpen(newChannel); - fireChannelBound(newChannel, newChannel.localAddress); - fireChannelConnected(this, newChannel.remoteAddress); + public FakeSocketChannel acceptNewConnection( + InetSocketAddress remoteAddress, ChannelSink sink) throws Exception { + ChannelPipeline newPipeline = + getConfig().getPipelineFactory().getPipeline(); + FakeSocketChannel newChannel = + new FakeSocketChannel(this, getFactory(), newPipeline, sink); + newChannel.localAddress = localAddress; + newChannel.remoteAddress = remoteAddress; + fireChannelOpen(newChannel); + fireChannelBound(newChannel, newChannel.localAddress); + fireChannelConnected(this, newChannel.remoteAddress); - return newChannel; - } + return newChannel; + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java index 36dec1904f1..28d2cb2ef68 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelConfig.java @@ -26,55 +26,49 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeServerSocketChannelConfig extends DefaultChannelConfig implements ServerSocketChannelConfig -{ +public class FakeServerSocketChannelConfig extends DefaultChannelConfig + implements ServerSocketChannelConfig { - public int backlog = 5; + public int backlog = 5; - public int receiveBufferSize = 1024; + public int receiveBufferSize = 1024; - public boolean reuseAddress = false; + public boolean reuseAddress = false; - public int connectionTimeout = 5000; + public int connectionTimeout = 5000; - public ChannelPipelineFactory pipelineFactory; + public ChannelPipelineFactory pipelineFactory; - public int writeTimeout = 5000; + public int writeTimeout = 5000; - public ChannelBufferFactory bufferFactory = new HeapChannelBufferFactory(); + public ChannelBufferFactory bufferFactory = new HeapChannelBufferFactory(); - public int getBacklog() - { - return backlog; - } + public int getBacklog() { + return backlog; + } - public void setBacklog(int backlog) - { - this.backlog = backlog; - } + public void setBacklog(int backlog) { + this.backlog = backlog; + } - public int getReceiveBufferSize() - { - return receiveBufferSize; - } + public int getReceiveBufferSize() { + return receiveBufferSize; + } - public void setReceiveBufferSize(int receiveBufferSize) - { - this.receiveBufferSize = receiveBufferSize; - } + public void setReceiveBufferSize(int receiveBufferSize) { + this.receiveBufferSize = receiveBufferSize; + } - public boolean isReuseAddress() - { - return reuseAddress; - } + public boolean isReuseAddress() { + return reuseAddress; + } - public void setReuseAddress(boolean reuseAddress) - { - this.reuseAddress = reuseAddress; - } + public void setReuseAddress(boolean reuseAddress) { + this.reuseAddress = reuseAddress; + } - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) - { - // ignore - } + public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + // ignore + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java index 2784a715db1..704ba958fff 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeServerSocketChannelFactory.java @@ -25,22 +25,20 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeServerSocketChannelFactory implements ServerSocketChannelFactory -{ +public class FakeServerSocketChannelFactory implements + ServerSocketChannelFactory { - public ChannelSink sink = new FakeChannelSink(); + public ChannelSink sink = new FakeChannelSink(); - public FakeServerSocketChannel createdChannel; + public FakeServerSocketChannel createdChannel; - public ServerSocketChannel newChannel(ChannelPipeline pipeline) - { - createdChannel = new FakeServerSocketChannel(this, pipeline, sink); - return createdChannel; - } + public ServerSocketChannel newChannel(ChannelPipeline pipeline) { + createdChannel = new FakeServerSocketChannel(this, pipeline, sink); + return createdChannel; + } - public void releaseExternalResources() - { - // nothing to do - } + public void releaseExternalResources() { + // nothing to do + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java b/src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java index dc8692ad02e..6849873cb96 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/FakeSocketChannel.java @@ -32,84 +32,73 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class FakeSocketChannel extends AbstractChannel implements SocketChannel -{ - - public InetSocketAddress localAddress; - - public InetSocketAddress remoteAddress; - - public SocketChannelConfig config = new FakeChannelConfig(); - - public boolean bound = false; - - public boolean connected = false; - - public ChannelSink sink; - - public FakeSocketChannel(Channel parent, ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink) - { - super(parent, factory, pipeline, sink); - this.sink = sink; - } - - public InetSocketAddress getLocalAddress() - { - return localAddress; - } - - public SocketChannelConfig getConfig() - { - return config; - } - - public InetSocketAddress getRemoteAddress() - { - return remoteAddress; - } - - public boolean isBound() - { - return bound; - } - - public boolean isConnected() - { - return connected; - } - - public void emulateConnected(InetSocketAddress localAddress, InetSocketAddress remoteAddress, - ChannelFuture connectedFuture) - { - if (connected) - { - return; - } - - emulateBound(localAddress, null); - this.remoteAddress = remoteAddress; - connected = true; - Channels.fireChannelConnected(this, remoteAddress); - if (connectedFuture != null) - { - connectedFuture.setSuccess(); - } - } - - public void emulateBound(InetSocketAddress localAddress, ChannelFuture boundFuture) - { - if (bound) - { - return; - } - - bound = true; - this.localAddress = localAddress; - Channels.fireChannelBound(this, localAddress); - if (boundFuture != null) - { - boundFuture.setSuccess(); - } - } +public class FakeSocketChannel extends AbstractChannel implements SocketChannel { + + public InetSocketAddress localAddress; + + public InetSocketAddress remoteAddress; + + public SocketChannelConfig config = new FakeChannelConfig(); + + public boolean bound = false; + + public boolean connected = false; + + public ChannelSink sink; + + public FakeSocketChannel(Channel parent, ChannelFactory factory, + ChannelPipeline pipeline, ChannelSink sink) { + super(parent, factory, pipeline, sink); + this.sink = sink; + } + + public InetSocketAddress getLocalAddress() { + return localAddress; + } + + public SocketChannelConfig getConfig() { + return config; + } + + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isBound() { + return bound; + } + + public boolean isConnected() { + return connected; + } + + public void emulateConnected(InetSocketAddress localAddress, + InetSocketAddress remoteAddress, ChannelFuture connectedFuture) { + if (connected) { + return; + } + + emulateBound(localAddress, null); + this.remoteAddress = remoteAddress; + connected = true; + Channels.fireChannelConnected(this, remoteAddress); + if (connectedFuture != null) { + connectedFuture.setSuccess(); + } + } + + public void emulateBound(InetSocketAddress localAddress, + ChannelFuture boundFuture) { + if (bound) { + return; + } + + bound = true; + this.localAddress = localAddress; + Channels.fireChannelBound(this, localAddress); + if (boundFuture != null) { + boundFuture.setSuccess(); + } + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java index 571314615ea..14dfe3ca64a 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelAcceptedChannelSinkTest.java @@ -32,63 +32,59 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class HttpTunnelAcceptedChannelSinkTest -{ +public class HttpTunnelAcceptedChannelSinkTest { - private static final String TUNNEL_ID = "1"; + private static final String TUNNEL_ID = "1"; - private final JUnit4Mockery mockContext = new JUnit4Mockery(); + private final JUnit4Mockery mockContext = new JUnit4Mockery(); - ServerMessageSwitchDownstreamInterface messageSwitch; + ServerMessageSwitchDownstreamInterface messageSwitch; - private HttpTunnelAcceptedChannelSink sink; + private HttpTunnelAcceptedChannelSink sink; - private FakeSocketChannel channel; + private FakeSocketChannel channel; - private UpstreamEventCatcher upstreamCatcher; + private UpstreamEventCatcher upstreamCatcher; - @Before - public void setUp() throws Exception - { - messageSwitch = mockContext.mock(ServerMessageSwitchDownstreamInterface.class); - sink = new HttpTunnelAcceptedChannelSink(messageSwitch, TUNNEL_ID, new HttpTunnelAcceptedChannelConfig()); - ChannelPipeline pipeline = Channels.pipeline(); - upstreamCatcher = new UpstreamEventCatcher(); - pipeline.addLast(UpstreamEventCatcher.NAME, upstreamCatcher); - channel = new FakeSocketChannel(null, null, pipeline, sink); - upstreamCatcher.events.clear(); - } + @Before + public void setUp() throws Exception { + messageSwitch = + mockContext.mock(ServerMessageSwitchDownstreamInterface.class); + sink = + new HttpTunnelAcceptedChannelSink(messageSwitch, TUNNEL_ID, + new HttpTunnelAcceptedChannelConfig()); + ChannelPipeline pipeline = Channels.pipeline(); + upstreamCatcher = new UpstreamEventCatcher(); + pipeline.addLast(UpstreamEventCatcher.NAME, upstreamCatcher); + channel = new FakeSocketChannel(null, null, pipeline, sink); + upstreamCatcher.events.clear(); + } - @Test - public void testSendInvalidDataType() - { - Channels.write(channel, new Object()); - assertEquals(1, upstreamCatcher.events.size()); - NettyTestUtils.checkIsExceptionEvent(upstreamCatcher.events.poll()); - } + @Test + public void testSendInvalidDataType() { + Channels.write(channel, new Object()); + assertEquals(1, upstreamCatcher.events.size()); + NettyTestUtils.checkIsExceptionEvent(upstreamCatcher.events.poll()); + } - @Test - public void testUnbind() - { - mockContext.checking(new Expectations() - { - { - one(messageSwitch).serverCloseTunnel(TUNNEL_ID); - } - }); - Channels.unbind(channel); - } + @Test + public void testUnbind() { + mockContext.checking(new Expectations() { + { + one(messageSwitch).serverCloseTunnel(TUNNEL_ID); + } + }); + Channels.unbind(channel); + } - @Test - public void testDisconnect() - { - mockContext.checking(new Expectations() - { - { - one(messageSwitch).serverCloseTunnel(TUNNEL_ID); - } - }); + @Test + public void testDisconnect() { + mockContext.checking(new Expectations() { + { + one(messageSwitch).serverCloseTunnel(TUNNEL_ID); + } + }); - Channels.disconnect(channel); - } + Channels.disconnect(channel); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java index f1d398947b6..5b9b535c198 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelConfigTest.java @@ -33,297 +33,262 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class HttpTunnelClientChannelConfigTest -{ - - JUnit4Mockery mockContext = new JUnit4Mockery(); - - SocketChannelConfig sendChannelConfig; - - SocketChannelConfig pollChannelConfig; - - HttpTunnelClientChannelConfig config; - - @Before - public void setUp() - { - sendChannelConfig = mockContext.mock(SocketChannelConfig.class, "sendChannelConfig"); - pollChannelConfig = mockContext.mock(SocketChannelConfig.class, "pollChannelConfig"); - - config = new HttpTunnelClientChannelConfig(sendChannelConfig, pollChannelConfig); - } - - @Test - public void testGetReceiveBufferSize() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).getReceiveBufferSize(); - will(returnValue(100)); - } - }); - - assertEquals(100, config.getReceiveBufferSize()); - } - - @Test - public void testGetSendBufferSize() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).getSendBufferSize(); - will(returnValue(100)); - } - }); - - assertEquals(100, config.getSendBufferSize()); - } - - @Test - public void testGetSoLinger() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).getSoLinger(); - will(returnValue(100)); - } - }); - - assertEquals(100, config.getSoLinger()); - } - - @Test - public void testTrafficClass() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).getTrafficClass(); - will(returnValue(1)); - } - }); - - assertEquals(1, config.getTrafficClass()); - } - - @Test - public void testIsKeepAlive() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).isKeepAlive(); - will(returnValue(true)); - } - }); - - assertTrue(config.isKeepAlive()); - } - - @Test - public void testIsReuseAddress() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).isReuseAddress(); - will(returnValue(true)); - } - }); - - assertTrue(config.isReuseAddress()); - } - - @Test - public void testIsTcpNoDelay() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).isTcpNoDelay(); - will(returnValue(true)); - } - }); - - assertTrue(config.isTcpNoDelay()); - } - - @Test - public void testSetKeepAlive() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setKeepAlive(true); - one(sendChannelConfig).setKeepAlive(true); - } - }); - - config.setKeepAlive(true); - } - - @Test - public void testSetPerformancePreferences() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setPerformancePreferences(100, 200, 300); - one(sendChannelConfig).setPerformancePreferences(100, 200, 300); - } - }); - - config.setPerformancePreferences(100, 200, 300); - } - - @Test - public void testSetReceiveBufferSize() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setReceiveBufferSize(100); - one(sendChannelConfig).setReceiveBufferSize(100); - } - }); - - config.setReceiveBufferSize(100); - } - - @Test - public void testSetReuseAddress() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setReuseAddress(true); - one(sendChannelConfig).setReuseAddress(true); - } - }); - - config.setReuseAddress(true); - } - - @Test - public void testSetSendBufferSize() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setSendBufferSize(100); - one(sendChannelConfig).setSendBufferSize(100); - } - }); - - config.setSendBufferSize(100); - } - - @Test - public void testSetSoLinger() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setSoLinger(100); - one(sendChannelConfig).setSoLinger(100); - } - }); - - config.setSoLinger(100); - } - - @Test - public void testTcpNoDelay() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setTcpNoDelay(true); - one(sendChannelConfig).setTcpNoDelay(true); - } - }); - - config.setTcpNoDelay(true); - } - - @Test - public void testSetTrafficClass() - { - mockContext.checking(new Expectations() - { - { - one(pollChannelConfig).setTrafficClass(1); - one(sendChannelConfig).setTrafficClass(1); - } - }); - - config.setTrafficClass(1); - } - - @Test - public void testSetHighWaterMark() - { - config.setWriteBufferHighWaterMark(128 * 1024); - assertEquals(128 * 1024, config.getWriteBufferHighWaterMark()); - } - - @Test(expected = IllegalArgumentException.class) - public void testSetHighWaterMark_negative() - { - config.setWriteBufferHighWaterMark(-1); - } - - @Test(expected = IllegalArgumentException.class) - public void testSetHighWaterMark_zero() - { - config.setWriteBufferHighWaterMark(0); - } - - @Test - public void testSetLowWaterMark() - { - config.setWriteBufferLowWaterMark(100); - assertEquals(100, config.getWriteBufferLowWaterMark()); - } - - @Test - public void testSetLowWaterMark_zero() - { - // zero is permitted for the low water mark, unlike high water mark - config.setWriteBufferLowWaterMark(0); - assertEquals(0, config.getWriteBufferLowWaterMark()); - } - - @Test - public void testSetHighWaterMark_lowerThanLow() - { - config.setWriteBufferLowWaterMark(100); - try - { - config.setWriteBufferHighWaterMark(80); - fail("expected IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - assertEquals("Write buffer high water mark must be strictly greater than the low water mark", e.getMessage()); - } - } - - @Test - public void testSetLowWaterMark_higherThanHigh() - { - config.setWriteBufferHighWaterMark(128 * 1024); - try - { - config.setWriteBufferLowWaterMark(256 * 1024); - fail("expected IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - assertEquals("Write buffer low water mark must be strictly less than the high water mark", e.getMessage()); - } - } +public class HttpTunnelClientChannelConfigTest { + + JUnit4Mockery mockContext = new JUnit4Mockery(); + + SocketChannelConfig sendChannelConfig; + + SocketChannelConfig pollChannelConfig; + + HttpTunnelClientChannelConfig config; + + @Before + public void setUp() { + sendChannelConfig = + mockContext + .mock(SocketChannelConfig.class, "sendChannelConfig"); + pollChannelConfig = + mockContext + .mock(SocketChannelConfig.class, "pollChannelConfig"); + + config = + new HttpTunnelClientChannelConfig(sendChannelConfig, + pollChannelConfig); + } + + @Test + public void testGetReceiveBufferSize() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).getReceiveBufferSize(); + will(returnValue(100)); + } + }); + + assertEquals(100, config.getReceiveBufferSize()); + } + + @Test + public void testGetSendBufferSize() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).getSendBufferSize(); + will(returnValue(100)); + } + }); + + assertEquals(100, config.getSendBufferSize()); + } + + @Test + public void testGetSoLinger() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).getSoLinger(); + will(returnValue(100)); + } + }); + + assertEquals(100, config.getSoLinger()); + } + + @Test + public void testTrafficClass() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).getTrafficClass(); + will(returnValue(1)); + } + }); + + assertEquals(1, config.getTrafficClass()); + } + + @Test + public void testIsKeepAlive() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).isKeepAlive(); + will(returnValue(true)); + } + }); + + assertTrue(config.isKeepAlive()); + } + + @Test + public void testIsReuseAddress() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).isReuseAddress(); + will(returnValue(true)); + } + }); + + assertTrue(config.isReuseAddress()); + } + + @Test + public void testIsTcpNoDelay() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).isTcpNoDelay(); + will(returnValue(true)); + } + }); + + assertTrue(config.isTcpNoDelay()); + } + + @Test + public void testSetKeepAlive() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setKeepAlive(true); + one(sendChannelConfig).setKeepAlive(true); + } + }); + + config.setKeepAlive(true); + } + + @Test + public void testSetPerformancePreferences() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setPerformancePreferences(100, 200, 300); + one(sendChannelConfig).setPerformancePreferences(100, 200, 300); + } + }); + + config.setPerformancePreferences(100, 200, 300); + } + + @Test + public void testSetReceiveBufferSize() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setReceiveBufferSize(100); + one(sendChannelConfig).setReceiveBufferSize(100); + } + }); + + config.setReceiveBufferSize(100); + } + + @Test + public void testSetReuseAddress() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setReuseAddress(true); + one(sendChannelConfig).setReuseAddress(true); + } + }); + + config.setReuseAddress(true); + } + + @Test + public void testSetSendBufferSize() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setSendBufferSize(100); + one(sendChannelConfig).setSendBufferSize(100); + } + }); + + config.setSendBufferSize(100); + } + + @Test + public void testSetSoLinger() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setSoLinger(100); + one(sendChannelConfig).setSoLinger(100); + } + }); + + config.setSoLinger(100); + } + + @Test + public void testTcpNoDelay() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setTcpNoDelay(true); + one(sendChannelConfig).setTcpNoDelay(true); + } + }); + + config.setTcpNoDelay(true); + } + + @Test + public void testSetTrafficClass() { + mockContext.checking(new Expectations() { + { + one(pollChannelConfig).setTrafficClass(1); + one(sendChannelConfig).setTrafficClass(1); + } + }); + + config.setTrafficClass(1); + } + + @Test + public void testSetHighWaterMark() { + config.setWriteBufferHighWaterMark(128 * 1024); + assertEquals(128 * 1024, config.getWriteBufferHighWaterMark()); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetHighWaterMark_negative() { + config.setWriteBufferHighWaterMark(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetHighWaterMark_zero() { + config.setWriteBufferHighWaterMark(0); + } + + @Test + public void testSetLowWaterMark() { + config.setWriteBufferLowWaterMark(100); + assertEquals(100, config.getWriteBufferLowWaterMark()); + } + + @Test + public void testSetLowWaterMark_zero() { + // zero is permitted for the low water mark, unlike high water mark + config.setWriteBufferLowWaterMark(0); + assertEquals(0, config.getWriteBufferLowWaterMark()); + } + + @Test + public void testSetHighWaterMark_lowerThanLow() { + config.setWriteBufferLowWaterMark(100); + try { + config.setWriteBufferHighWaterMark(80); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals( + "Write buffer high water mark must be strictly greater than the low water mark", + e.getMessage()); + } + } + + @Test + public void testSetLowWaterMark_higherThanHigh() { + config.setWriteBufferHighWaterMark(128 * 1024); + try { + config.setWriteBufferLowWaterMark(256 * 1024); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals( + "Write buffer low water mark must be strictly less than the high water mark", + e.getMessage()); + } + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java index bd0d4839af9..ccc185ca2a3 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientChannelTest.java @@ -39,217 +39,232 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class HttpTunnelClientChannelTest -{ +public class HttpTunnelClientChannelTest { - public static final int LOCAL_PORT = 50123; + public static final int LOCAL_PORT = 50123; - /** used to emulate the selection of a random port in response to a bind request - * on an ephemeral port. - */ - public static final int OTHER_LOCAL_PORT = 40652; + /** used to emulate the selection of a random port in response to a bind request + * on an ephemeral port. + */ + public static final int OTHER_LOCAL_PORT = 40652; - public static final InetSocketAddress LOCAL_ADDRESS = InetSocketAddress.createUnresolved("localhost", LOCAL_PORT); + public static final InetSocketAddress LOCAL_ADDRESS = InetSocketAddress + .createUnresolved("localhost", LOCAL_PORT); - public static final InetSocketAddress LOCAL_ADDRESS_EPHEMERAL_PORT = InetSocketAddress.createUnresolved("localhost", - 0); + public static final InetSocketAddress LOCAL_ADDRESS_EPHEMERAL_PORT = + InetSocketAddress.createUnresolved("localhost", 0); - public static final InetSocketAddress REMOTE_ADDRESS = InetSocketAddress.createUnresolved("test.server.com", 12345); - - public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4; - - public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6; - - public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT; - - public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT; - - public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT; - - public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT; - - static - { - try - { - InetAddress localhostIPV4 = InetAddress.getByAddress(new byte[] - {127, 0, 0, 1}); - InetAddress localhostIPV6 = InetAddress.getByAddress(new byte[] - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}); - RESOLVED_LOCAL_ADDRESS_IPV4 = new InetSocketAddress(localhostIPV4, LOCAL_PORT); - RESOLVED_LOCAL_ADDRESS_IPV6 = new InetSocketAddress(localhostIPV6, LOCAL_PORT); - RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT = new InetSocketAddress(localhostIPV4, 0); - RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT = new InetSocketAddress(localhostIPV6, 0); - RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT = new InetSocketAddress(localhostIPV4, OTHER_LOCAL_PORT); - RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT = new InetSocketAddress(localhostIPV6, OTHER_LOCAL_PORT); - } - catch (UnknownHostException e) - { - throw new RuntimeException( - "Creation of InetAddresses should not fail when explicitly specified and the correct length", e); - } - } - - private UpstreamEventCatcher upstreamCatcher; - - private HttpTunnelClientChannel channel; - - private FakeClientSocketChannelFactory outboundFactory; - - private FakeSocketChannel sendChannel; - - private FakeSocketChannel pollChannel; - - private FakeChannelSink sendSink; - - private FakeChannelSink pollSink; - - @Before - public void setUp() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); - upstreamCatcher = new UpstreamEventCatcher(); - pipeline.addLast(UpstreamEventCatcher.NAME, upstreamCatcher); - - outboundFactory = new FakeClientSocketChannelFactory(); - - HttpTunnelClientChannelFactory factory = new HttpTunnelClientChannelFactory(outboundFactory); - channel = factory.newChannel(pipeline); - - assertEquals(2, outboundFactory.createdChannels.size()); - - sendChannel = outboundFactory.createdChannels.get(0); - pollChannel = outboundFactory.createdChannels.get(1); - sendSink = (FakeChannelSink) sendChannel.sink; - pollSink = (FakeChannelSink) pollChannel.sink; - } - - @Test - public void testConnect() - { - Channels.connect(channel, REMOTE_ADDRESS); - - // this should result in a CONNECTED state event on the send channel, but not on the poll - // channel just yet - assertEquals(1, sendSink.events.size()); - assertEquals(0, pollSink.events.size()); - ChannelEvent sendChannelEvent = sendSink.events.poll(); - NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.CONNECTED, REMOTE_ADDRESS); - - // once the send channel indicates that it is connected, we should see the tunnel open request - // being sent - sendChannel.emulateConnected(LOCAL_ADDRESS, REMOTE_ADDRESS, ((ChannelStateEvent) sendChannelEvent).getFuture()); - assertEquals(1, sendSink.events.size()); - ChannelEvent openTunnelRequest = sendSink.events.poll(); - NettyTestUtils.checkIsDownstreamMessageEvent(openTunnelRequest, ChannelBuffer.class); - } - - @Test - public void testBind_unresolvedAddress() - { - // requesting a binding with an unresolved local address - // should attempt to bind the send channel with that address unaltered - // and attempt to bind the poll address with the same host name but - // an ephemeral port. We emulate a resolved IPV4 address for the bind - // response. - checkBinding(LOCAL_ADDRESS, LOCAL_ADDRESS, LOCAL_ADDRESS_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV4, - RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT); - } - - @Test - public void testBind_resolvedAddress_ipv4() - { - // variant that uses resolved addresses. The bind request - // for the poll channel should also use a resolved address, - // built from the provided resolved address. - checkBinding(RESOLVED_LOCAL_ADDRESS_IPV4, RESOLVED_LOCAL_ADDRESS_IPV4, - RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV4, - RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT); - } - - @Test - public void testBind_resolvedAddress_ipv6() - { - // variant that uses a resolved IPV6 address. - // bind request on the poll channel should use the same - // IPv6 host, with an ephemeral port. - checkBinding(RESOLVED_LOCAL_ADDRESS_IPV6, RESOLVED_LOCAL_ADDRESS_IPV6, - RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV6, - RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT); - } - - private void checkBinding(InetSocketAddress requestedBindAddress, InetSocketAddress expectedPollBindRequest, - InetSocketAddress expectedSendBindRequest, InetSocketAddress emulatedPollBindAddress, - InetSocketAddress emulatedSendBindAddress) - { - - ChannelFuture bindFuture = Channels.bind(channel, requestedBindAddress); - assertFalse(bindFuture.isDone()); - - assertEquals(1, sendSink.events.size()); - assertEquals(1, pollSink.events.size()); - - ChannelEvent sendChannelEvent = sendSink.events.poll(); - NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.BOUND, expectedPollBindRequest); - ChannelEvent pollChannelEvent = pollSink.events.poll(); - NettyTestUtils.checkIsStateEvent(pollChannelEvent, ChannelState.BOUND, expectedSendBindRequest); - - sendChannel.emulateBound(emulatedPollBindAddress, sendChannelEvent.getFuture()); - assertFalse(bindFuture.isDone()); - pollChannel.emulateBound(emulatedSendBindAddress, pollChannelEvent.getFuture()); - assertTrue(bindFuture.isDone()); - assertTrue(bindFuture.isSuccess()); - - assertEquals(channel.getLocalAddress(), emulatedPollBindAddress); - } - - @Test - public void testBind_preResolvedAddress_ipv6() - { - ChannelFuture bindFuture = Channels.bind(channel, RESOLVED_LOCAL_ADDRESS_IPV6); - assertFalse(bindFuture.isDone()); - - assertEquals(1, sendSink.events.size()); - assertEquals(1, pollSink.events.size()); - - ChannelEvent sendChannelEvent = sendSink.events.poll(); - NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.BOUND, RESOLVED_LOCAL_ADDRESS_IPV6); - ChannelEvent pollChannelEvent = pollSink.events.poll(); - NettyTestUtils - .checkIsStateEvent(pollChannelEvent, ChannelState.BOUND, RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT); - - sendChannel.emulateBound(RESOLVED_LOCAL_ADDRESS_IPV6, sendChannelEvent.getFuture()); - assertFalse(bindFuture.isDone()); - pollChannel.emulateBound(RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT, pollChannelEvent.getFuture()); - assertTrue(bindFuture.isDone()); - assertTrue(bindFuture.isSuccess()); - - assertEquals(channel.getLocalAddress(), RESOLVED_LOCAL_ADDRESS_IPV6); - } - - @Test - public void testBind_sendBindFails() - { - ChannelFuture bindFuture = Channels.bind(channel, LOCAL_ADDRESS); - assertFalse(bindFuture.isDone()); - - Exception bindFailureReason = new Exception("could not bind"); - ((ChannelStateEvent) sendSink.events.poll()).getFuture().setFailure(bindFailureReason); - assertTrue(bindFuture.isDone()); - assertFalse(bindFuture.isSuccess()); - assertSame(bindFailureReason, bindFuture.getCause()); - } - - @Test - public void testBind_pollBindFails() - { - ChannelFuture bindFuture = Channels.bind(channel, LOCAL_ADDRESS); - assertFalse(bindFuture.isDone()); - - Exception bindFailureReason = new Exception("could not bind"); - ((ChannelStateEvent) pollSink.events.poll()).getFuture().setFailure(bindFailureReason); - assertTrue(bindFuture.isDone()); - assertFalse(bindFuture.isSuccess()); - assertSame(bindFailureReason, bindFuture.getCause()); - } + public static final InetSocketAddress REMOTE_ADDRESS = InetSocketAddress + .createUnresolved("test.server.com", 12345); + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT; + + public static final InetSocketAddress RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT; + + static { + try { + InetAddress localhostIPV4 = + InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); + InetAddress localhostIPV6 = + InetAddress.getByAddress(new byte[] { 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1 }); + RESOLVED_LOCAL_ADDRESS_IPV4 = + new InetSocketAddress(localhostIPV4, LOCAL_PORT); + RESOLVED_LOCAL_ADDRESS_IPV6 = + new InetSocketAddress(localhostIPV6, LOCAL_PORT); + RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT = + new InetSocketAddress(localhostIPV4, 0); + RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT = + new InetSocketAddress(localhostIPV6, 0); + RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT = + new InetSocketAddress(localhostIPV4, OTHER_LOCAL_PORT); + RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT = + new InetSocketAddress(localhostIPV6, OTHER_LOCAL_PORT); + } catch (UnknownHostException e) { + throw new RuntimeException( + "Creation of InetAddresses should not fail when explicitly specified and the correct length", + e); + } + } + + private UpstreamEventCatcher upstreamCatcher; + + private HttpTunnelClientChannel channel; + + private FakeClientSocketChannelFactory outboundFactory; + + private FakeSocketChannel sendChannel; + + private FakeSocketChannel pollChannel; + + private FakeChannelSink sendSink; + + private FakeChannelSink pollSink; + + @Before + public void setUp() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + upstreamCatcher = new UpstreamEventCatcher(); + pipeline.addLast(UpstreamEventCatcher.NAME, upstreamCatcher); + + outboundFactory = new FakeClientSocketChannelFactory(); + + HttpTunnelClientChannelFactory factory = + new HttpTunnelClientChannelFactory(outboundFactory); + channel = factory.newChannel(pipeline); + + assertEquals(2, outboundFactory.createdChannels.size()); + + sendChannel = outboundFactory.createdChannels.get(0); + pollChannel = outboundFactory.createdChannels.get(1); + sendSink = (FakeChannelSink) sendChannel.sink; + pollSink = (FakeChannelSink) pollChannel.sink; + } + + @Test + public void testConnect() { + Channels.connect(channel, REMOTE_ADDRESS); + + // this should result in a CONNECTED state event on the send channel, but not on the poll + // channel just yet + assertEquals(1, sendSink.events.size()); + assertEquals(0, pollSink.events.size()); + ChannelEvent sendChannelEvent = sendSink.events.poll(); + NettyTestUtils.checkIsStateEvent(sendChannelEvent, + ChannelState.CONNECTED, REMOTE_ADDRESS); + + // once the send channel indicates that it is connected, we should see the tunnel open request + // being sent + sendChannel.emulateConnected(LOCAL_ADDRESS, REMOTE_ADDRESS, + ((ChannelStateEvent) sendChannelEvent).getFuture()); + assertEquals(1, sendSink.events.size()); + ChannelEvent openTunnelRequest = sendSink.events.poll(); + NettyTestUtils.checkIsDownstreamMessageEvent(openTunnelRequest, + ChannelBuffer.class); + } + + @Test + public void testBind_unresolvedAddress() { + // requesting a binding with an unresolved local address + // should attempt to bind the send channel with that address unaltered + // and attempt to bind the poll address with the same host name but + // an ephemeral port. We emulate a resolved IPV4 address for the bind + // response. + checkBinding(LOCAL_ADDRESS, LOCAL_ADDRESS, + LOCAL_ADDRESS_EPHEMERAL_PORT, RESOLVED_LOCAL_ADDRESS_IPV4, + RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT); + } + + @Test + public void testBind_resolvedAddress_ipv4() { + // variant that uses resolved addresses. The bind request + // for the poll channel should also use a resolved address, + // built from the provided resolved address. + checkBinding(RESOLVED_LOCAL_ADDRESS_IPV4, RESOLVED_LOCAL_ADDRESS_IPV4, + RESOLVED_LOCAL_ADDRESS_IPV4_EPHEMERAL_PORT, + RESOLVED_LOCAL_ADDRESS_IPV4, + RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT); + } + + @Test + public void testBind_resolvedAddress_ipv6() { + // variant that uses a resolved IPV6 address. + // bind request on the poll channel should use the same + // IPv6 host, with an ephemeral port. + checkBinding(RESOLVED_LOCAL_ADDRESS_IPV6, RESOLVED_LOCAL_ADDRESS_IPV6, + RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT, + RESOLVED_LOCAL_ADDRESS_IPV6, + RESOLVED_LOCAL_ADDRESS_IPV6_SELECTED_PORT); + } + + private void checkBinding(InetSocketAddress requestedBindAddress, + InetSocketAddress expectedPollBindRequest, + InetSocketAddress expectedSendBindRequest, + InetSocketAddress emulatedPollBindAddress, + InetSocketAddress emulatedSendBindAddress) { + + ChannelFuture bindFuture = Channels.bind(channel, requestedBindAddress); + assertFalse(bindFuture.isDone()); + + assertEquals(1, sendSink.events.size()); + assertEquals(1, pollSink.events.size()); + + ChannelEvent sendChannelEvent = sendSink.events.poll(); + NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.BOUND, + expectedPollBindRequest); + ChannelEvent pollChannelEvent = pollSink.events.poll(); + NettyTestUtils.checkIsStateEvent(pollChannelEvent, ChannelState.BOUND, + expectedSendBindRequest); + + sendChannel.emulateBound(emulatedPollBindAddress, + sendChannelEvent.getFuture()); + assertFalse(bindFuture.isDone()); + pollChannel.emulateBound(emulatedSendBindAddress, + pollChannelEvent.getFuture()); + assertTrue(bindFuture.isDone()); + assertTrue(bindFuture.isSuccess()); + + assertEquals(channel.getLocalAddress(), emulatedPollBindAddress); + } + + @Test + public void testBind_preResolvedAddress_ipv6() { + ChannelFuture bindFuture = + Channels.bind(channel, RESOLVED_LOCAL_ADDRESS_IPV6); + assertFalse(bindFuture.isDone()); + + assertEquals(1, sendSink.events.size()); + assertEquals(1, pollSink.events.size()); + + ChannelEvent sendChannelEvent = sendSink.events.poll(); + NettyTestUtils.checkIsStateEvent(sendChannelEvent, ChannelState.BOUND, + RESOLVED_LOCAL_ADDRESS_IPV6); + ChannelEvent pollChannelEvent = pollSink.events.poll(); + NettyTestUtils.checkIsStateEvent(pollChannelEvent, ChannelState.BOUND, + RESOLVED_LOCAL_ADDRESS_IPV6_EPHEMERAL_PORT); + + sendChannel.emulateBound(RESOLVED_LOCAL_ADDRESS_IPV6, + sendChannelEvent.getFuture()); + assertFalse(bindFuture.isDone()); + pollChannel.emulateBound(RESOLVED_LOCAL_ADDRESS_IPV4_SELECTED_PORT, + pollChannelEvent.getFuture()); + assertTrue(bindFuture.isDone()); + assertTrue(bindFuture.isSuccess()); + + assertEquals(channel.getLocalAddress(), RESOLVED_LOCAL_ADDRESS_IPV6); + } + + @Test + public void testBind_sendBindFails() { + ChannelFuture bindFuture = Channels.bind(channel, LOCAL_ADDRESS); + assertFalse(bindFuture.isDone()); + + Exception bindFailureReason = new Exception("could not bind"); + ((ChannelStateEvent) sendSink.events.poll()).getFuture().setFailure( + bindFailureReason); + assertTrue(bindFuture.isDone()); + assertFalse(bindFuture.isSuccess()); + assertSame(bindFailureReason, bindFuture.getCause()); + } + + @Test + public void testBind_pollBindFails() { + ChannelFuture bindFuture = Channels.bind(channel, LOCAL_ADDRESS); + assertFalse(bindFuture.isDone()); + + Exception bindFailureReason = new Exception("could not bind"); + ((ChannelStateEvent) pollSink.events.poll()).getFuture().setFailure( + bindFailureReason); + assertTrue(bindFuture.isDone()); + assertFalse(bindFuture.isSuccess()); + assertSame(bindFailureReason, bindFuture.getCause()); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java index 5955c444f37..fa8685d1b99 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientPollHandlerTest.java @@ -36,91 +36,88 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class HttpTunnelClientPollHandlerTest -{ - - private static final String TUNNEL_ID = "1"; - - private static final InetSocketAddress SERVER_ADDRESS = createAddress(new byte[] - {10, 0, 0, 3}, 12345); - - private static final InetSocketAddress PROXY_ADDRESS = createAddress(new byte[] - {10, 0, 0, 2}, 8888); - - private static final InetSocketAddress LOCAL_ADDRESS = createAddress(new byte[] - {10, 0, 0, 1}, 54321); - - private FakeSocketChannel channel; - - private FakeChannelSink sink; - - private HttpTunnelClientPollHandler handler; - - private MockChannelStateListener listener; - - private static InetSocketAddress createAddress(byte[] addr, int port) - { - try - { - return new InetSocketAddress(InetAddress.getByAddress(addr), port); - } - catch (UnknownHostException e) - { - throw new RuntimeException("Bad address in test"); - } - } - - @Before - public void setUp() throws Exception - { - sink = new FakeChannelSink(); - - ChannelPipeline pipeline = Channels.pipeline(); - listener = new MockChannelStateListener(); - listener.serverHostName = HttpTunnelMessageUtils.convertToHostString(SERVER_ADDRESS); - handler = new HttpTunnelClientPollHandler(listener); - handler.setTunnelId(TUNNEL_ID); - pipeline.addLast(HttpTunnelClientPollHandler.NAME, handler); - - channel = new FakeSocketChannel(null, null, pipeline, sink); - channel.remoteAddress = PROXY_ADDRESS; - channel.localAddress = LOCAL_ADDRESS; - } - - @Test - public void testSendsRequestOnConnect() - { - Channels.fireChannelConnected(channel, PROXY_ADDRESS); - assertEquals(1, sink.events.size()); - HttpRequest request = checkIsMessageEventContainingHttpRequest(sink.events.poll()); - assertTrue(HttpTunnelMessageUtils.isServerToClientRequest(request)); - assertTrue(HttpTunnelMessageUtils.checkHost(request, SERVER_ADDRESS)); - assertTrue(listener.fullyEstablished); - } - - @Test - public void testSendsReceivedDataSentUpstream() - { - HttpResponse response = HttpTunnelMessageUtils.createRecvDataResponse(NettyTestUtils.createData(1234L)); - Channels.fireMessageReceived(channel, response); - assertEquals(1, listener.messages.size()); - assertEquals(1234L, listener.messages.get(0).readLong()); - } - - @Test - public void testSendsAnotherRequestAfterResponse() - { - HttpResponse response = HttpTunnelMessageUtils.createRecvDataResponse(NettyTestUtils.createData(1234L)); - Channels.fireMessageReceived(channel, response); - assertEquals(1, sink.events.size()); - checkIsMessageEventContainingHttpRequest(sink.events.poll()); - } - - private HttpRequest checkIsMessageEventContainingHttpRequest(ChannelEvent event) - { - assertTrue(event instanceof DownstreamMessageEvent); - DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; - assertTrue(messageEvent.getMessage() instanceof HttpRequest); - return (HttpRequest) messageEvent.getMessage(); - } +public class HttpTunnelClientPollHandlerTest { + + private static final String TUNNEL_ID = "1"; + + private static final InetSocketAddress SERVER_ADDRESS = createAddress( + new byte[] { 10, 0, 0, 3 }, 12345); + + private static final InetSocketAddress PROXY_ADDRESS = createAddress( + new byte[] { 10, 0, 0, 2 }, 8888); + + private static final InetSocketAddress LOCAL_ADDRESS = createAddress( + new byte[] { 10, 0, 0, 1 }, 54321); + + private FakeSocketChannel channel; + + private FakeChannelSink sink; + + private HttpTunnelClientPollHandler handler; + + private MockChannelStateListener listener; + + private static InetSocketAddress createAddress(byte[] addr, int port) { + try { + return new InetSocketAddress(InetAddress.getByAddress(addr), port); + } catch (UnknownHostException e) { + throw new RuntimeException("Bad address in test"); + } + } + + @Before + public void setUp() throws Exception { + sink = new FakeChannelSink(); + + ChannelPipeline pipeline = Channels.pipeline(); + listener = new MockChannelStateListener(); + listener.serverHostName = + HttpTunnelMessageUtils.convertToHostString(SERVER_ADDRESS); + handler = new HttpTunnelClientPollHandler(listener); + handler.setTunnelId(TUNNEL_ID); + pipeline.addLast(HttpTunnelClientPollHandler.NAME, handler); + + channel = new FakeSocketChannel(null, null, pipeline, sink); + channel.remoteAddress = PROXY_ADDRESS; + channel.localAddress = LOCAL_ADDRESS; + } + + @Test + public void testSendsRequestOnConnect() { + Channels.fireChannelConnected(channel, PROXY_ADDRESS); + assertEquals(1, sink.events.size()); + HttpRequest request = + checkIsMessageEventContainingHttpRequest(sink.events.poll()); + assertTrue(HttpTunnelMessageUtils.isServerToClientRequest(request)); + assertTrue(HttpTunnelMessageUtils.checkHost(request, SERVER_ADDRESS)); + assertTrue(listener.fullyEstablished); + } + + @Test + public void testSendsReceivedDataSentUpstream() { + HttpResponse response = + HttpTunnelMessageUtils.createRecvDataResponse(NettyTestUtils + .createData(1234L)); + Channels.fireMessageReceived(channel, response); + assertEquals(1, listener.messages.size()); + assertEquals(1234L, listener.messages.get(0).readLong()); + } + + @Test + public void testSendsAnotherRequestAfterResponse() { + HttpResponse response = + HttpTunnelMessageUtils.createRecvDataResponse(NettyTestUtils + .createData(1234L)); + Channels.fireMessageReceived(channel, response); + assertEquals(1, sink.events.size()); + checkIsMessageEventContainingHttpRequest(sink.events.poll()); + } + + private HttpRequest checkIsMessageEventContainingHttpRequest( + ChannelEvent event) { + assertTrue(event instanceof DownstreamMessageEvent); + DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; + assertTrue(messageEvent.getMessage() instanceof HttpRequest); + return (HttpRequest) messageEvent.getMessage(); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java index 987fcaca1a3..943789a58e8 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelClientSendHandlerTest.java @@ -39,179 +39,185 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class HttpTunnelClientSendHandlerTest -{ - - private static final InetSocketAddress SERVER_ADDRESS = createAddress(new byte[] - {10, 0, 0, 3}, 12345); - - private static final InetSocketAddress PROXY_ADDRESS = createAddress(new byte[] - {10, 0, 0, 2}, 8888); - - private static final InetSocketAddress LOCAL_ADDRESS = createAddress(new byte[] - {10, 0, 0, 1}, 54321); - - private FakeSocketChannel channel; - - private FakeChannelSink sink; - - private HttpTunnelClientSendHandler handler; - - private MockChannelStateListener listener; - - @Before - public void setUp() - { - sink = new FakeChannelSink(); - ChannelPipeline pipeline = Channels.pipeline(); - listener = new MockChannelStateListener(); - listener.serverHostName = HttpTunnelMessageUtils.convertToHostString(SERVER_ADDRESS); - handler = new HttpTunnelClientSendHandler(listener); - pipeline.addLast(HttpTunnelClientSendHandler.NAME, handler); - channel = new FakeSocketChannel(null, null, pipeline, sink); - channel.remoteAddress = PROXY_ADDRESS; - channel.localAddress = LOCAL_ADDRESS; - } - - private static InetSocketAddress createAddress(byte[] addr, int port) - { - try - { - return new InetSocketAddress(InetAddress.getByAddress(addr), port); - } - catch (UnknownHostException e) - { - throw new RuntimeException("Bad address in test"); - } - } - - @Test - public void testSendsTunnelOpen() throws Exception - { - Channels.fireChannelConnected(channel, PROXY_ADDRESS); - assertEquals(1, sink.events.size()); - HttpRequest request = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpRequest.class); - assertTrue(HttpTunnelMessageUtils.isOpenTunnelRequest(request)); - assertTrue(HttpTunnelMessageUtils.checkHost(request, SERVER_ADDRESS)); - } - - @Test - public void testStoresTunnelId() throws Exception - { - emulateConnect(); - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelOpenResponse("newTunnel")); - assertEquals("newTunnel", handler.getTunnelId()); - assertEquals("newTunnel", listener.tunnelId); - } - - @Test - public void testSendData() - { - emulateConnectAndOpen(); - channel.write(NettyTestUtils.createData(1234L)); - assertEquals(1, sink.events.size()); - ChannelEvent sentEvent = sink.events.poll(); - checkIsSendDataRequestWithData(sentEvent, NettyTestUtils.createData(1234L)); - } - - @Test - public void testWillNotSendDataUntilTunnelIdSet() - { - emulateConnect(); - channel.write(NettyTestUtils.createData(1234L)); - - assertEquals(0, sink.events.size()); - - Channels.fireChannelConnected(channel, PROXY_ADDRESS); - assertEquals(1, sink.events.size()); - } - - @Test - public void testOnlyOneRequestAtATime() - { - emulateConnectAndOpen(); - - channel.write(NettyTestUtils.createData(1234L)); - assertEquals(1, sink.events.size()); - checkIsSendDataRequestWithData(sink.events.poll(), NettyTestUtils.createData(1234L)); - - channel.write(NettyTestUtils.createData(5678L)); - assertEquals(0, sink.events.size()); - - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createSendDataResponse()); - assertEquals(1, sink.events.size()); - checkIsSendDataRequestWithData(sink.events.poll(), NettyTestUtils.createData(5678L)); - } - - @Test - public void testDisconnect() - { - emulateConnectAndOpen(); - - channel.write(NettyTestUtils.createData(1234L)); - assertEquals(1, sink.events.size()); - checkIsSendDataRequestWithData(sink.events.poll(), NettyTestUtils.createData(1234L)); - - channel.disconnect(); - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createSendDataResponse()); - assertEquals(1, sink.events.size()); - - HttpRequest request = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpRequest.class); - assertTrue(HttpTunnelMessageUtils.isCloseTunnelRequest(request)); - assertEquals("newTunnel", HttpTunnelMessageUtils.extractTunnelId(request)); - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelCloseResponse()); - assertEquals(1, sink.events.size()); - NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.CONNECTED, null); - } - - @Test - public void testClose() - { - emulateConnectAndOpen(); - - channel.close(); - assertEquals(1, sink.events.size()); - HttpRequest request = NettyTestUtils.checkIsDownstreamMessageEvent(sink.events.poll(), HttpRequest.class); - assertTrue(HttpTunnelMessageUtils.isCloseTunnelRequest(request)); - assertEquals("newTunnel", HttpTunnelMessageUtils.extractTunnelId(request)); - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelCloseResponse()); - assertEquals(1, sink.events.size()); - NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.OPEN, false); - } - - @Test - public void testWritesAfterCloseAreRejected() - { - emulateConnectAndOpen(); - - channel.close(); - assertFalse(channel.write(NettyTestUtils.createData(1234L)).isSuccess()); - } - - private void checkIsSendDataRequestWithData(ChannelEvent event, ChannelBuffer data) - { - assertTrue(event instanceof DownstreamMessageEvent); - DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; - assertTrue(messageEvent.getMessage() instanceof HttpRequest); - HttpRequest request = (HttpRequest) messageEvent.getMessage(); - assertTrue(HttpTunnelMessageUtils.isSendDataRequest(request)); - assertEquals(data.readableBytes(), HttpHeaders.getContentLength(request)); - - ChannelBuffer content = request.getContent(); - NettyTestUtils.assertEquals(data, content); - } - - private void emulateConnect() - { - channel.emulateConnected(LOCAL_ADDRESS, PROXY_ADDRESS, null); - sink.events.clear(); - } - - private void emulateConnectAndOpen() - { - emulateConnect(); - Channels.fireMessageReceived(channel, HttpTunnelMessageUtils.createTunnelOpenResponse("newTunnel")); - - sink.events.clear(); - } +public class HttpTunnelClientSendHandlerTest { + + private static final InetSocketAddress SERVER_ADDRESS = createAddress( + new byte[] { 10, 0, 0, 3 }, 12345); + + private static final InetSocketAddress PROXY_ADDRESS = createAddress( + new byte[] { 10, 0, 0, 2 }, 8888); + + private static final InetSocketAddress LOCAL_ADDRESS = createAddress( + new byte[] { 10, 0, 0, 1 }, 54321); + + private FakeSocketChannel channel; + + private FakeChannelSink sink; + + private HttpTunnelClientSendHandler handler; + + private MockChannelStateListener listener; + + @Before + public void setUp() { + sink = new FakeChannelSink(); + ChannelPipeline pipeline = Channels.pipeline(); + listener = new MockChannelStateListener(); + listener.serverHostName = + HttpTunnelMessageUtils.convertToHostString(SERVER_ADDRESS); + handler = new HttpTunnelClientSendHandler(listener); + pipeline.addLast(HttpTunnelClientSendHandler.NAME, handler); + channel = new FakeSocketChannel(null, null, pipeline, sink); + channel.remoteAddress = PROXY_ADDRESS; + channel.localAddress = LOCAL_ADDRESS; + } + + private static InetSocketAddress createAddress(byte[] addr, int port) { + try { + return new InetSocketAddress(InetAddress.getByAddress(addr), port); + } catch (UnknownHostException e) { + throw new RuntimeException("Bad address in test"); + } + } + + @Test + public void testSendsTunnelOpen() throws Exception { + Channels.fireChannelConnected(channel, PROXY_ADDRESS); + assertEquals(1, sink.events.size()); + HttpRequest request = + NettyTestUtils.checkIsDownstreamMessageEvent( + sink.events.poll(), HttpRequest.class); + assertTrue(HttpTunnelMessageUtils.isOpenTunnelRequest(request)); + assertTrue(HttpTunnelMessageUtils.checkHost(request, SERVER_ADDRESS)); + } + + @Test + public void testStoresTunnelId() throws Exception { + emulateConnect(); + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createTunnelOpenResponse("newTunnel")); + assertEquals("newTunnel", handler.getTunnelId()); + assertEquals("newTunnel", listener.tunnelId); + } + + @Test + public void testSendData() { + emulateConnectAndOpen(); + channel.write(NettyTestUtils.createData(1234L)); + assertEquals(1, sink.events.size()); + ChannelEvent sentEvent = sink.events.poll(); + checkIsSendDataRequestWithData(sentEvent, + NettyTestUtils.createData(1234L)); + } + + @Test + public void testWillNotSendDataUntilTunnelIdSet() { + emulateConnect(); + channel.write(NettyTestUtils.createData(1234L)); + + assertEquals(0, sink.events.size()); + + Channels.fireChannelConnected(channel, PROXY_ADDRESS); + assertEquals(1, sink.events.size()); + } + + @Test + public void testOnlyOneRequestAtATime() { + emulateConnectAndOpen(); + + channel.write(NettyTestUtils.createData(1234L)); + assertEquals(1, sink.events.size()); + checkIsSendDataRequestWithData(sink.events.poll(), + NettyTestUtils.createData(1234L)); + + channel.write(NettyTestUtils.createData(5678L)); + assertEquals(0, sink.events.size()); + + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createSendDataResponse()); + assertEquals(1, sink.events.size()); + checkIsSendDataRequestWithData(sink.events.poll(), + NettyTestUtils.createData(5678L)); + } + + @Test + public void testDisconnect() { + emulateConnectAndOpen(); + + channel.write(NettyTestUtils.createData(1234L)); + assertEquals(1, sink.events.size()); + checkIsSendDataRequestWithData(sink.events.poll(), + NettyTestUtils.createData(1234L)); + + channel.disconnect(); + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createSendDataResponse()); + assertEquals(1, sink.events.size()); + + HttpRequest request = + NettyTestUtils.checkIsDownstreamMessageEvent( + sink.events.poll(), HttpRequest.class); + assertTrue(HttpTunnelMessageUtils.isCloseTunnelRequest(request)); + assertEquals("newTunnel", + HttpTunnelMessageUtils.extractTunnelId(request)); + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createTunnelCloseResponse()); + assertEquals(1, sink.events.size()); + NettyTestUtils.checkIsStateEvent(sink.events.poll(), + ChannelState.CONNECTED, null); + } + + @Test + public void testClose() { + emulateConnectAndOpen(); + + channel.close(); + assertEquals(1, sink.events.size()); + HttpRequest request = + NettyTestUtils.checkIsDownstreamMessageEvent( + sink.events.poll(), HttpRequest.class); + assertTrue(HttpTunnelMessageUtils.isCloseTunnelRequest(request)); + assertEquals("newTunnel", + HttpTunnelMessageUtils.extractTunnelId(request)); + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createTunnelCloseResponse()); + assertEquals(1, sink.events.size()); + NettyTestUtils.checkIsStateEvent(sink.events.poll(), ChannelState.OPEN, + false); + } + + @Test + public void testWritesAfterCloseAreRejected() { + emulateConnectAndOpen(); + + channel.close(); + assertFalse(channel.write(NettyTestUtils.createData(1234L)).isSuccess()); + } + + private void checkIsSendDataRequestWithData(ChannelEvent event, + ChannelBuffer data) { + assertTrue(event instanceof DownstreamMessageEvent); + DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; + assertTrue(messageEvent.getMessage() instanceof HttpRequest); + HttpRequest request = (HttpRequest) messageEvent.getMessage(); + assertTrue(HttpTunnelMessageUtils.isSendDataRequest(request)); + assertEquals(data.readableBytes(), + HttpHeaders.getContentLength(request)); + + ChannelBuffer content = request.getContent(); + NettyTestUtils.assertEquals(data, content); + } + + private void emulateConnect() { + channel.emulateConnected(LOCAL_ADDRESS, PROXY_ADDRESS, null); + sink.events.clear(); + } + + private void emulateConnectAndOpen() { + emulateConnect(); + Channels.fireMessageReceived(channel, + HttpTunnelMessageUtils.createTunnelOpenResponse("newTunnel")); + + sink.events.clear(); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java index d926cc67af8..cb592a096b1 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelFactoryTest.java @@ -37,75 +37,70 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class HttpTunnelServerChannelFactoryTest -{ +public class HttpTunnelServerChannelFactoryTest { - private final JUnit4Mockery mockContext = new JUnit4Mockery(); + private final JUnit4Mockery mockContext = new JUnit4Mockery(); - ServerSocketChannelFactory realChannelFactory; + ServerSocketChannelFactory realChannelFactory; - private HttpTunnelServerChannelFactory factory; + private HttpTunnelServerChannelFactory factory; - ServerSocketChannel realChannel; + ServerSocketChannel realChannel; - @Before - public void setUp() throws Exception - { - realChannelFactory = mockContext.mock(ServerSocketChannelFactory.class); - factory = new HttpTunnelServerChannelFactory(realChannelFactory); - ChannelPipeline pipeline = Channels.pipeline(); - realChannel = new FakeServerSocketChannel(factory, pipeline, new FakeChannelSink()); - } + @Before + public void setUp() throws Exception { + realChannelFactory = mockContext.mock(ServerSocketChannelFactory.class); + factory = new HttpTunnelServerChannelFactory(realChannelFactory); + ChannelPipeline pipeline = Channels.pipeline(); + realChannel = + new FakeServerSocketChannel(factory, pipeline, + new FakeChannelSink()); + } - @Test - public void testNewChannel() - { - mockContext.checking(new Expectations() - { - { - one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); - will(returnValue(realChannel)); - } - }); - ChannelPipeline pipeline = Channels.pipeline(); - HttpTunnelServerChannel newChannel = factory.newChannel(pipeline); - assertNotNull(newChannel); - assertSame(pipeline, newChannel.getPipeline()); - } + @Test + public void testNewChannel() { + mockContext.checking(new Expectations() { + { + one(realChannelFactory).newChannel( + with(any(ChannelPipeline.class))); + will(returnValue(realChannel)); + } + }); + ChannelPipeline pipeline = Channels.pipeline(); + HttpTunnelServerChannel newChannel = factory.newChannel(pipeline); + assertNotNull(newChannel); + assertSame(pipeline, newChannel.getPipeline()); + } - @Test - public void testNewChannel_forwardsWrappedFactoryFailure() - { - final ChannelException innerException = new ChannelException(); - mockContext.checking(new Expectations() - { - { - one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); - will(throwException(innerException)); - } - }); + @Test + public void testNewChannel_forwardsWrappedFactoryFailure() { + final ChannelException innerException = new ChannelException(); + mockContext.checking(new Expectations() { + { + one(realChannelFactory).newChannel( + with(any(ChannelPipeline.class))); + will(throwException(innerException)); + } + }); - try - { - factory.newChannel(Channels.pipeline()); - fail("Expected ChannelException"); - } - catch (ChannelException e) - { - assertSame(innerException, e); - } - } + try { + factory.newChannel(Channels.pipeline()); + fail("Expected ChannelException"); + } catch (ChannelException e) { + assertSame(innerException, e); + } + } - // @Test - // public void testChannelCreation_withServerBootstrap() { - // mockContext.checking(new Expectations() {{ - // one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); will(returnValue(realChannel)); - // }}); - // - // ServerBootstrap bootstrap = new ServerBootstrap(factory); - // Channel newChannel = bootstrap.bind(new InetSocketAddress(80)); - // assertNotNull(newChannel); - // - // } + // @Test + // public void testChannelCreation_withServerBootstrap() { + // mockContext.checking(new Expectations() {{ + // one(realChannelFactory).newChannel(with(any(ChannelPipeline.class))); will(returnValue(realChannel)); + // }}); + // + // ServerBootstrap bootstrap = new ServerBootstrap(factory); + // Channel newChannel = bootstrap.bind(new InetSocketAddress(80)); + // assertNotNull(newChannel); + // + // } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java index c85d8038bb7..57b00307034 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelSinkTest.java @@ -42,132 +42,118 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class HttpTunnelServerChannelSinkTest -{ - - private final JUnit4Mockery mockContext = new JUnit4Mockery(); - - private HttpTunnelServerChannelSink sink; - - private ChannelPipeline pipeline; - - private FakeSocketChannel channel; - - ServerSocketChannel realChannel; - - ChannelFuture realFuture; - - Throwable exceptionInPipeline; - - @Before - public void setUp() throws Exception - { - realChannel = mockContext.mock(ServerSocketChannel.class); - pipeline = Channels.pipeline(); - pipeline.addLast("exceptioncatcher", new ExceptionCatcher()); - sink = new HttpTunnelServerChannelSink(); - sink.setRealChannel(realChannel); - channel = new FakeSocketChannel(null, null, pipeline, sink); - realFuture = Channels.future(realChannel); - } - - @After - public void teardown() throws Exception - { - assertTrue("exception caught in pipeline: " + exceptionInPipeline, exceptionInPipeline == null); - } - - public void testCloseRequest() throws Exception - { - mockContext.checking(new Expectations() - { - { - one(realChannel).close(); - will(returnValue(realFuture)); - } - }); - - ChannelFuture virtualFuture1 = Channels.close(channel); - mockContext.assertIsSatisfied(); - ChannelFuture virtualFuture = virtualFuture1; - realFuture.setSuccess(); - assertTrue(virtualFuture.isSuccess()); - } - - @Test - public void testUnbindRequest_withSuccess() throws Exception - { - ChannelFuture virtualFuture = checkUnbind(); - realFuture.setSuccess(); - assertTrue(virtualFuture.isSuccess()); - } - - @Test - public void testUnbindRequest_withFailure() throws Exception - { - ChannelFuture virtualFuture = checkUnbind(); - realFuture.setFailure(new Exception("Something bad happened")); - assertFalse(virtualFuture.isSuccess()); - } - - private ChannelFuture checkUnbind() - { - mockContext.checking(new Expectations() - { - { - one(realChannel).unbind(); - will(returnValue(realFuture)); - } - }); - - ChannelFuture virtualFuture = Channels.unbind(channel); - mockContext.assertIsSatisfied(); - return virtualFuture; - } - - @Test - public void testBindRequest_withSuccess() - { - ChannelFuture virtualFuture = checkBind(); - realFuture.setSuccess(); - assertTrue(virtualFuture.isSuccess()); - } - - @Test - public void testBindRequest_withFailure() - { - ChannelFuture virtualFuture = checkBind(); - realFuture.setFailure(new Exception("Something bad happened")); - assertFalse(virtualFuture.isSuccess()); - } - - private ChannelFuture checkBind() - { - final SocketAddress toAddress = new InetSocketAddress(80); - mockContext.checking(new Expectations() - { - { - one(realChannel).bind(toAddress); - will(returnValue(realFuture)); - } - }); - - ChannelFuture virtualFuture = Channels.bind(channel, toAddress); - return virtualFuture; - } - - private final class ExceptionCatcher extends SimpleChannelUpstreamHandler - { - - ExceptionCatcher() - { - super(); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception - { - exceptionInPipeline = e.getCause(); - } - } +public class HttpTunnelServerChannelSinkTest { + + private final JUnit4Mockery mockContext = new JUnit4Mockery(); + + private HttpTunnelServerChannelSink sink; + + private ChannelPipeline pipeline; + + private FakeSocketChannel channel; + + ServerSocketChannel realChannel; + + ChannelFuture realFuture; + + Throwable exceptionInPipeline; + + @Before + public void setUp() throws Exception { + realChannel = mockContext.mock(ServerSocketChannel.class); + pipeline = Channels.pipeline(); + pipeline.addLast("exceptioncatcher", new ExceptionCatcher()); + sink = new HttpTunnelServerChannelSink(); + sink.setRealChannel(realChannel); + channel = new FakeSocketChannel(null, null, pipeline, sink); + realFuture = Channels.future(realChannel); + } + + @After + public void teardown() throws Exception { + assertTrue("exception caught in pipeline: " + exceptionInPipeline, + exceptionInPipeline == null); + } + + public void testCloseRequest() throws Exception { + mockContext.checking(new Expectations() { + { + one(realChannel).close(); + will(returnValue(realFuture)); + } + }); + + ChannelFuture virtualFuture1 = Channels.close(channel); + mockContext.assertIsSatisfied(); + ChannelFuture virtualFuture = virtualFuture1; + realFuture.setSuccess(); + assertTrue(virtualFuture.isSuccess()); + } + + @Test + public void testUnbindRequest_withSuccess() throws Exception { + ChannelFuture virtualFuture = checkUnbind(); + realFuture.setSuccess(); + assertTrue(virtualFuture.isSuccess()); + } + + @Test + public void testUnbindRequest_withFailure() throws Exception { + ChannelFuture virtualFuture = checkUnbind(); + realFuture.setFailure(new Exception("Something bad happened")); + assertFalse(virtualFuture.isSuccess()); + } + + private ChannelFuture checkUnbind() { + mockContext.checking(new Expectations() { + { + one(realChannel).unbind(); + will(returnValue(realFuture)); + } + }); + + ChannelFuture virtualFuture = Channels.unbind(channel); + mockContext.assertIsSatisfied(); + return virtualFuture; + } + + @Test + public void testBindRequest_withSuccess() { + ChannelFuture virtualFuture = checkBind(); + realFuture.setSuccess(); + assertTrue(virtualFuture.isSuccess()); + } + + @Test + public void testBindRequest_withFailure() { + ChannelFuture virtualFuture = checkBind(); + realFuture.setFailure(new Exception("Something bad happened")); + assertFalse(virtualFuture.isSuccess()); + } + + private ChannelFuture checkBind() { + final SocketAddress toAddress = new InetSocketAddress(80); + mockContext.checking(new Expectations() { + { + one(realChannel).bind(toAddress); + will(returnValue(realFuture)); + } + }); + + ChannelFuture virtualFuture = Channels.bind(channel, toAddress); + return virtualFuture; + } + + private final class ExceptionCatcher extends SimpleChannelUpstreamHandler { + + ExceptionCatcher() { + super(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + exceptionInPipeline = e.getCause(); + } + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java index 256c607fdb2..baaaa9c41d3 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelServerChannelTest.java @@ -46,197 +46,195 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class HttpTunnelServerChannelTest -{ - - JUnit4Mockery mockContext = new JUnit4Mockery(); - - private HttpTunnelServerChannel virtualChannel; - - private UpstreamEventCatcher upstreamEvents; - - private FakeServerSocketChannelFactory realChannelFactory; - - @Before - public void setUp() throws Exception - { - realChannelFactory = new FakeServerSocketChannelFactory(); - realChannelFactory.sink = new FakeChannelSink(); - - HttpTunnelServerChannelFactory factory = new HttpTunnelServerChannelFactory(realChannelFactory); - virtualChannel = factory.newChannel(createVirtualChannelPipeline()); - } - - private ChannelPipeline createVirtualChannelPipeline() - { - ChannelPipeline pipeline = Channels.pipeline(); - upstreamEvents = new UpstreamEventCatcher(); - pipeline.addLast(UpstreamEventCatcher.NAME, upstreamEvents); - return pipeline; - } - - @Test - public void testGetLocalAddress_delegatedToRealChannel() - { - realChannelFactory.createdChannel.localAddress = InetSocketAddress.createUnresolved("mycomputer", 80); - SocketAddress returned = virtualChannel.getLocalAddress(); - assertSame(realChannelFactory.createdChannel.localAddress, returned); - } - - @Test - public void testGetRemoteAddress_returnsNull() - { - assertNull(virtualChannel.getRemoteAddress()); - } - - @Test - public void testIsBound_delegatedToRealChannel() - { - realChannelFactory.createdChannel.bound = true; - assertTrue(virtualChannel.isBound()); - realChannelFactory.createdChannel.bound = false; - assertFalse(virtualChannel.isBound()); - } - - @Test - public void testConstruction_firesOpenEvent() - { - assertTrue(upstreamEvents.events.size() > 0); - checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.OPEN, Boolean.TRUE); - } - - @Test - public void testChannelBoundEventFromReal_replicatedOnVirtual() - { - upstreamEvents.events.clear(); - InetSocketAddress boundAddr = InetSocketAddress.createUnresolved("mycomputer", 12345); - Channels.fireChannelBound(realChannelFactory.createdChannel, boundAddr); - assertEquals(1, upstreamEvents.events.size()); - checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.BOUND, boundAddr); - } - - @Test - public void testChannelUnboundEventFromReal_replicatedOnVirtual() - { - upstreamEvents.events.clear(); - Channels.fireChannelUnbound(realChannelFactory.createdChannel); - assertEquals(1, upstreamEvents.events.size()); - checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.BOUND, null); - } - - @Test - public void testChannelClosedEventFromReal_replicatedOnVirtual() - { - upstreamEvents.events.clear(); - Channels.fireChannelClosed(realChannelFactory.createdChannel); - assertEquals(1, upstreamEvents.events.size()); - checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), virtualChannel, ChannelState.OPEN, Boolean.FALSE); - } - - @Test - public void testHasConfiguration() - { - assertNotNull(virtualChannel.getConfig()); - } - - @Test - public void testChangePipelineFactoryDoesNotAffectRealChannel() - { - ChannelPipelineFactory realPipelineFactory = realChannelFactory.createdChannel.getConfig().getPipelineFactory(); - ChannelPipelineFactory virtualPipelineFactory = mockContext.mock(ChannelPipelineFactory.class); - virtualChannel.getConfig().setPipelineFactory(virtualPipelineFactory); - assertSame(virtualPipelineFactory, virtualChannel.getConfig().getPipelineFactory()); - - // channel pipeline factory is a special case: we do not want it set on the configuration - // of the underlying factory - assertSame(realPipelineFactory, realChannelFactory.createdChannel.getConfig().getPipelineFactory()); - } - - @Test - public void testChangingBacklogAffectsRealChannel() - { - virtualChannel.getConfig().setBacklog(1234); - assertEquals(1234, realChannelFactory.createdChannel.getConfig().getBacklog()); - } - - @Test - public void testChangingConnectTimeoutMillisAffectsRealChannel() - { - virtualChannel.getConfig().setConnectTimeoutMillis(54321); - assertEquals(54321, realChannelFactory.createdChannel.getConfig().getConnectTimeoutMillis()); - } - - @Test - public void testChangingPerformancePreferencesAffectsRealChannel() - { - final ServerSocketChannelConfig mockConfig = mockContext.mock(ServerSocketChannelConfig.class); - realChannelFactory.createdChannel.config = mockConfig; - mockContext.checking(new Expectations() - { - { - one(mockConfig).setPerformancePreferences(100, 200, 300); - } - }); - virtualChannel.getConfig().setPerformancePreferences(100, 200, 300); - mockContext.assertIsSatisfied(); - } - - @Test - public void testChangingReceiveBufferSizeAffectsRealChannel() - { - virtualChannel.getConfig().setReceiveBufferSize(10101); - assertEquals(10101, realChannelFactory.createdChannel.getConfig().getReceiveBufferSize()); - } - - @Test - public void testChangingReuseAddressAffectsRealChannel() - { - virtualChannel.getConfig().setReuseAddress(true); - assertEquals(true, realChannelFactory.createdChannel.getConfig().isReuseAddress()); - } - - @Test - public void testSetChannelPipelineFactoryViaOption() - { - final ServerSocketChannelConfig mockConfig = mockContext.mock(ServerSocketChannelConfig.class); - realChannelFactory.createdChannel.config = mockConfig; - - mockContext.checking(new Expectations() - { - { - never(mockConfig); - } - }); - - ChannelPipelineFactory factory = mockContext.mock(ChannelPipelineFactory.class); - virtualChannel.getConfig().setOption("pipelineFactory", factory); - assertSame(factory, virtualChannel.getConfig().getPipelineFactory()); - } - - @Test - public void testSetOptionAffectsRealChannel() - { - final ServerSocketChannelConfig mockConfig = mockContext.mock(ServerSocketChannelConfig.class); - realChannelFactory.createdChannel.config = mockConfig; - - mockContext.checking(new Expectations() - { - { - one(mockConfig).setOption("testOption", "testValue"); - } - }); - - virtualChannel.getConfig().setOption("testOption", "testValue"); - } - - private void checkIsUpstreamChannelStateEvent(ChannelEvent ev, Channel expectedChannel, ChannelState expectedState, - Object expectedValue) - { - assertTrue(ev instanceof UpstreamChannelStateEvent); - UpstreamChannelStateEvent checkedEv = (UpstreamChannelStateEvent) ev; - assertSame(expectedChannel, checkedEv.getChannel()); - assertEquals(expectedState, checkedEv.getState()); - assertEquals(expectedValue, checkedEv.getValue()); - } +public class HttpTunnelServerChannelTest { + + JUnit4Mockery mockContext = new JUnit4Mockery(); + + private HttpTunnelServerChannel virtualChannel; + + private UpstreamEventCatcher upstreamEvents; + + private FakeServerSocketChannelFactory realChannelFactory; + + @Before + public void setUp() throws Exception { + realChannelFactory = new FakeServerSocketChannelFactory(); + realChannelFactory.sink = new FakeChannelSink(); + + HttpTunnelServerChannelFactory factory = + new HttpTunnelServerChannelFactory(realChannelFactory); + virtualChannel = factory.newChannel(createVirtualChannelPipeline()); + } + + private ChannelPipeline createVirtualChannelPipeline() { + ChannelPipeline pipeline = Channels.pipeline(); + upstreamEvents = new UpstreamEventCatcher(); + pipeline.addLast(UpstreamEventCatcher.NAME, upstreamEvents); + return pipeline; + } + + @Test + public void testGetLocalAddress_delegatedToRealChannel() { + realChannelFactory.createdChannel.localAddress = + InetSocketAddress.createUnresolved("mycomputer", 80); + SocketAddress returned = virtualChannel.getLocalAddress(); + assertSame(realChannelFactory.createdChannel.localAddress, returned); + } + + @Test + public void testGetRemoteAddress_returnsNull() { + assertNull(virtualChannel.getRemoteAddress()); + } + + @Test + public void testIsBound_delegatedToRealChannel() { + realChannelFactory.createdChannel.bound = true; + assertTrue(virtualChannel.isBound()); + realChannelFactory.createdChannel.bound = false; + assertFalse(virtualChannel.isBound()); + } + + @Test + public void testConstruction_firesOpenEvent() { + assertTrue(upstreamEvents.events.size() > 0); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), + virtualChannel, ChannelState.OPEN, Boolean.TRUE); + } + + @Test + public void testChannelBoundEventFromReal_replicatedOnVirtual() { + upstreamEvents.events.clear(); + InetSocketAddress boundAddr = + InetSocketAddress.createUnresolved("mycomputer", 12345); + Channels.fireChannelBound(realChannelFactory.createdChannel, boundAddr); + assertEquals(1, upstreamEvents.events.size()); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), + virtualChannel, ChannelState.BOUND, boundAddr); + } + + @Test + public void testChannelUnboundEventFromReal_replicatedOnVirtual() { + upstreamEvents.events.clear(); + Channels.fireChannelUnbound(realChannelFactory.createdChannel); + assertEquals(1, upstreamEvents.events.size()); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), + virtualChannel, ChannelState.BOUND, null); + } + + @Test + public void testChannelClosedEventFromReal_replicatedOnVirtual() { + upstreamEvents.events.clear(); + Channels.fireChannelClosed(realChannelFactory.createdChannel); + assertEquals(1, upstreamEvents.events.size()); + checkIsUpstreamChannelStateEvent(upstreamEvents.events.poll(), + virtualChannel, ChannelState.OPEN, Boolean.FALSE); + } + + @Test + public void testHasConfiguration() { + assertNotNull(virtualChannel.getConfig()); + } + + @Test + public void testChangePipelineFactoryDoesNotAffectRealChannel() { + ChannelPipelineFactory realPipelineFactory = + realChannelFactory.createdChannel.getConfig() + .getPipelineFactory(); + ChannelPipelineFactory virtualPipelineFactory = + mockContext.mock(ChannelPipelineFactory.class); + virtualChannel.getConfig().setPipelineFactory(virtualPipelineFactory); + assertSame(virtualPipelineFactory, virtualChannel.getConfig() + .getPipelineFactory()); + + // channel pipeline factory is a special case: we do not want it set on the configuration + // of the underlying factory + assertSame(realPipelineFactory, realChannelFactory.createdChannel + .getConfig().getPipelineFactory()); + } + + @Test + public void testChangingBacklogAffectsRealChannel() { + virtualChannel.getConfig().setBacklog(1234); + assertEquals(1234, realChannelFactory.createdChannel.getConfig() + .getBacklog()); + } + + @Test + public void testChangingConnectTimeoutMillisAffectsRealChannel() { + virtualChannel.getConfig().setConnectTimeoutMillis(54321); + assertEquals(54321, realChannelFactory.createdChannel.getConfig() + .getConnectTimeoutMillis()); + } + + @Test + public void testChangingPerformancePreferencesAffectsRealChannel() { + final ServerSocketChannelConfig mockConfig = + mockContext.mock(ServerSocketChannelConfig.class); + realChannelFactory.createdChannel.config = mockConfig; + mockContext.checking(new Expectations() { + { + one(mockConfig).setPerformancePreferences(100, 200, 300); + } + }); + virtualChannel.getConfig().setPerformancePreferences(100, 200, 300); + mockContext.assertIsSatisfied(); + } + + @Test + public void testChangingReceiveBufferSizeAffectsRealChannel() { + virtualChannel.getConfig().setReceiveBufferSize(10101); + assertEquals(10101, realChannelFactory.createdChannel.getConfig() + .getReceiveBufferSize()); + } + + @Test + public void testChangingReuseAddressAffectsRealChannel() { + virtualChannel.getConfig().setReuseAddress(true); + assertEquals(true, realChannelFactory.createdChannel.getConfig() + .isReuseAddress()); + } + + @Test + public void testSetChannelPipelineFactoryViaOption() { + final ServerSocketChannelConfig mockConfig = + mockContext.mock(ServerSocketChannelConfig.class); + realChannelFactory.createdChannel.config = mockConfig; + + mockContext.checking(new Expectations() { + { + never(mockConfig); + } + }); + + ChannelPipelineFactory factory = + mockContext.mock(ChannelPipelineFactory.class); + virtualChannel.getConfig().setOption("pipelineFactory", factory); + assertSame(factory, virtualChannel.getConfig().getPipelineFactory()); + } + + @Test + public void testSetOptionAffectsRealChannel() { + final ServerSocketChannelConfig mockConfig = + mockContext.mock(ServerSocketChannelConfig.class); + realChannelFactory.createdChannel.config = mockConfig; + + mockContext.checking(new Expectations() { + { + one(mockConfig).setOption("testOption", "testValue"); + } + }); + + virtualChannel.getConfig().setOption("testOption", "testValue"); + } + + private void checkIsUpstreamChannelStateEvent(ChannelEvent ev, + Channel expectedChannel, ChannelState expectedState, + Object expectedValue) { + assertTrue(ev instanceof UpstreamChannelStateEvent); + UpstreamChannelStateEvent checkedEv = (UpstreamChannelStateEvent) ev; + assertSame(expectedChannel, checkedEv.getChannel()); + assertEquals(expectedState, checkedEv.getState()); + assertEquals(expectedValue, checkedEv.getValue()); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java index 7fe0d50b8ed..0a271b7c294 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelSoakTester.java @@ -56,432 +56,425 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class HttpTunnelSoakTester -{ - - private static final int SERVER_PORT = 20100; - - static final Logger LOG = Logger.getLogger(HttpTunnelSoakTester.class.getName()); - - private static final long BYTES_TO_SEND = 1024 * 1024 * 1024; - - private static final int MAX_WRITE_SIZE = 64 * 1024; - - private final ServerBootstrap serverBootstrap; - - private final ClientBootstrap clientBootstrap; - - final ChannelGroup channels; - - private final ExecutorService executor; - - final ScheduledExecutorService scheduledExecutor; - - final DataSender c2sDataSender = new DataSender("C2S"); - final DataSender s2cDataSender = new DataSender("S2C"); - - private DataVerifier c2sVerifier = new DataVerifier("C2S-Verifier"); - private DataVerifier s2cVerifier = new DataVerifier("S2C-Verifier"); - - private static byte[] SEND_STREAM; - - static { - SEND_STREAM = new byte[MAX_WRITE_SIZE + 127]; - for(int i=0; i < SEND_STREAM.length; i++) { - SEND_STREAM[i] = (byte)(i % 127); - } - } - - public HttpTunnelSoakTester() - { - scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); - executor = Executors.newCachedThreadPool(); - ServerSocketChannelFactory serverChannelFactory = new NioServerSocketChannelFactory(executor, executor); - HttpTunnelServerChannelFactory serverTunnelFactory = new HttpTunnelServerChannelFactory(serverChannelFactory); - - serverBootstrap = new ServerBootstrap(serverTunnelFactory); - serverBootstrap.setPipelineFactory(createServerPipelineFactory()); - - ClientSocketChannelFactory clientChannelFactory = new NioClientSocketChannelFactory(executor, executor); - HttpTunnelClientChannelFactory clientTunnelFactory = new HttpTunnelClientChannelFactory(clientChannelFactory); - - clientBootstrap = new ClientBootstrap(clientTunnelFactory); - clientBootstrap.setPipelineFactory(createClientPipelineFactory()); - configureProxy(); - - channels = new DefaultChannelGroup(); - } - - private void configureProxy() - { - String proxyHost = System.getProperty("http.proxyHost"); - if (proxyHost != null && proxyHost.length() != 0) - { - int proxyPort = Integer.getInteger("http.proxyPort", 80); - InetAddress chosenAddress = chooseAddress(proxyHost); - InetSocketAddress proxyAddress = new InetSocketAddress(chosenAddress, proxyPort); - if (!proxyAddress.isUnresolved()) - { - clientBootstrap.setOption(HttpTunnelClientChannelConfig.PROXY_ADDRESS_OPTION, proxyAddress); - System.out.println("Using " + proxyAddress + " as a proxy for this test run"); - } - else - { - System.err.println("Failed to resolve proxy address " + proxyAddress); - } - } - else - { - System.out.println("No proxy specified, will connect to server directly"); - } - } - - private InetAddress chooseAddress(String proxyHost) - { - try - { - InetAddress[] allByName = InetAddress.getAllByName(proxyHost); - for (InetAddress address : allByName) - { - if (address.isAnyLocalAddress() || address.isLinkLocalAddress()) - { - continue; +public class HttpTunnelSoakTester { + + private static final int SERVER_PORT = 20100; + + static final Logger LOG = Logger.getLogger(HttpTunnelSoakTester.class + .getName()); + + private static final long BYTES_TO_SEND = 1024 * 1024 * 1024; + + private static final int MAX_WRITE_SIZE = 64 * 1024; + + private final ServerBootstrap serverBootstrap; + + private final ClientBootstrap clientBootstrap; + + final ChannelGroup channels; + + private final ExecutorService executor; + + final ScheduledExecutorService scheduledExecutor; + + final DataSender c2sDataSender = new DataSender("C2S"); + + final DataSender s2cDataSender = new DataSender("S2C"); + + private DataVerifier c2sVerifier = new DataVerifier("C2S-Verifier"); + + private DataVerifier s2cVerifier = new DataVerifier("S2C-Verifier"); + + private static byte[] SEND_STREAM; + + static { + SEND_STREAM = new byte[MAX_WRITE_SIZE + 127]; + for (int i = 0; i < SEND_STREAM.length; i ++) { + SEND_STREAM[i] = (byte) (i % 127); + } + } + + public HttpTunnelSoakTester() { + scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); + executor = Executors.newCachedThreadPool(); + ServerSocketChannelFactory serverChannelFactory = + new NioServerSocketChannelFactory(executor, executor); + HttpTunnelServerChannelFactory serverTunnelFactory = + new HttpTunnelServerChannelFactory(serverChannelFactory); + + serverBootstrap = new ServerBootstrap(serverTunnelFactory); + serverBootstrap.setPipelineFactory(createServerPipelineFactory()); + + ClientSocketChannelFactory clientChannelFactory = + new NioClientSocketChannelFactory(executor, executor); + HttpTunnelClientChannelFactory clientTunnelFactory = + new HttpTunnelClientChannelFactory(clientChannelFactory); + + clientBootstrap = new ClientBootstrap(clientTunnelFactory); + clientBootstrap.setPipelineFactory(createClientPipelineFactory()); + configureProxy(); + + channels = new DefaultChannelGroup(); + } + + private void configureProxy() { + String proxyHost = System.getProperty("http.proxyHost"); + if (proxyHost != null && proxyHost.length() != 0) { + int proxyPort = Integer.getInteger("http.proxyPort", 80); + InetAddress chosenAddress = chooseAddress(proxyHost); + InetSocketAddress proxyAddress = + new InetSocketAddress(chosenAddress, proxyPort); + if (!proxyAddress.isUnresolved()) { + clientBootstrap.setOption( + HttpTunnelClientChannelConfig.PROXY_ADDRESS_OPTION, + proxyAddress); + System.out.println("Using " + proxyAddress + + " as a proxy for this test run"); + } else { + System.err.println("Failed to resolve proxy address " + + proxyAddress); + } + } else { + System.out + .println("No proxy specified, will connect to server directly"); + } + } + + private InetAddress chooseAddress(String proxyHost) { + try { + InetAddress[] allByName = InetAddress.getAllByName(proxyHost); + for (InetAddress address: allByName) { + if (address.isAnyLocalAddress() || address.isLinkLocalAddress()) { + continue; + } + + return address; } - return address; - } - - return null; - } - catch (UnknownHostException e) - { - return null; - } - } - - protected ChannelPipelineFactory createClientPipelineFactory() - { - return new ChannelPipelineFactory() - { - - public ChannelPipeline getPipeline() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("s2cVerifier", s2cVerifier); - pipeline.addLast("throttleControl", new SendThrottle(c2sDataSender)); - return pipeline; - } - }; - } - - protected ChannelPipelineFactory createServerPipelineFactory() - { - return new ChannelPipelineFactory() - { - - public ChannelPipeline getPipeline() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("c2sVerifier", c2sVerifier); - pipeline.addLast("throttleControl", new SendThrottle(s2cDataSender)); - pipeline.addLast("sendStarter", new SimpleChannelUpstreamHandler() { - public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { - Channel childChannel = e.getChannel(); - channels.add(childChannel); - s2cDataSender.setChannel(childChannel); - executor.execute(s2cDataSender); - }; - }); - return pipeline; - } - }; - } - - public void run() throws InterruptedException - { - LOG.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()); - - SocketChannel clientChannel = createClientChannel(); - if (clientChannel == null) - { - LOG.severe("no client channel - bailing out"); - return; - } - - channels.add(clientChannel); - c2sDataSender.setChannel(clientChannel); - - executor.execute(c2sDataSender); - - if(!c2sDataSender.waitForFinish(5, TimeUnit.MINUTES)) { - LOG.severe("Data send from client to server failed"); - } - - if(!s2cDataSender.waitForFinish(5, TimeUnit.MINUTES)) { - LOG.severe("Data send from server to client failed"); - } - - - LOG.log(Level.INFO, "Waiting for verification to complete"); - if (!c2sVerifier.waitForCompletion(30L, TimeUnit.SECONDS)) - { - LOG.warning("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"); - } - - LOG.info("closing client channel"); - closeChannel(clientChannel); - LOG.info("server channel status: " + (serverChannel.isOpen() ? "open" : "closed")); - LOG.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"); - } - } - catch (InterruptedException e) - { - LOG.severe("Interrupted while closing connection"); - } - - } - - private SocketChannel createClientChannel() - { - InetSocketAddress serverAddress = new InetSocketAddress("localhost", SERVER_PORT); - ChannelFuture clientChannelFuture = clientBootstrap.connect(serverAddress); - try - { - if (!clientChannelFuture.await(1000, TimeUnit.MILLISECONDS)) - { - LOG.severe("did not connect within acceptable time period"); return null; - } - } - catch (InterruptedException e) - { - LOG.severe("Interrupted while waiting for client connect to be established"); - return null; - } - - if (!clientChannelFuture.isSuccess()) - { - LOG.log(Level.SEVERE, "did not connect successfully", clientChannelFuture.getCause()); - return null; - } - - HttpTunnelClientChannelConfig config = ((HttpTunnelClientChannelConfig)clientChannelFuture.getChannel().getConfig()); - config.setWriteBufferHighWaterMark(2 * 1024 * 1024); - config.setWriteBufferLowWaterMark(1024 * 1024); - - - return (SocketChannel) clientChannelFuture.getChannel(); - } - - private ChannelBuffer createRandomSizeBuffer(AtomicInteger nextWriteByte) - { - Random random = new Random(); - int arraySize = random.nextInt(MAX_WRITE_SIZE) + 1; - - // cheaply create the buffer by wrapping an appropriately sized section of the pre-built array - ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(SEND_STREAM, nextWriteByte.get(), arraySize); - nextWriteByte.set((nextWriteByte.get() + arraySize) % 127); - - return buffer; - } - - public static void main(String[] args) throws Exception - { - HttpTunnelSoakTester soakTester = new HttpTunnelSoakTester(); - try - { - soakTester.run(); - } - finally - { - soakTester.shutdown(); - } - } - - private void shutdown() - { - serverBootstrap.releaseExternalResources(); - clientBootstrap.releaseExternalResources(); - executor.shutdownNow(); - scheduledExecutor.shutdownNow(); - } - - private class DataVerifier extends SimpleChannelUpstreamHandler - { - private String name; - - private int expectedNext = 0; - - private int verifiedBytes = 0; - - private CountDownLatch completionLatch = new CountDownLatch(1); - - public DataVerifier(String name) - { - this.name = name; - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - ChannelBuffer bytesToVerify = (ChannelBuffer)e.getMessage(); - - while (bytesToVerify.readable()) - { - byte readByte = bytesToVerify.readByte(); - if (readByte != expectedNext) - { - LOG.log(Level.SEVERE, "{0}: received a byte out of sequence. Expected {1}, got {2}", new Object[] { name, expectedNext, readByte }); - System.exit(-1); - return; + } catch (UnknownHostException e) { + return null; + } + } + + protected ChannelPipelineFactory createClientPipelineFactory() { + return new ChannelPipelineFactory() { + + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("s2cVerifier", s2cVerifier); + pipeline.addLast("throttleControl", new SendThrottle( + c2sDataSender)); + return pipeline; + } + }; + } + + protected ChannelPipelineFactory createServerPipelineFactory() { + return new ChannelPipelineFactory() { + + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("c2sVerifier", c2sVerifier); + pipeline.addLast("throttleControl", new SendThrottle( + s2cDataSender)); + pipeline.addLast("sendStarter", + new SimpleChannelUpstreamHandler() { + public void channelConnected( + ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + Channel childChannel = e.getChannel(); + channels.add(childChannel); + s2cDataSender.setChannel(childChannel); + executor.execute(s2cDataSender); + }; + }); + return pipeline; } + }; + } + + public void run() throws InterruptedException { + LOG.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()); + + SocketChannel clientChannel = createClientChannel(); + if (clientChannel == null) { + LOG.severe("no client channel - bailing out"); + return; + } + + channels.add(clientChannel); + c2sDataSender.setChannel(clientChannel); + + executor.execute(c2sDataSender); + + if (!c2sDataSender.waitForFinish(5, TimeUnit.MINUTES)) { + LOG.severe("Data send from client to server failed"); + } + + if (!s2cDataSender.waitForFinish(5, TimeUnit.MINUTES)) { + LOG.severe("Data send from server to client failed"); + } + + LOG.log(Level.INFO, "Waiting for verification to complete"); + if (!c2sVerifier.waitForCompletion(30L, TimeUnit.SECONDS)) { + LOG.warning("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"); + } + + LOG.info("closing client channel"); + closeChannel(clientChannel); + LOG.info("server channel status: " + + (serverChannel.isOpen()? "open" : "closed")); + LOG.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"); + } + } catch (InterruptedException e) { + LOG.severe("Interrupted while closing connection"); + } + + } + + private SocketChannel createClientChannel() { + InetSocketAddress serverAddress = + new InetSocketAddress("localhost", SERVER_PORT); + ChannelFuture clientChannelFuture = + clientBootstrap.connect(serverAddress); + try { + if (!clientChannelFuture.await(1000, TimeUnit.MILLISECONDS)) { + LOG.severe("did not connect within acceptable time period"); + return null; + } + } catch (InterruptedException e) { + LOG.severe("Interrupted while waiting for client connect to be established"); + return null; + } - expectedNext = (expectedNext + 1) % 127; - verifiedBytes++; - } + if (!clientChannelFuture.isSuccess()) { + LOG.log(Level.SEVERE, "did not connect successfully", + clientChannelFuture.getCause()); + return null; + } + + HttpTunnelClientChannelConfig config = + ((HttpTunnelClientChannelConfig) clientChannelFuture + .getChannel().getConfig()); + config.setWriteBufferHighWaterMark(2 * 1024 * 1024); + config.setWriteBufferLowWaterMark(1024 * 1024); + + return (SocketChannel) clientChannelFuture.getChannel(); + } + + private ChannelBuffer createRandomSizeBuffer(AtomicInteger nextWriteByte) { + Random random = new Random(); + int arraySize = random.nextInt(MAX_WRITE_SIZE) + 1; + + // cheaply create the buffer by wrapping an appropriately sized section of the pre-built array + ChannelBuffer buffer = + ChannelBuffers.wrappedBuffer(SEND_STREAM, nextWriteByte.get(), + arraySize); + nextWriteByte.set((nextWriteByte.get() + arraySize) % 127); + + return buffer; + } + + public static void main(String[] args) throws Exception { + HttpTunnelSoakTester soakTester = new HttpTunnelSoakTester(); + try { + soakTester.run(); + } finally { + soakTester.shutdown(); + } + } + + private void shutdown() { + serverBootstrap.releaseExternalResources(); + clientBootstrap.releaseExternalResources(); + executor.shutdownNow(); + scheduledExecutor.shutdownNow(); + } + + private class DataVerifier extends SimpleChannelUpstreamHandler { + private String name; + + private int expectedNext = 0; + + private int verifiedBytes = 0; + + private CountDownLatch completionLatch = new CountDownLatch(1); + + public DataVerifier(String name) { + this.name = name; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + ChannelBuffer bytesToVerify = (ChannelBuffer) e.getMessage(); + + while (bytesToVerify.readable()) { + byte readByte = bytesToVerify.readByte(); + if (readByte != expectedNext) { + LOG.log(Level.SEVERE, + "{0}: received a byte out of sequence. Expected {1}, got {2}", + new Object[] { name, expectedNext, readByte }); + System.exit(-1); + return; + } + + expectedNext = (expectedNext + 1) % 127; + verifiedBytes ++; + } - if (verifiedBytes >= BYTES_TO_SEND) - { - completionLatch.countDown(); - return; - } - } - - @Override - public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - channels.add(ctx.getChannel()); - } - - public boolean waitForCompletion(long timeout, TimeUnit timeoutUnit) throws InterruptedException { - return completionLatch.await(timeout, timeoutUnit); - } - } - - private class SendThrottle extends SimpleChannelUpstreamHandler - { - private final DataSender sender; - - public SendThrottle(DataSender sender) - { - this.sender = sender; - } - - @Override - public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - boolean writeEnabled = ctx.getChannel().isWritable(); - sender.setWriteEnabled(writeEnabled); - - } - } - - private class DataSender implements Runnable { - - private AtomicReference channel = new AtomicReference(); - private long totalBytesSent = 0; - private long numWrites = 0; - private long runStartTime = System.currentTimeMillis(); - private boolean firstRun = true; - private AtomicBoolean writeEnabled = new AtomicBoolean(true); - private AtomicBoolean running = new AtomicBoolean(false); - private CountDownLatch finishLatch = new CountDownLatch(1); - private String name; - private AtomicInteger nextWriteByte = new AtomicInteger(0); - - public DataSender(String name) { - this.name = name; - } - - public void setChannel(Channel channel) - { - this.channel.set(channel); - } - - public void setWriteEnabled(boolean enabled) - { - writeEnabled.set(enabled); - if(enabled && !this.isRunning() && finishLatch.getCount() > 0) { - executor.execute(this); - } - } - - @Override - public void run() - { - if(!running.compareAndSet(false, true)) { - LOG.log(Level.WARNING, "{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); - } - - if(firstRun) { - firstRun = false; - runStartTime = System.currentTimeMillis(); - LOG.log(Level.INFO, "{0}: sending data", name); - } - - while (totalBytesSent < BYTES_TO_SEND) - { - if(!writeEnabled.get()) { - running.set(false); - return; + if (verifiedBytes >= BYTES_TO_SEND) { + completionLatch.countDown(); + return; } - - ChannelBuffer randomBytesForSend = createRandomSizeBuffer(nextWriteByte); - totalBytesSent += randomBytesForSend.readableBytes(); + } - channel.get().write(ChannelBuffers.wrappedBuffer(randomBytesForSend)); + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channels.add(ctx.getChannel()); + } - numWrites++; - if (numWrites % 100 == 0) - { - LOG.log(Level.INFO, "{0}: {1} writes dispatched, totalling {2} bytes", new Object[] - {name, numWrites, totalBytesSent}); + public boolean waitForCompletion(long timeout, TimeUnit timeoutUnit) + throws InterruptedException { + return completionLatch.await(timeout, timeoutUnit); + } + } + + private class SendThrottle extends SimpleChannelUpstreamHandler { + private final DataSender sender; + + public SendThrottle(DataSender sender) { + this.sender = sender; + } + + @Override + public void channelInterestChanged(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + boolean writeEnabled = ctx.getChannel().isWritable(); + sender.setWriteEnabled(writeEnabled); + + } + } + + private class DataSender implements Runnable { + + private AtomicReference channel = + new AtomicReference(); + + private long totalBytesSent = 0; + + private long numWrites = 0; + + private long runStartTime = System.currentTimeMillis(); + + private boolean firstRun = true; + + private AtomicBoolean writeEnabled = new AtomicBoolean(true); + + private AtomicBoolean running = new AtomicBoolean(false); + + private CountDownLatch finishLatch = new CountDownLatch(1); + + private String name; + + private AtomicInteger nextWriteByte = new AtomicInteger(0); + + public DataSender(String name) { + this.name = name; + } + + public void setChannel(Channel channel) { + this.channel.set(channel); + } + + public void setWriteEnabled(boolean enabled) { + writeEnabled.set(enabled); + if (enabled && !this.isRunning() && finishLatch.getCount() > 0) { + executor.execute(this); + } + } + + @Override + public void run() { + if (!running.compareAndSet(false, true)) { + LOG.log(Level.WARNING, + "{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); + } + + if (firstRun) { + firstRun = false; + runStartTime = System.currentTimeMillis(); + LOG.log(Level.INFO, "{0}: sending data", name); } - } - - LOG.log(Level.INFO, "{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 } ); - - finishLatch.countDown(); - running.set(false); - } - - public boolean isRunning() { - return running.get(); - } - - public boolean waitForFinish(long timeout, TimeUnit timeoutUnit) throws InterruptedException { - return finishLatch.await(timeout, timeoutUnit); - } - - } + + while (totalBytesSent < BYTES_TO_SEND) { + if (!writeEnabled.get()) { + running.set(false); + return; + } + + ChannelBuffer randomBytesForSend = + createRandomSizeBuffer(nextWriteByte); + totalBytesSent += randomBytesForSend.readableBytes(); + + channel.get().write( + ChannelBuffers.wrappedBuffer(randomBytesForSend)); + + numWrites ++; + if (numWrites % 100 == 0) { + LOG.log(Level.INFO, + "{0}: {1} writes dispatched, totalling {2} bytes", + new Object[] { name, numWrites, totalBytesSent }); + } + } + + LOG.log(Level.INFO, "{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 }); + + finishLatch.countDown(); + running.set(false); + } + + public boolean isRunning() { + return running.get(); + } + + public boolean waitForFinish(long timeout, TimeUnit timeoutUnit) + throws InterruptedException { + return finishLatch.await(timeout, timeoutUnit); + } + + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java index e1a44e87d4d..c000fe425cf 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/HttpTunnelTest.java @@ -53,176 +53,183 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class HttpTunnelTest -{ +public class HttpTunnelTest { - private HttpTunnelClientChannelFactory clientFactory; + private HttpTunnelClientChannelFactory clientFactory; - private HttpTunnelServerChannelFactory serverFactory; + private HttpTunnelServerChannelFactory serverFactory; - private ClientBootstrap clientBootstrap; + private ClientBootstrap clientBootstrap; - private ServerBootstrap serverBootstrap; + private ServerBootstrap serverBootstrap; - ChannelGroup activeConnections; + ChannelGroup activeConnections; - ChannelHandler clientCaptureHandler; + ChannelHandler clientCaptureHandler; - ServerEndHandler connectionCaptureHandler; + ServerEndHandler connectionCaptureHandler; - Channel serverEnd; + Channel serverEnd; - CountDownLatch serverEndLatch; + CountDownLatch serverEndLatch; - ChannelBuffer receivedBytes; + ChannelBuffer receivedBytes; - CountDownLatch messageReceivedLatch; + CountDownLatch messageReceivedLatch; - ChannelBuffer clientReceivedBytes; + ChannelBuffer clientReceivedBytes; - CountDownLatch clientMessageReceivedLatch; + CountDownLatch clientMessageReceivedLatch; - private Channel serverChannel; + private Channel serverChannel; - @Before - public void setUp() throws UnknownHostException - { - activeConnections = new DefaultChannelGroup(); - clientFactory = new HttpTunnelClientChannelFactory(new NioClientSocketChannelFactory( - Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); - serverFactory = new HttpTunnelServerChannelFactory(new NioServerSocketChannelFactory( - Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); + @Before + public void setUp() throws UnknownHostException { + activeConnections = new DefaultChannelGroup(); + clientFactory = + new HttpTunnelClientChannelFactory( + new NioClientSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); + serverFactory = + new HttpTunnelServerChannelFactory( + new NioServerSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); - clientBootstrap = new ClientBootstrap(clientFactory); + clientBootstrap = new ClientBootstrap(clientFactory); - clientCaptureHandler = new ClientEndHandler(); - clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() - { + clientCaptureHandler = new ClientEndHandler(); + clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - public ChannelPipeline getPipeline() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("clientCapture", clientCaptureHandler); - return pipeline; - } - }); + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("clientCapture", clientCaptureHandler); + return pipeline; + } + }); - clientReceivedBytes = ChannelBuffers.dynamicBuffer(); - clientMessageReceivedLatch = new CountDownLatch(1); + clientReceivedBytes = ChannelBuffers.dynamicBuffer(); + clientMessageReceivedLatch = new CountDownLatch(1); - serverBootstrap = new ServerBootstrap(serverFactory); + serverBootstrap = new ServerBootstrap(serverFactory); - connectionCaptureHandler = new ServerEndHandler(); - serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() - { + connectionCaptureHandler = new ServerEndHandler(); + serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - public ChannelPipeline getPipeline() throws Exception - { - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("capture", connectionCaptureHandler); - return pipeline; - } - }); + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("capture", connectionCaptureHandler); + return pipeline; + } + }); - serverEndLatch = new CountDownLatch(1); - receivedBytes = ChannelBuffers.dynamicBuffer(); - messageReceivedLatch = new CountDownLatch(1); + serverEndLatch = new CountDownLatch(1); + receivedBytes = ChannelBuffers.dynamicBuffer(); + messageReceivedLatch = new CountDownLatch(1); - serverChannel = serverBootstrap.bind(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); - activeConnections.add(serverChannel); - } - - @After - public void tearDown() throws Exception - { - activeConnections.disconnect().await(1000L); - clientBootstrap.releaseExternalResources(); - serverBootstrap.releaseExternalResources(); - } - - @Test(timeout = 2000) - public void testConnectClientToServer() throws Exception - { - ChannelFuture connectFuture = clientBootstrap.connect(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); - assertTrue(connectFuture.await(1000L)); - assertTrue(connectFuture.isSuccess()); - assertNotNull(connectFuture.getChannel()); - - Channel clientChannel = connectFuture.getChannel(); - activeConnections.add(clientChannel); - assertEquals(serverChannel.getLocalAddress(), clientChannel.getRemoteAddress()); - - assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); - assertNotNull(serverEnd); - assertEquals(clientChannel.getLocalAddress(), serverEnd.getRemoteAddress()); - } - - @Test - public void testSendDataFromClientToServer() throws Exception - { - ChannelFuture connectFuture = clientBootstrap.connect(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); - assertTrue(connectFuture.await(1000L)); - - Channel clientEnd = connectFuture.getChannel(); - activeConnections.add(clientEnd); - - assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); - - ChannelFuture writeFuture = Channels.write(clientEnd, NettyTestUtils.createData(100L)); - assertTrue(writeFuture.await(1000L)); - assertTrue(writeFuture.isSuccess()); - - assertTrue(messageReceivedLatch.await(1000L, TimeUnit.MILLISECONDS)); - assertEquals(100L, receivedBytes.readLong()); - } - - @Test - public void testSendDataFromServerToClient() throws Exception - { - ChannelFuture connectFuture = clientBootstrap.connect(new InetSocketAddress(InetAddress.getLocalHost(), 12345)); - assertTrue(connectFuture.await(1000L)); - - Channel clientEnd = connectFuture.getChannel(); - activeConnections.add(clientEnd); - - assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); - - ChannelFuture writeFuture = Channels.write(serverEnd, NettyTestUtils.createData(4321L)); - assertTrue(writeFuture.await(1000L)); - assertTrue(writeFuture.isSuccess()); - - assertTrue(clientMessageReceivedLatch.await(1000, TimeUnit.MILLISECONDS)); - assertEquals(4321L, clientReceivedBytes.readLong()); - } - - class ServerEndHandler extends SimpleChannelUpstreamHandler - { - - @Override - public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception - { - serverEnd = e.getChannel(); - activeConnections.add(serverEnd); - serverEndLatch.countDown(); - super.channelConnected(ctx, e); - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - receivedBytes.writeBytes((ChannelBuffer) e.getMessage()); - messageReceivedLatch.countDown(); - } - } - - class ClientEndHandler extends SimpleChannelUpstreamHandler - { - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception - { - clientReceivedBytes.writeBytes((ChannelBuffer) e.getMessage()); - clientMessageReceivedLatch.countDown(); - } - } + serverChannel = + serverBootstrap.bind(new InetSocketAddress(InetAddress + .getLocalHost(), 12345)); + activeConnections.add(serverChannel); + } + + @After + public void tearDown() throws Exception { + activeConnections.disconnect().await(1000L); + clientBootstrap.releaseExternalResources(); + serverBootstrap.releaseExternalResources(); + } + + @Test(timeout = 2000) + public void testConnectClientToServer() throws Exception { + ChannelFuture connectFuture = + clientBootstrap.connect(new InetSocketAddress(InetAddress + .getLocalHost(), 12345)); + assertTrue(connectFuture.await(1000L)); + assertTrue(connectFuture.isSuccess()); + assertNotNull(connectFuture.getChannel()); + + Channel clientChannel = connectFuture.getChannel(); + activeConnections.add(clientChannel); + assertEquals(serverChannel.getLocalAddress(), + clientChannel.getRemoteAddress()); + + assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); + assertNotNull(serverEnd); + assertEquals(clientChannel.getLocalAddress(), + serverEnd.getRemoteAddress()); + } + + @Test + public void testSendDataFromClientToServer() throws Exception { + ChannelFuture connectFuture = + clientBootstrap.connect(new InetSocketAddress(InetAddress + .getLocalHost(), 12345)); + assertTrue(connectFuture.await(1000L)); + + Channel clientEnd = connectFuture.getChannel(); + activeConnections.add(clientEnd); + + assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); + + ChannelFuture writeFuture = + Channels.write(clientEnd, NettyTestUtils.createData(100L)); + assertTrue(writeFuture.await(1000L)); + assertTrue(writeFuture.isSuccess()); + + assertTrue(messageReceivedLatch.await(1000L, TimeUnit.MILLISECONDS)); + assertEquals(100L, receivedBytes.readLong()); + } + + @Test + public void testSendDataFromServerToClient() throws Exception { + ChannelFuture connectFuture = + clientBootstrap.connect(new InetSocketAddress(InetAddress + .getLocalHost(), 12345)); + assertTrue(connectFuture.await(1000L)); + + Channel clientEnd = connectFuture.getChannel(); + activeConnections.add(clientEnd); + + assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS)); + + ChannelFuture writeFuture = + Channels.write(serverEnd, NettyTestUtils.createData(4321L)); + assertTrue(writeFuture.await(1000L)); + assertTrue(writeFuture.isSuccess()); + + assertTrue(clientMessageReceivedLatch + .await(1000, TimeUnit.MILLISECONDS)); + assertEquals(4321L, clientReceivedBytes.readLong()); + } + + class ServerEndHandler extends SimpleChannelUpstreamHandler { + + @Override + public void channelConnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + serverEnd = e.getChannel(); + activeConnections.add(serverEnd); + serverEndLatch.countDown(); + super.channelConnected(ctx, e); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + receivedBytes.writeBytes((ChannelBuffer) e.getMessage()); + messageReceivedLatch.countDown(); + } + } + + class ClientEndHandler extends SimpleChannelUpstreamHandler { + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + clientReceivedBytes.writeBytes((ChannelBuffer) e.getMessage()); + clientMessageReceivedLatch.countDown(); + } + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java b/src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java index a63c7bcd59c..73645f089d8 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/MockChannelStateListener.java @@ -27,40 +27,35 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class MockChannelStateListener implements HttpTunnelClientWorkerOwner -{ +public class MockChannelStateListener implements HttpTunnelClientWorkerOwner { - public boolean fullyEstablished = false; + public boolean fullyEstablished = false; - public List messages = new ArrayList(); + public List messages = new ArrayList(); - public String tunnelId = null; + public String tunnelId = null; - public String serverHostName = null; + public String serverHostName = null; - public void fullyEstablished() - { - fullyEstablished = true; - } + public void fullyEstablished() { + fullyEstablished = true; + } - public void onConnectRequest(ChannelFuture connectFuture, InetSocketAddress remoteAddress) - { - // not relevant for test - } + public void onConnectRequest(ChannelFuture connectFuture, + InetSocketAddress remoteAddress) { + // not relevant for test + } - public void onMessageReceived(ChannelBuffer content) - { - messages.add(content); - } + public void onMessageReceived(ChannelBuffer content) { + messages.add(content); + } - public void onTunnelOpened(String tunnelId) - { - this.tunnelId = tunnelId; - } + public void onTunnelOpened(String tunnelId) { + this.tunnelId = tunnelId; + } - public String getServerHostName() - { - return serverHostName; - } + public String getServerHostName() { + return serverHostName; + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java index e423bf0d2b5..d896cacdfc2 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtils.java @@ -43,158 +43,143 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class NettyTestUtils -{ - - public static ByteBuffer convertReadable(ChannelBuffer b) - { - int startIndex = b.readerIndex(); - ByteBuffer converted = ByteBuffer.allocate(b.readableBytes()); - b.readBytes(converted); - b.readerIndex(startIndex); - converted.flip(); - return converted; - } - - public static void assertEquals(ChannelBuffer expected, ChannelBuffer actual) - { - if (expected.readableBytes() != actual.readableBytes()) - { - Assert.failNotEquals("channel buffers have differing readable sizes", expected.readableBytes(), - actual.readableBytes()); - } - - int startPositionExpected = expected.readerIndex(); - int startPositionActual = actual.readerIndex(); - int position = 0; - while (expected.readable()) - { - byte expectedByte = expected.readByte(); - byte actualByte = actual.readByte(); - if (expectedByte != actualByte) - { - Assert.failNotEquals("channel buffers differ at position " + position, expectedByte, actualByte); - } - - position++; - } - - expected.readerIndex(startPositionExpected); - actual.readerIndex(startPositionActual); - } - - public static boolean checkEquals(ChannelBuffer expected, ChannelBuffer actual) - { - if (expected.readableBytes() != actual.readableBytes()) - { - return false; - } - - int position = 0; - while (expected.readable()) - { - byte expectedByte = expected.readByte(); - byte actualByte = actual.readByte(); - if (expectedByte != actualByte) - { +public class NettyTestUtils { + + public static ByteBuffer convertReadable(ChannelBuffer b) { + int startIndex = b.readerIndex(); + ByteBuffer converted = ByteBuffer.allocate(b.readableBytes()); + b.readBytes(converted); + b.readerIndex(startIndex); + converted.flip(); + return converted; + } + + public static void assertEquals(ChannelBuffer expected, ChannelBuffer actual) { + if (expected.readableBytes() != actual.readableBytes()) { + Assert.failNotEquals( + "channel buffers have differing readable sizes", + expected.readableBytes(), actual.readableBytes()); + } + + int startPositionExpected = expected.readerIndex(); + int startPositionActual = actual.readerIndex(); + int position = 0; + while (expected.readable()) { + byte expectedByte = expected.readByte(); + byte actualByte = actual.readByte(); + if (expectedByte != actualByte) { + Assert.failNotEquals("channel buffers differ at position " + + position, expectedByte, actualByte); + } + + position ++; + } + + expected.readerIndex(startPositionExpected); + actual.readerIndex(startPositionActual); + } + + public static boolean checkEquals(ChannelBuffer expected, + ChannelBuffer actual) { + if (expected.readableBytes() != actual.readableBytes()) { return false; - } - - position++; - } - - return true; - } - - public static List splitIntoChunks(int chunkSize, ChannelBuffer... buffers) - { - LinkedList chunks = new LinkedList(); - - ArrayList sourceBuffers = new ArrayList(); - Collections.addAll(sourceBuffers, buffers); - Iterator sourceIter = sourceBuffers.iterator(); - ChannelBuffer chunk = ChannelBuffers.buffer(chunkSize); - while (sourceIter.hasNext()) - { - ChannelBuffer source = sourceIter.next(); - - int index = source.readerIndex(); - while (source.writerIndex() > index) - { - int fragmentSize = Math.min(source.writerIndex() - index, chunk.writableBytes()); - chunk.writeBytes(source, index, fragmentSize); - if (!chunk.writable()) - { - chunks.add(chunk); - chunk = ChannelBuffers.buffer(chunkSize); + } + + int position = 0; + while (expected.readable()) { + byte expectedByte = expected.readByte(); + byte actualByte = actual.readByte(); + if (expectedByte != actualByte) { + return false; + } + + position ++; + } + + return true; + } + + public static List splitIntoChunks(int chunkSize, + ChannelBuffer... buffers) { + LinkedList chunks = new LinkedList(); + + ArrayList sourceBuffers = new ArrayList(); + Collections.addAll(sourceBuffers, buffers); + Iterator sourceIter = sourceBuffers.iterator(); + ChannelBuffer chunk = ChannelBuffers.buffer(chunkSize); + while (sourceIter.hasNext()) { + ChannelBuffer source = sourceIter.next(); + + int index = source.readerIndex(); + while (source.writerIndex() > index) { + int fragmentSize = + Math.min(source.writerIndex() - index, + chunk.writableBytes()); + chunk.writeBytes(source, index, fragmentSize); + if (!chunk.writable()) { + chunks.add(chunk); + chunk = ChannelBuffers.buffer(chunkSize); + } + index += fragmentSize; } - index += fragmentSize; - } - } - - if (chunk.readable()) - { - chunks.add(chunk); - } - - return chunks; - } - - public static ChannelBuffer createData(long containedNumber) - { - ChannelBuffer data = ChannelBuffers.dynamicBuffer(); - data.writeLong(containedNumber); - return data; - } - - public static void checkIsUpstreamMessageEventContainingData(ChannelEvent event, ChannelBuffer expectedData) - { - ChannelBuffer data = checkIsUpstreamMessageEvent(event, ChannelBuffer.class); - assertEquals(expectedData, data); - } - - public static T checkIsUpstreamMessageEvent(ChannelEvent event, Class expectedMessageType) - { - assertTrue(event instanceof UpstreamMessageEvent); - UpstreamMessageEvent messageEvent = (UpstreamMessageEvent) event; - assertTrue(expectedMessageType.isInstance(messageEvent.getMessage())); - return expectedMessageType.cast(messageEvent.getMessage()); - } - - public static T checkIsDownstreamMessageEvent(ChannelEvent event, Class expectedMessageType) - { - assertTrue(event instanceof DownstreamMessageEvent); - DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; - assertTrue(expectedMessageType.isInstance(messageEvent.getMessage())); - return expectedMessageType.cast(messageEvent.getMessage()); - } - - public static InetSocketAddress createAddress(byte[] addr, int port) - { - try - { - return new InetSocketAddress(InetAddress.getByAddress(addr), port); - } - catch (UnknownHostException e) - { - throw new RuntimeException("Bad address in test"); - } - } - - public static Throwable checkIsExceptionEvent(ChannelEvent ev) - { - assertTrue(ev instanceof ExceptionEvent); - ExceptionEvent exceptionEv = (ExceptionEvent) ev; - return exceptionEv.getCause(); - } - - public static ChannelStateEvent checkIsStateEvent(ChannelEvent event, ChannelState expectedState, - Object expectedValue) - { - assertTrue(event instanceof ChannelStateEvent); - ChannelStateEvent stateEvent = (ChannelStateEvent) event; - Assert.assertEquals(expectedState, stateEvent.getState()); - Assert.assertEquals(expectedValue, stateEvent.getValue()); - return stateEvent; - } + } + + if (chunk.readable()) { + chunks.add(chunk); + } + + return chunks; + } + + public static ChannelBuffer createData(long containedNumber) { + ChannelBuffer data = ChannelBuffers.dynamicBuffer(); + data.writeLong(containedNumber); + return data; + } + + public static void checkIsUpstreamMessageEventContainingData( + ChannelEvent event, ChannelBuffer expectedData) { + ChannelBuffer data = + checkIsUpstreamMessageEvent(event, ChannelBuffer.class); + assertEquals(expectedData, data); + } + + public static T checkIsUpstreamMessageEvent(ChannelEvent event, + Class expectedMessageType) { + assertTrue(event instanceof UpstreamMessageEvent); + UpstreamMessageEvent messageEvent = (UpstreamMessageEvent) event; + assertTrue(expectedMessageType.isInstance(messageEvent.getMessage())); + return expectedMessageType.cast(messageEvent.getMessage()); + } + + public static T checkIsDownstreamMessageEvent(ChannelEvent event, + Class expectedMessageType) { + assertTrue(event instanceof DownstreamMessageEvent); + DownstreamMessageEvent messageEvent = (DownstreamMessageEvent) event; + assertTrue(expectedMessageType.isInstance(messageEvent.getMessage())); + return expectedMessageType.cast(messageEvent.getMessage()); + } + + public static InetSocketAddress createAddress(byte[] addr, int port) { + try { + return new InetSocketAddress(InetAddress.getByAddress(addr), port); + } catch (UnknownHostException e) { + throw new RuntimeException("Bad address in test"); + } + } + + public static Throwable checkIsExceptionEvent(ChannelEvent ev) { + assertTrue(ev instanceof ExceptionEvent); + ExceptionEvent exceptionEv = (ExceptionEvent) ev; + return exceptionEv.getCause(); + } + + public static ChannelStateEvent checkIsStateEvent(ChannelEvent event, + ChannelState expectedState, Object expectedValue) { + assertTrue(event instanceof ChannelStateEvent); + ChannelStateEvent stateEvent = (ChannelStateEvent) event; + Assert.assertEquals(expectedState, stateEvent.getState()); + Assert.assertEquals(expectedValue, stateEvent.getValue()); + return stateEvent; + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java index 2936c3da1fc..2f71b647d86 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/NettyTestUtilsTest.java @@ -29,116 +29,96 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class NettyTestUtilsTest -{ - - @Test - public void testSplitIntoChunks() - { - ChannelBuffer a = createFullBuffer(20, (byte) 0); - ChannelBuffer b = createFullBuffer(20, (byte) 1); - ChannelBuffer c = createFullBuffer(20, (byte) 2); - - List chunks = NettyTestUtils.splitIntoChunks(10, a, b, c); - assertEquals(6, chunks.size()); - for (ChannelBuffer chunk : chunks) - { - assertEquals(10, chunk.readableBytes()); - } - - // reader index should not be modified by splitIntoChunks() - assertEquals(0, a.readerIndex()); - assertEquals(0, b.readerIndex()); - assertEquals(0, c.readerIndex()); - } - - @Test - public void testSplitIntoChunks_chunksCrossBoundaries() - { - ChannelBuffer a = createFullBuffer(5, (byte) 0); - ChannelBuffer b = createFullBuffer(5, (byte) 1); - ChannelBuffer c = createFullBuffer(5, (byte) 2); - - List chunks = NettyTestUtils.splitIntoChunks(4, a, b, c); - assertEquals(4, chunks.size()); - checkBufferContains(chunks.get(0), new byte[] - {0, 0, 0, 0}); - checkBufferContains(chunks.get(1), new byte[] - {0, 1, 1, 1}); - checkBufferContains(chunks.get(2), new byte[] - {1, 1, 2, 2}); - checkBufferContains(chunks.get(3), new byte[] - {2, 2, 2}); - } - - @Test - public void testSplitIntoChunks_smallestChunksPossible() - { - ChannelBuffer a = createFullBuffer(5, (byte) 0); - ChannelBuffer b = createFullBuffer(5, (byte) 1); - ChannelBuffer c = createFullBuffer(5, (byte) 2); - - List chunks = NettyTestUtils.splitIntoChunks(1, a, b, c); - assertEquals(15, chunks.size()); - checkBufferContains(chunks.get(0), new byte[] - {0}); - checkBufferContains(chunks.get(5), new byte[] - {1}); - checkBufferContains(chunks.get(10), new byte[] - {2}); - } - - @Test - public void testSplitIntoChunks_sourceBuffersArePartiallyRead() - { - ChannelBuffer a = createFullBuffer(5, (byte) 0); - a.readerIndex(1); - ChannelBuffer b = createFullBuffer(5, (byte) 1); - b.readerIndex(2); - ChannelBuffer c = createFullBuffer(5, (byte) 2); - - // will be ignored, as fully read - ChannelBuffer d = createFullBuffer(5, (byte) 3); - d.readerIndex(5); - ChannelBuffer e = createFullBuffer(5, (byte) 4); - e.readerIndex(4); - - List chunks = NettyTestUtils.splitIntoChunks(3, a, b, c, d, e); - checkBufferContains(chunks.get(0), new byte[] - {0, 0, 0}); - checkBufferContains(chunks.get(1), new byte[] - {0, 1, 1}); - checkBufferContains(chunks.get(2), new byte[] - {1, 2, 2}); - checkBufferContains(chunks.get(3), new byte[] - {2, 2, 2}); - checkBufferContains(chunks.get(4), new byte[] - {4}); - } - - private void checkBufferContains(ChannelBuffer channelBuffer, byte[] bs) - { - if (channelBuffer.readableBytes() != bs.length) - { - fail("buffer does not have enough bytes"); - } - - for (int i = 0; i < bs.length; i++) - { - assertEquals("byte at position " + i + " does not match", bs[i], channelBuffer.getByte(i)); - } - } - - private ChannelBuffer createFullBuffer(int size, byte value) - { - ChannelBuffer buffer = ChannelBuffers.buffer(size); - byte[] contents = new byte[size]; - for (int i = 0; i < contents.length; i++) - { - contents[i] = value; - } - buffer.writeBytes(contents); - return buffer; - } +public class NettyTestUtilsTest { + + @Test + public void testSplitIntoChunks() { + ChannelBuffer a = createFullBuffer(20, (byte) 0); + ChannelBuffer b = createFullBuffer(20, (byte) 1); + ChannelBuffer c = createFullBuffer(20, (byte) 2); + + List chunks = + NettyTestUtils.splitIntoChunks(10, a, b, c); + assertEquals(6, chunks.size()); + for (ChannelBuffer chunk: chunks) { + assertEquals(10, chunk.readableBytes()); + } + + // reader index should not be modified by splitIntoChunks() + assertEquals(0, a.readerIndex()); + assertEquals(0, b.readerIndex()); + assertEquals(0, c.readerIndex()); + } + + @Test + public void testSplitIntoChunks_chunksCrossBoundaries() { + ChannelBuffer a = createFullBuffer(5, (byte) 0); + ChannelBuffer b = createFullBuffer(5, (byte) 1); + ChannelBuffer c = createFullBuffer(5, (byte) 2); + + List chunks = NettyTestUtils.splitIntoChunks(4, a, b, c); + assertEquals(4, chunks.size()); + checkBufferContains(chunks.get(0), new byte[] { 0, 0, 0, 0 }); + checkBufferContains(chunks.get(1), new byte[] { 0, 1, 1, 1 }); + checkBufferContains(chunks.get(2), new byte[] { 1, 1, 2, 2 }); + checkBufferContains(chunks.get(3), new byte[] { 2, 2, 2 }); + } + + @Test + public void testSplitIntoChunks_smallestChunksPossible() { + ChannelBuffer a = createFullBuffer(5, (byte) 0); + ChannelBuffer b = createFullBuffer(5, (byte) 1); + ChannelBuffer c = createFullBuffer(5, (byte) 2); + + List chunks = NettyTestUtils.splitIntoChunks(1, a, b, c); + assertEquals(15, chunks.size()); + checkBufferContains(chunks.get(0), new byte[] { 0 }); + checkBufferContains(chunks.get(5), new byte[] { 1 }); + checkBufferContains(chunks.get(10), new byte[] { 2 }); + } + + @Test + public void testSplitIntoChunks_sourceBuffersArePartiallyRead() { + ChannelBuffer a = createFullBuffer(5, (byte) 0); + a.readerIndex(1); + ChannelBuffer b = createFullBuffer(5, (byte) 1); + b.readerIndex(2); + ChannelBuffer c = createFullBuffer(5, (byte) 2); + + // will be ignored, as fully read + ChannelBuffer d = createFullBuffer(5, (byte) 3); + d.readerIndex(5); + ChannelBuffer e = createFullBuffer(5, (byte) 4); + e.readerIndex(4); + + List chunks = + NettyTestUtils.splitIntoChunks(3, a, b, c, d, e); + checkBufferContains(chunks.get(0), new byte[] { 0, 0, 0 }); + checkBufferContains(chunks.get(1), new byte[] { 0, 1, 1 }); + checkBufferContains(chunks.get(2), new byte[] { 1, 2, 2 }); + checkBufferContains(chunks.get(3), new byte[] { 2, 2, 2 }); + checkBufferContains(chunks.get(4), new byte[] { 4 }); + } + + private void checkBufferContains(ChannelBuffer channelBuffer, byte[] bs) { + if (channelBuffer.readableBytes() != bs.length) { + fail("buffer does not have enough bytes"); + } + + for (int i = 0; i < bs.length; i ++) { + assertEquals("byte at position " + i + " does not match", bs[i], + channelBuffer.getByte(i)); + } + } + + private ChannelBuffer createFullBuffer(int size, byte value) { + ChannelBuffer buffer = ChannelBuffers.buffer(size); + byte[] contents = new byte[size]; + for (int i = 0; i < contents.length; i ++) { + contents[i] = value; + } + buffer.writeBytes(contents); + return buffer; + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java b/src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java index 53c8de0158a..1c080255ec3 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/NullChannelHandler.java @@ -25,17 +25,17 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class NullChannelHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler -{ +public class NullChannelHandler implements ChannelUpstreamHandler, + ChannelDownstreamHandler { - public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception - { - ctx.sendUpstream(e); - } + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + ctx.sendUpstream(e); + } - public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception - { - ctx.sendDownstream(e); - } + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + ctx.sendDownstream(e); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java b/src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java index 922a31dbf1e..fcd4dca6974 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/SaturationManagerTest.java @@ -6,29 +6,27 @@ import org.junit.Before; import org.junit.Test; -public class SaturationManagerTest -{ +public class SaturationManagerTest { - private SaturationManager manager; - - @Before - public void setUp() { - manager = new SaturationManager(100L, 200L); - } - - @Test - public void testQueueSizeChanged() - { - assertEquals(NO_CHANGE, manager.queueSizeChanged(100L)); - assertEquals(NO_CHANGE, manager.queueSizeChanged(99L)); - assertEquals(NO_CHANGE, manager.queueSizeChanged(1L)); - assertEquals(SATURATED, manager.queueSizeChanged(1L)); - assertEquals(NO_CHANGE, manager.queueSizeChanged(10L)); - - assertEquals(NO_CHANGE, manager.queueSizeChanged(-10L)); - assertEquals(NO_CHANGE, manager.queueSizeChanged(-1L)); - assertEquals(NO_CHANGE, manager.queueSizeChanged(-1L)); - assertEquals(DESATURATED, manager.queueSizeChanged(-99L)); - assertEquals(NO_CHANGE, manager.queueSizeChanged(-100L)); - } + private SaturationManager manager; + + @Before + public void setUp() { + manager = new SaturationManager(100L, 200L); + } + + @Test + public void testQueueSizeChanged() { + assertEquals(NO_CHANGE, manager.queueSizeChanged(100L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(99L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(1L)); + assertEquals(SATURATED, manager.queueSizeChanged(1L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(10L)); + + assertEquals(NO_CHANGE, manager.queueSizeChanged(-10L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(-1L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(-1L)); + assertEquals(DESATURATED, manager.queueSizeChanged(-99L)); + assertEquals(NO_CHANGE, manager.queueSizeChanged(-100L)); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java b/src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java index f7b0b139c3a..142cb440ac9 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/ServerMessageSwitchTest.java @@ -38,144 +38,147 @@ * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ @RunWith(JMock.class) -public class ServerMessageSwitchTest -{ - - public static final InetSocketAddress REMOTE_ADDRESS = InetSocketAddress.createUnresolved("test.client.com", 52354); - - private final JUnit4Mockery mockContext = new JUnit4Mockery(); - - private ServerMessageSwitch messageSwitch; - - HttpTunnelAcceptedChannelFactory newChannelFactory; - - private FakeChannelSink responseCatcher; - - private FakeSocketChannel htunChannel; - - private FakeSocketChannel requesterChannel; - - private HttpTunnelAcceptedChannelReceiver htunAcceptedChannel; - - @Before - public void setUp() throws Exception - { - newChannelFactory = mockContext.mock(HttpTunnelAcceptedChannelFactory.class); - messageSwitch = new ServerMessageSwitch(newChannelFactory); - - htunAcceptedChannel = mockContext.mock(HttpTunnelAcceptedChannelReceiver.class); - createRequesterChannel(); - - mockContext.checking(new Expectations() - { - { - one(newChannelFactory).newChannel(with(any(String.class)), with(equal(REMOTE_ADDRESS))); - will(returnValue(htunAcceptedChannel)); - ignoring(newChannelFactory).generateTunnelId(); - will(returnValue("TEST_TUNNEL")); - } - }); - } - - private FakeSocketChannel createRequesterChannel() - { - ChannelPipeline requesterChannelPipeline = Channels.pipeline(); - responseCatcher = new FakeChannelSink(); - requesterChannel = new FakeSocketChannel(null, null, requesterChannelPipeline, responseCatcher); - responseCatcher.events.clear(); - - return requesterChannel; - } - - @Test - public void testRouteInboundData() - { - final ChannelBuffer inboundData = ChannelBuffers.dynamicBuffer(); - inboundData.writeLong(1234L); - - mockContext.checking(new Expectations() - { - { - one(htunAcceptedChannel).dataReceived(with(same(inboundData))); - } - }); - - String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); - messageSwitch.routeInboundData(tunnelId, inboundData); - mockContext.assertIsSatisfied(); - } - - @Test - public void testRouteOutboundData_onPoll() - { - ChannelBuffer outboundData = ChannelBuffers.dynamicBuffer(); - outboundData.writeLong(1234L); - - String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); - messageSwitch.routeOutboundData(tunnelId, outboundData, Channels.future(htunChannel)); - messageSwitch.pollOutboundData(tunnelId, requesterChannel); - - assertEquals(1, responseCatcher.events.size()); - HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseCatcher.events.poll(), - HttpResponse.class); - NettyTestUtils.assertEquals(outboundData, response.getContent()); - } - - @Test - public void testRouteOutboundData_withDanglingRequest() - { - String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); - messageSwitch.pollOutboundData(tunnelId, requesterChannel); - assertEquals(0, responseCatcher.events.size()); - - ChannelBuffer outboundData = ChannelBuffers.dynamicBuffer(); - outboundData.writeLong(1234L); - - messageSwitch.routeOutboundData(tunnelId, outboundData, Channels.future(htunChannel)); - assertEquals(1, responseCatcher.events.size()); - HttpResponse response = NettyTestUtils.checkIsDownstreamMessageEvent(responseCatcher.events.poll(), - HttpResponse.class); - NettyTestUtils.assertEquals(outboundData, response.getContent()); - } - - @Test - public void testCloseTunnel() - { - String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); - messageSwitch.serverCloseTunnel(tunnelId); - assertEquals(TunnelStatus.CLOSED, messageSwitch.routeInboundData(tunnelId, ChannelBuffers.dynamicBuffer())); - } - - /* TODO: require tests that check the various permutations of a client sending or polling - data after the server has closed the connection */ - - /* TODO: require tests that check what happens when a client closes a connection */ - - @Test - public void testRouteInboundDataIgnoredAfterClose() - { - ChannelBuffer data = NettyTestUtils.createData(1234L); - String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); - messageSwitch.serverCloseTunnel(tunnelId); - - mockContext.checking(new Expectations() - { - { - never(htunAcceptedChannel).dataReceived(with(any(ChannelBuffer.class))); - } - }); - - messageSwitch.routeInboundData(tunnelId, data); - mockContext.assertIsSatisfied(); - } - - @Test - public void testRouteOutboundDataIgnoredAfterClose() - { - ChannelBuffer data = NettyTestUtils.createData(1234L); - String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); - messageSwitch.serverCloseTunnel(tunnelId); - messageSwitch.routeOutboundData(tunnelId, data, Channels.future(htunChannel)); - assertEquals(0, responseCatcher.events.size()); - } +public class ServerMessageSwitchTest { + + public static final InetSocketAddress REMOTE_ADDRESS = InetSocketAddress + .createUnresolved("test.client.com", 52354); + + private final JUnit4Mockery mockContext = new JUnit4Mockery(); + + private ServerMessageSwitch messageSwitch; + + HttpTunnelAcceptedChannelFactory newChannelFactory; + + private FakeChannelSink responseCatcher; + + private FakeSocketChannel htunChannel; + + private FakeSocketChannel requesterChannel; + + private HttpTunnelAcceptedChannelReceiver htunAcceptedChannel; + + @Before + public void setUp() throws Exception { + newChannelFactory = + mockContext.mock(HttpTunnelAcceptedChannelFactory.class); + messageSwitch = new ServerMessageSwitch(newChannelFactory); + + htunAcceptedChannel = + mockContext.mock(HttpTunnelAcceptedChannelReceiver.class); + createRequesterChannel(); + + mockContext.checking(new Expectations() { + { + one(newChannelFactory).newChannel(with(any(String.class)), + with(equal(REMOTE_ADDRESS))); + will(returnValue(htunAcceptedChannel)); + ignoring(newChannelFactory).generateTunnelId(); + will(returnValue("TEST_TUNNEL")); + } + }); + } + + private FakeSocketChannel createRequesterChannel() { + ChannelPipeline requesterChannelPipeline = Channels.pipeline(); + responseCatcher = new FakeChannelSink(); + requesterChannel = + new FakeSocketChannel(null, null, requesterChannelPipeline, + responseCatcher); + responseCatcher.events.clear(); + + return requesterChannel; + } + + @Test + public void testRouteInboundData() { + final ChannelBuffer inboundData = ChannelBuffers.dynamicBuffer(); + inboundData.writeLong(1234L); + + mockContext.checking(new Expectations() { + { + one(htunAcceptedChannel).dataReceived(with(same(inboundData))); + } + }); + + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.routeInboundData(tunnelId, inboundData); + mockContext.assertIsSatisfied(); + } + + @Test + public void testRouteOutboundData_onPoll() { + ChannelBuffer outboundData = ChannelBuffers.dynamicBuffer(); + outboundData.writeLong(1234L); + + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.routeOutboundData(tunnelId, outboundData, + Channels.future(htunChannel)); + messageSwitch.pollOutboundData(tunnelId, requesterChannel); + + assertEquals(1, responseCatcher.events.size()); + HttpResponse response = + NettyTestUtils.checkIsDownstreamMessageEvent( + responseCatcher.events.poll(), HttpResponse.class); + NettyTestUtils.assertEquals(outboundData, response.getContent()); + } + + @Test + public void testRouteOutboundData_withDanglingRequest() { + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.pollOutboundData(tunnelId, requesterChannel); + assertEquals(0, responseCatcher.events.size()); + + ChannelBuffer outboundData = ChannelBuffers.dynamicBuffer(); + outboundData.writeLong(1234L); + + messageSwitch.routeOutboundData(tunnelId, outboundData, + Channels.future(htunChannel)); + assertEquals(1, responseCatcher.events.size()); + HttpResponse response = + NettyTestUtils.checkIsDownstreamMessageEvent( + responseCatcher.events.poll(), HttpResponse.class); + NettyTestUtils.assertEquals(outboundData, response.getContent()); + } + + @Test + public void testCloseTunnel() { + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.serverCloseTunnel(tunnelId); + assertEquals( + TunnelStatus.CLOSED, + messageSwitch.routeInboundData(tunnelId, + ChannelBuffers.dynamicBuffer())); + } + + /* TODO: require tests that check the various permutations of a client sending or polling + data after the server has closed the connection */ + + /* TODO: require tests that check what happens when a client closes a connection */ + + @Test + public void testRouteInboundDataIgnoredAfterClose() { + ChannelBuffer data = NettyTestUtils.createData(1234L); + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.serverCloseTunnel(tunnelId); + + mockContext.checking(new Expectations() { + { + never(htunAcceptedChannel).dataReceived( + with(any(ChannelBuffer.class))); + } + }); + + messageSwitch.routeInboundData(tunnelId, data); + mockContext.assertIsSatisfied(); + } + + @Test + public void testRouteOutboundDataIgnoredAfterClose() { + ChannelBuffer data = NettyTestUtils.createData(1234L); + String tunnelId = messageSwitch.createTunnel(REMOTE_ADDRESS); + messageSwitch.serverCloseTunnel(tunnelId); + messageSwitch.routeOutboundData(tunnelId, data, + Channels.future(htunChannel)); + assertEquals(0, responseCatcher.events.size()); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java b/src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java index 4a2c7296fea..95401bf8ff7 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/UpstreamEventCatcher.java @@ -27,16 +27,15 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class UpstreamEventCatcher implements ChannelUpstreamHandler -{ +public class UpstreamEventCatcher implements ChannelUpstreamHandler { - public static final String NAME = "upstreamCatcher"; + public static final String NAME = "upstreamCatcher"; - public Queue events = new LinkedList(); + public Queue events = new LinkedList(); - public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception - { - events.add(e); - } + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + events.add(e); + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java b/src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java index 63fbd695ba6..cf03851be09 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/WriteFragmenterTest.java @@ -34,123 +34,124 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class WriteFragmenterTest -{ - - private FakeSocketChannel channel; - - private WriteFragmenter fragmenter; - - private FakeChannelSink downstreamCatcher; - - @Before - public void setUp() throws Exception - { - fragmenter = new WriteFragmenter(100); - - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast(WriteFragmenter.NAME, fragmenter); - downstreamCatcher = new FakeChannelSink(); - channel = new FakeSocketChannel(null, null, pipeline, downstreamCatcher); - } - - @Test - public void testLeavesWritesBeneathThresholdUntouched() - { - ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[99]); - Channels.write(channel, data); - - assertEquals(1, downstreamCatcher.events.size()); - ChannelBuffer sentData = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), - ChannelBuffer.class); - assertSame(data, sentData); - } - - @Test - public void testLeavesMessagesOnThresholdUntouched() - { - ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[100]); - Channels.write(channel, data); - - assertEquals(1, downstreamCatcher.events.size()); - ChannelBuffer sentData = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), - ChannelBuffer.class); - assertSame(data, sentData); - } - - @Test - public void testSplitsMessagesAboveThreshold_twoChunks() - { - ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[101]); - Channels.write(channel, data); - - assertEquals(2, downstreamCatcher.events.size()); - ChannelBuffer chunk1 = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), - ChannelBuffer.class); - ChannelBuffer chunk2 = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), - ChannelBuffer.class); - assertEquals(100, chunk1.readableBytes()); - assertEquals(1, chunk2.readableBytes()); - } - - @Test - public void testSplitsMessagesAboveThreshold_multipleChunks() - { - ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); - Channels.write(channel, data); - - assertEquals(26, downstreamCatcher.events.size()); - for (int i = 0; i < 25; i++) - { - ChannelBuffer chunk = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), - ChannelBuffer.class); - assertEquals(100, chunk.readableBytes()); - } - - ChannelBuffer endChunk = NettyTestUtils.checkIsDownstreamMessageEvent(downstreamCatcher.events.poll(), - ChannelBuffer.class); - assertEquals(40, endChunk.readableBytes()); - } - - @Test - public void testChannelFutureTriggeredOnlyWhenAllChunksWritten() - { - ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); - ChannelFuture mainWriteFuture = Channels.write(channel, data); - - assertEquals(26, downstreamCatcher.events.size()); - for (int i = 0; i < 25; i++) - { - ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setSuccess(); - assertFalse(mainWriteFuture.isDone()); - } - - ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setSuccess(); - assertTrue(mainWriteFuture.isDone()); - assertTrue(mainWriteFuture.isSuccess()); - } - - @Test - public void testChannelFutureFailsOnFirstWriteFailure() - { - ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); - ChannelFuture mainWriteFuture = Channels.write(channel, data); - - assertEquals(26, downstreamCatcher.events.size()); - for (int i = 0; i < 10; i++) - { - ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setSuccess(); - assertFalse(mainWriteFuture.isDone()); - } - - ((MessageEvent) downstreamCatcher.events.poll()).getFuture().setFailure(new Exception("Something bad happened")); - assertTrue(mainWriteFuture.isDone()); - assertFalse(mainWriteFuture.isSuccess()); - - // check all the subsequent writes got cancelled - for (int i = 0; i < 15; i++) - { - assertTrue(((MessageEvent) downstreamCatcher.events.poll()).getFuture().isCancelled()); - } - } +public class WriteFragmenterTest { + + private FakeSocketChannel channel; + + private WriteFragmenter fragmenter; + + private FakeChannelSink downstreamCatcher; + + @Before + public void setUp() throws Exception { + fragmenter = new WriteFragmenter(100); + + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast(WriteFragmenter.NAME, fragmenter); + downstreamCatcher = new FakeChannelSink(); + channel = + new FakeSocketChannel(null, null, pipeline, downstreamCatcher); + } + + @Test + public void testLeavesWritesBeneathThresholdUntouched() { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[99]); + Channels.write(channel, data); + + assertEquals(1, downstreamCatcher.events.size()); + ChannelBuffer sentData = + NettyTestUtils.checkIsDownstreamMessageEvent( + downstreamCatcher.events.poll(), ChannelBuffer.class); + assertSame(data, sentData); + } + + @Test + public void testLeavesMessagesOnThresholdUntouched() { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[100]); + Channels.write(channel, data); + + assertEquals(1, downstreamCatcher.events.size()); + ChannelBuffer sentData = + NettyTestUtils.checkIsDownstreamMessageEvent( + downstreamCatcher.events.poll(), ChannelBuffer.class); + assertSame(data, sentData); + } + + @Test + public void testSplitsMessagesAboveThreshold_twoChunks() { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[101]); + Channels.write(channel, data); + + assertEquals(2, downstreamCatcher.events.size()); + ChannelBuffer chunk1 = + NettyTestUtils.checkIsDownstreamMessageEvent( + downstreamCatcher.events.poll(), ChannelBuffer.class); + ChannelBuffer chunk2 = + NettyTestUtils.checkIsDownstreamMessageEvent( + downstreamCatcher.events.poll(), ChannelBuffer.class); + assertEquals(100, chunk1.readableBytes()); + assertEquals(1, chunk2.readableBytes()); + } + + @Test + public void testSplitsMessagesAboveThreshold_multipleChunks() { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); + Channels.write(channel, data); + + assertEquals(26, downstreamCatcher.events.size()); + for (int i = 0; i < 25; i ++) { + ChannelBuffer chunk = + NettyTestUtils.checkIsDownstreamMessageEvent( + downstreamCatcher.events.poll(), + ChannelBuffer.class); + assertEquals(100, chunk.readableBytes()); + } + + ChannelBuffer endChunk = + NettyTestUtils.checkIsDownstreamMessageEvent( + downstreamCatcher.events.poll(), ChannelBuffer.class); + assertEquals(40, endChunk.readableBytes()); + } + + @Test + public void testChannelFutureTriggeredOnlyWhenAllChunksWritten() { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); + ChannelFuture mainWriteFuture = Channels.write(channel, data); + + assertEquals(26, downstreamCatcher.events.size()); + for (int i = 0; i < 25; i ++) { + ((MessageEvent) downstreamCatcher.events.poll()).getFuture() + .setSuccess(); + assertFalse(mainWriteFuture.isDone()); + } + + ((MessageEvent) downstreamCatcher.events.poll()).getFuture() + .setSuccess(); + assertTrue(mainWriteFuture.isDone()); + assertTrue(mainWriteFuture.isSuccess()); + } + + @Test + public void testChannelFutureFailsOnFirstWriteFailure() { + ChannelBuffer data = ChannelBuffers.wrappedBuffer(new byte[2540]); + ChannelFuture mainWriteFuture = Channels.write(channel, data); + + assertEquals(26, downstreamCatcher.events.size()); + for (int i = 0; i < 10; i ++) { + ((MessageEvent) downstreamCatcher.events.poll()).getFuture() + .setSuccess(); + assertFalse(mainWriteFuture.isDone()); + } + + ((MessageEvent) downstreamCatcher.events.poll()).getFuture() + .setFailure(new Exception("Something bad happened")); + assertTrue(mainWriteFuture.isDone()); + assertFalse(mainWriteFuture.isSuccess()); + + // check all the subsequent writes got cancelled + for (int i = 0; i < 15; i ++) { + assertTrue(((MessageEvent) downstreamCatcher.events.poll()) + .getFuture().isCancelled()); + } + } } diff --git a/src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java b/src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java index dd823833ac1..d0258f54614 100644 --- a/src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java +++ b/src/test/java/org/jboss/netty/channel/socket/http/WriteSplitterTest.java @@ -30,75 +30,70 @@ * @author The Netty Project (netty-dev@lists.jboss.org) * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ -public class WriteSplitterTest -{ - - private static final int SPLIT_THRESHOLD = 1024; - - @Test - public void testSplit_bufferUnderThreshold() - { - ChannelBuffer buffer = createBufferWithContents(800); - List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); - assertNotNull(fragments); - assertEquals(1, fragments.size()); - } - - @Test - public void testSplit_bufferMatchesThreshold() - { - ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD); - List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); - assertNotNull(fragments); - assertEquals(1, fragments.size()); - } - - @Test - public void testSplit_bufferOverThreshold() - { - ChannelBuffer buffer = createBufferWithContents((int) (SPLIT_THRESHOLD * 1.5)); - List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); - assertNotNull(fragments); - assertEquals(2, fragments.size()); - - ChannelBuffer fragment1 = fragments.get(0); - checkMatches(buffer, fragment1); - ChannelBuffer fragment2 = fragments.get(1); - checkMatches(buffer, fragment2); - } - - @Test - public void testSplit_largeNumberOfFragments() - { - ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD * 250); - List fragments = WriteSplitter.split(buffer, SPLIT_THRESHOLD); - assertNotNull(fragments); - assertEquals(250, fragments.size()); - - for (ChannelBuffer fragment : fragments) - { - checkMatches(buffer, fragment); - } - } - - private void checkMatches(ChannelBuffer mainBuffer, ChannelBuffer fragment) - { - assertTrue(mainBuffer.readableBytes() >= fragment.readableBytes()); - while (fragment.readable()) - { - assertEquals(mainBuffer.readByte(), fragment.readByte()); - } - } - - private ChannelBuffer createBufferWithContents(int size) - { - byte[] contents = new byte[size]; - for (int i = 0; i < contents.length; i++) - { - contents[i] = (byte) (i % 10); - } - - return ChannelBuffers.copiedBuffer(contents); - } +public class WriteSplitterTest { + + private static final int SPLIT_THRESHOLD = 1024; + + @Test + public void testSplit_bufferUnderThreshold() { + ChannelBuffer buffer = createBufferWithContents(800); + List fragments = + WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(1, fragments.size()); + } + + @Test + public void testSplit_bufferMatchesThreshold() { + ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD); + List fragments = + WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(1, fragments.size()); + } + + @Test + public void testSplit_bufferOverThreshold() { + ChannelBuffer buffer = + createBufferWithContents((int) (SPLIT_THRESHOLD * 1.5)); + List fragments = + WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(2, fragments.size()); + + ChannelBuffer fragment1 = fragments.get(0); + checkMatches(buffer, fragment1); + ChannelBuffer fragment2 = fragments.get(1); + checkMatches(buffer, fragment2); + } + + @Test + public void testSplit_largeNumberOfFragments() { + ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD * 250); + List fragments = + WriteSplitter.split(buffer, SPLIT_THRESHOLD); + assertNotNull(fragments); + assertEquals(250, fragments.size()); + + for (ChannelBuffer fragment: fragments) { + checkMatches(buffer, fragment); + } + } + + private void checkMatches(ChannelBuffer mainBuffer, ChannelBuffer fragment) { + assertTrue(mainBuffer.readableBytes() >= fragment.readableBytes()); + while (fragment.readable()) { + assertEquals(mainBuffer.readByte(), fragment.readByte()); + } + } + + private ChannelBuffer createBufferWithContents(int size) { + byte[] contents = new byte[size]; + for (int i = 0; i < contents.length; i ++) { + contents[i] = (byte) (i % 10); + } + + return ChannelBuffers.copiedBuffer(contents); + } } From 887ca0c688d41eeb9a50241dea578f71bb587b62 Mon Sep 17 00:00:00 2001 From: iainmcgin Date: Fri, 8 Apr 2011 14:31:51 +0100 Subject: [PATCH 6/6] fixing formatting for NioSocketChannelConfig, which I had incorrectly merged earlier. --- .../socket/nio/NioSocketChannelConfig.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java b/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java index 7a43427af09..35ffccc8ed4 100644 --- a/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java +++ b/src/main/java/org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.java @@ -59,36 +59,36 @@ */ public interface NioSocketChannelConfig extends SocketChannelConfig { - /** - * Returns the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code false}. - */ + /** + * Returns the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code false}. + */ int getWriteBufferHighWaterMark(); - /** - * Sets the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code false}. - */ + /** + * Sets the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code false}. + */ void setWriteBufferHighWaterMark(int writeBufferHighWaterMark); - /** - * Returns the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code true} again. - */ + /** + * Returns the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will return + * {@code true} again. + */ int getWriteBufferLowWaterMark(); - /** - * Sets the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code true} again. - */ + /** + * Sets the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will return + * {@code true} again. + */ void setWriteBufferLowWaterMark(int writeBufferLowWaterMark); /** @@ -144,5 +144,6 @@ public interface NioSocketChannelConfig extends SocketChannelConfig { * will be called with the new predictor. The default factory is * {@link AdaptiveReceiveBufferSizePredictorFactory}(64, 1024, 65536). */ - void setReceiveBufferSizePredictorFactory(ReceiveBufferSizePredictorFactory predictorFactory); + void setReceiveBufferSizePredictorFactory( + ReceiveBufferSizePredictorFactory predictorFactory); }