Removed the modules that are not part of 4.0.0.Alpha1

- Will add them back before Beta1 is out
This commit is contained in:
Trustin Lee 2012-05-27 19:28:28 -07:00
parent f4a19886d3
commit 528b5c4328
144 changed files with 5 additions and 15555 deletions

View File

@ -29,6 +29,11 @@
<name>Netty/Example</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
@ -39,11 +44,6 @@
<artifactId>netty-codec-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport-sctp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>

View File

@ -34,11 +34,6 @@
<artifactId>netty-buffer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>

View File

@ -72,9 +72,6 @@
<module>codec</module>
<module>codec-http</module>
<module>transport</module>
<module>transport-http</module>
<module>transport-rxtx</module>
<module>transport-sctp</module>
<module>handler</module>
<module>example</module>
<module>all</module>

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2011 The Netty Project
~
~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at:
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
</parent>
<artifactId>netty-transport-http</artifactId>
<packaging>jar</packaging>
<name>Netty/Transport/HTTP</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Servlet API - completely optional -->
<!-- Used for HTTP tunneling transport -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -1,53 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.Channels;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.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.
*/
class AcceptedServerChannelPipelineFactory implements ChannelPipelineFactory {
private final ServerMessageSwitch messageSwitch;
public AcceptedServerChannelPipelineFactory(
ServerMessageSwitch messageSwitch) {
this.messageSwitch = messageSwitch;
}
@Override
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;
}
}

View File

@ -1,178 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.logging.InternalLogger;
import io.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.
*/
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.channel()
.getRemoteAddress());
if (LOG.isDebugEnabled()) {
LOG.debug("open tunnel request received from " +
ctx.channel().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) == 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.channel());
}
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.channel());
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.channel().getRemoteAddress();
String tunnelId =
HttpTunnelMessageUtils.extractTunnelId(rejectedRequest);
if (tunnelId == null) {
tunnelId = "<UNKNOWN>";
}
LOG.warn("Rejecting request from " + remoteAddress +
" representing tunnel " + tunnelId + ": " + errorMessage);
}
HttpResponse rejection =
HttpTunnelMessageUtils.createRejection(rejectedRequest,
errorMessage);
respondWith(ctx, rejection).addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.security.SecureRandom;
/**
* Default implementation of TunnelIdGenerator, which uses a
* {@link java.security.SecureRandom SecureRandom} generator
* to produce 32-bit tunnel identifiers.
*/
public class DefaultTunnelIdGenerator implements TunnelIdGenerator {
private SecureRandom generator;
public DefaultTunnelIdGenerator() {
this(new SecureRandom());
}
public DefaultTunnelIdGenerator(SecureRandom generator) {
this.generator = generator;
}
@Override
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());
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static io.netty.channel.Channels.fireChannelBound;
import static io.netty.channel.Channels.fireChannelConnected;
import static io.netty.channel.Channels.fireChannelOpen;
import java.net.InetSocketAddress;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.AbstractChannel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.socket.SocketChannel;
import io.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.
*/
final class HttpTunnelAcceptedChannel extends AbstractChannel implements
SocketChannel, HttpTunnelAcceptedChannelReceiver {
private final HttpTunnelAcceptedChannelConfig config;
private final HttpTunnelAcceptedChannelSink sink;
private final InetSocketAddress remoteAddress;
protected static HttpTunnelAcceptedChannel create(
HttpTunnelServerChannel parent, ChannelFactory factory,
ChannelPipeline pipeline, HttpTunnelAcceptedChannelSink sink,
InetSocketAddress remoteAddress,
HttpTunnelAcceptedChannelConfig config) {
HttpTunnelAcceptedChannel instance = new HttpTunnelAcceptedChannel(parent, factory, pipeline, sink,
remoteAddress, config);
fireChannelOpen(instance);
fireChannelBound(instance, instance.getLocalAddress());
fireChannelConnected(instance, instance.getRemoteAddress());
return instance;
}
private 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;
}
@Override
public SocketChannelConfig getConfig() {
return config;
}
@Override
public InetSocketAddress getLocalAddress() {
return ((HttpTunnelServerChannel) getParent()).getLocalAddress();
}
@Override
public InetSocketAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public boolean isBound() {
return sink.isActive();
}
@Override
public boolean isConnected() {
return sink.isActive();
}
@Override
public void clientClosed() {
this.setClosed();
Channels.fireChannelClosed(this);
}
@Override
public void dataReceived(ChannelBuffer data) {
Channels.fireMessageReceived(this, data);
}
@Override
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);
}
}

View File

@ -1,111 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
/**
* 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.
*/
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
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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.
*/
interface HttpTunnelAcceptedChannelFactory {
HttpTunnelAcceptedChannelReceiver newChannel(String newTunnelId,
InetSocketAddress remoteAddress);
String generateTunnelId();
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.buffer.ChannelBuffer;
/**
* Interface from the server message switch and channel sink to an
* accepted channel. Exists primarily for mock testing purposes.
*
*/
interface HttpTunnelAcceptedChannelReceiver {
void updateInterestOps(SaturationStateChange transition);
void dataReceived(ChannelBuffer data);
void clientClosed();
}

View File

@ -1,128 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.AbstractChannelSink;
import io.netty.channel.Channel;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.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.
*/
class HttpTunnelAcceptedChannelSink extends AbstractChannelSink {
final SaturationManager saturationManager;
private final ServerMessageSwitchDownstreamInterface messageSwitch;
private final String tunnelId;
private final AtomicBoolean active = new AtomicBoolean(false);
private final HttpTunnelAcceptedChannelConfig config;
public HttpTunnelAcceptedChannelSink(
ServerMessageSwitchDownstreamInterface messageSwitch,
String tunnelId, HttpTunnelAcceptedChannelConfig config) {
this.messageSwitch = messageSwitch;
this.tunnelId = tunnelId;
this.config = config;
saturationManager =
new SaturationManager(config.getWriteBufferLowWaterMark(),
config.getWriteBufferHighWaterMark());
}
@Override
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.channel();
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.channel();
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();
}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.DefaultChannelConfig;
import io.netty.channel.socket.SocketChannelConfig;
import io.netty.channel.socket.nio.NioSocketChannelConfig;
/**
* 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.
* <p>
* HTTP tunnel clients have the following additional options:
*
* <table border="1" cellspacing="0" cellpadding="6">
* <tr>
* <th>Name</th><th>Associated setter method</th>
* </tr>
* <tr><td>{@code "writeBufferHighWaterMark"}</td><td>{@link #setWriteBufferHighWaterMark(int)}</td></tr>
* <tr><td>{@code "writeBufferLowWaterMark"}</td><td>{@link #setWriteBufferLowWaterMark(int)}</td></tr>
* </table>
*/
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 io.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 io.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 io.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 io.netty.channel.Channel#isWritable()} to
* false.
*
* @see NioSocketChannelConfig#setWriteBufferHighWaterMark(int)
*/
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 io.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 io.netty.channel.socket.nio.NioSocketChannelConfig#setWriteBufferLowWaterMark(int)
*/
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;
}
}

View File

@ -1,400 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.AbstractChannel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.socket.ClientSocketChannelFactory;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.logging.InternalLogger;
import io.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.
*/
final class HttpTunnelClientChannel extends AbstractChannel implements
SocketChannel {
static final InternalLogger LOG = InternalLoggerFactory
.getInstance(HttpTunnelClientChannel.class);
private final HttpTunnelClientChannelConfig config;
final SocketChannel sendChannel;
final SocketChannel pollChannel;
volatile String tunnelId;
volatile ChannelFuture connectFuture;
volatile boolean connected;
volatile boolean bound;
volatile InetSocketAddress serverAddress;
volatile String serverHostName;
private final WorkerCallbacks callbackProxy;
private final SaturationManager saturationManager;
protected static HttpTunnelClientChannel create(ChannelFactory factory,
ChannelPipeline pipeline, HttpTunnelClientChannelSink sink,
ClientSocketChannelFactory outboundFactory,
ChannelGroup realConnections) {
HttpTunnelClientChannel instance = new HttpTunnelClientChannel(factory, pipeline, sink,
outboundFactory, realConnections);
Channels.fireChannelOpen(instance);
return instance;
}
/**
* @see HttpTunnelClientChannelFactory#newChannel(ChannelPipeline)
*/
private HttpTunnelClientChannel(ChannelFactory factory,
ChannelPipeline pipeline, HttpTunnelClientChannelSink sink,
ClientSocketChannelFactory outboundFactory,
ChannelGroup realConnections) {
super(null, factory, pipeline, sink);
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);
}
@Override
public HttpTunnelClientChannelConfig getConfig() {
return config;
}
@Override
public boolean isBound() {
return bound;
}
@Override
public boolean isConnected() {
return connected;
}
@Override
public InetSocketAddress getLocalAddress() {
return sendChannel.getLocalAddress();
}
@Override
public InetSocketAddress getRemoteAddress() {
return serverAddress;
}
@Override
protected boolean setClosed() {
return super.setClosed();
}
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() {
@Override
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;
}
void setTunnelIdForPollChannel() {
HttpTunnelClientPollHandler pollHandler =
pollChannel.pipeline()
.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() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
if (future.isSuccess()) {
originalFuture.setSuccess();
} else {
originalFuture.setFailure(future.cause());
}
updateSaturationStatus(-messageSize);
}
});
}
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 static 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);
}
@Override
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.cause());
}
}
/**
* 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);
setClosed();
}
@Override
protected void allFuturesComplete() {
if (LOG.isDebugEnabled()) {
LOG.debug("Tunnel " + tunnelId + " closed");
}
setClosed();
}
}
/**
* Contains the implementing methods of HttpTunnelClientWorkerOwner, so that these are hidden
* from the public API.
*/
class WorkerCallbacks implements HttpTunnelClientWorkerOwner {
@Override
public void onConnectRequest(ChannelFuture connectFuture,
InetSocketAddress remoteAddress) {
HttpTunnelClientChannel.this.onConnectRequest(connectFuture,
remoteAddress);
}
@Override
public void onTunnelOpened(String tunnelId) {
HttpTunnelClientChannel.this.tunnelId = tunnelId;
setTunnelIdForPollChannel();
Channels.connect(pollChannel, sendChannel.getRemoteAddress());
}
@Override
public void fullyEstablished() {
if (!bound) {
bound = true;
Channels.fireChannelBound(HttpTunnelClientChannel.this,
getLocalAddress());
}
connected = true;
connectFuture.setSuccess();
Channels.fireChannelConnected(HttpTunnelClientChannel.this,
getRemoteAddress());
}
@Override
public void onMessageReceived(ChannelBuffer content) {
Channels.fireMessageReceived(HttpTunnelClientChannel.this, content);
}
@Override
public String getServerHostName() {
if (serverHostName == null) {
serverHostName =
HttpTunnelMessageUtils
.convertToHostString(serverAddress);
}
return serverHostName;
}
}
}

View File

@ -1,172 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.SocketAddress;
import io.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}.
* <p>
* HTTP tunnel clients have the following additional options:
*
* <table border="1" cellspacing="0" cellpadding="6">
* <tr>
* <th>Name</th><th>Associated setter method</th>
* </tr>
* <tr><td>{@code "proxyAddress"}</td><td>{@link #setProxyAddress(SocketAddress)}</td></tr>
* <tr><td>{@code "writeBufferHighWaterMark"}</td><td>{@link #setWriteBufferHighWaterMark(int)}</td></tr>
* <tr><td>{@code "writeBufferLowWaterMark"}</td><td>{@link #setWriteBufferLowWaterMark(int)}</td></tr>
* </table>
*/
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 */
@Override
public int getReceiveBufferSize() {
return pollChannelConfig.getReceiveBufferSize();
}
@Override
public int getSendBufferSize() {
return pollChannelConfig.getSendBufferSize();
}
@Override
public int getSoLinger() {
return pollChannelConfig.getSoLinger();
}
@Override
public int getTrafficClass() {
return pollChannelConfig.getTrafficClass();
}
@Override
public boolean isKeepAlive() {
return pollChannelConfig.isKeepAlive();
}
@Override
public boolean isReuseAddress() {
return pollChannelConfig.isReuseAddress();
}
@Override
public boolean isTcpNoDelay() {
return pollChannelConfig.isTcpNoDelay();
}
@Override
public void setKeepAlive(boolean keepAlive) {
pollChannelConfig.setKeepAlive(keepAlive);
sendChannelConfig.setKeepAlive(keepAlive);
}
@Override
public void setPerformancePreferences(int connectionTime, int latency,
int bandwidth) {
pollChannelConfig.setPerformancePreferences(connectionTime, latency,
bandwidth);
sendChannelConfig.setPerformancePreferences(connectionTime, latency,
bandwidth);
}
@Override
public void setReceiveBufferSize(int receiveBufferSize) {
pollChannelConfig.setReceiveBufferSize(receiveBufferSize);
sendChannelConfig.setReceiveBufferSize(receiveBufferSize);
}
@Override
public void setReuseAddress(boolean reuseAddress) {
pollChannelConfig.setReuseAddress(reuseAddress);
sendChannelConfig.setReuseAddress(reuseAddress);
}
@Override
public void setSendBufferSize(int sendBufferSize) {
pollChannelConfig.setSendBufferSize(sendBufferSize);
sendChannelConfig.setSendBufferSize(sendBufferSize);
}
@Override
public void setSoLinger(int soLinger) {
pollChannelConfig.setSoLinger(soLinger);
sendChannelConfig.setSoLinger(soLinger);
}
@Override
public void setTcpNoDelay(boolean tcpNoDelay) {
pollChannelConfig.setTcpNoDelay(true);
sendChannelConfig.setTcpNoDelay(true);
}
@Override
public void setTrafficClass(int trafficClass) {
pollChannelConfig.setTrafficClass(1);
sendChannelConfig.setTrafficClass(1);
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.ClientSocketChannelFactory;
/**
* Factory used to create new client channels.
*/
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;
}
@Override
public HttpTunnelClientChannel newChannel(ChannelPipeline pipeline) {
return HttpTunnelClientChannel.create(this, pipeline, new HttpTunnelClientChannelSink(), factory,
realConnections);
}
@Override
public void releaseExternalResources() {
realConnections.close().awaitUninterruptibly();
factory.releaseExternalResources();
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.MessageEvent;
/**
* Sink of a client channel, deals with sunk events and then makes appropriate calls
* on the channel itself to push data.
*/
class HttpTunnelClientChannelSink extends AbstractChannelSink {
@Override
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.channel();
channel.sendData(e);
}
private void handleChannelStateEvent(ChannelStateEvent e) {
HttpTunnelClientChannel channel =
(HttpTunnelClientChannel) e.channel();
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;
}
}
}

View File

@ -1,93 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelHandler;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
/**
* Pipeline component which controls the client poll loop to the server.
*/
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.channel()), request);
}
}

View File

@ -1,241 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.DownstreamMessageEvent;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelHandler;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
/**
* Pipeline component which deals with sending data from the client to server.
*/
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;
private final AtomicBoolean disconnecting;
private ChannelStateEvent postShutdownEvent;
private final ConcurrentLinkedQueue<MessageEvent> queuedWrites;
private final AtomicInteger pendingRequestCount;
private long sendRequestTime;
public HttpTunnelClientSendHandler(HttpTunnelClientWorkerOwner tunnelChannel) {
this.tunnelChannel = tunnelChannel;
queuedWrites = new ConcurrentLinkedQueue<MessageEvent>();
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.channel();
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.channel().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.channel()), 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.channel(), e.getFuture(),
request, ctx.channel().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.channel().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;
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import io.netty.buffer.ChannelBuffer;
import io.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.
*/
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.
*/
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
*/
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.
*/
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
*/
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.
*/
String getServerHostName();
}

View File

@ -1,340 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.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.
*/
final 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, 0) == 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() {
return createResponseTemplate(HttpResponseStatus.RESET_CONTENT, null);
}
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) == 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;
}
private HttpTunnelMessageUtils() {
// Unused
}
}

View File

@ -1,129 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import io.netty.channel.AbstractServerChannel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineException;
import io.netty.channel.Channels;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.ServerSocketChannelConfig;
/**
*/
final class HttpTunnelServerChannel extends AbstractServerChannel implements
ServerSocketChannel {
private final ServerSocketChannel realChannel;
final HttpTunnelServerChannelConfig config;
final ServerMessageSwitch messageSwitch;
private final ChannelFutureListener CLOSE_FUTURE_PROXY =
new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
HttpTunnelServerChannel.this.setClosed();
}
};
protected static HttpTunnelServerChannel create(
HttpTunnelServerChannelFactory factory, ChannelPipeline pipeline) {
HttpTunnelServerChannel instance = new HttpTunnelServerChannel(factory, pipeline);
Channels.fireChannelOpen(instance);
return instance;
}
private HttpTunnelServerChannel(HttpTunnelServerChannelFactory factory,
ChannelPipeline pipeline) {
super(factory, pipeline, new HttpTunnelServerChannelSink());
messageSwitch = new ServerMessageSwitch(new TunnelCreator());
realChannel = factory.createRealChannel(this, messageSwitch);
// TODO fix calling of overrideable getPipeline() from constructor
HttpTunnelServerChannelSink sink =
(HttpTunnelServerChannelSink) pipeline().getSink();
sink.setRealChannel(realChannel);
sink.setCloseListener(CLOSE_FUTURE_PROXY);
config = new HttpTunnelServerChannelConfig(realChannel);
}
@Override
public ServerSocketChannelConfig getConfig() {
return config;
}
@Override
public InetSocketAddress getLocalAddress() {
return realChannel.getLocalAddress();
}
@Override
public InetSocketAddress getRemoteAddress() {
// server channels never have a remote address
return null;
}
@Override
public boolean isBound() {
return realChannel.isBound();
}
@Override
protected boolean setClosed() {
return super.setClosed();
}
/**
* Used to hide the newChannel method from the public API.
*/
private final class TunnelCreator implements
HttpTunnelAcceptedChannelFactory {
TunnelCreator() {
}
@Override
public HttpTunnelAcceptedChannelReceiver newChannel(
String newTunnelId, InetSocketAddress remoteAddress) {
ChannelPipeline childPipeline = null;
try {
childPipeline = getConfig().getPipelineFactory().pipeline();
} catch (Exception e) {
throw new ChannelPipelineException(
"Failed to initialize a pipeline.", e);
}
HttpTunnelAcceptedChannelConfig config =
new HttpTunnelAcceptedChannelConfig();
HttpTunnelAcceptedChannelSink sink =
new HttpTunnelAcceptedChannelSink(messageSwitch,
newTunnelId, config);
return HttpTunnelAcceptedChannel.create(HttpTunnelServerChannel.this, getFactory(), childPipeline, sink,
remoteAddress, config);
}
@Override
public String generateTunnelId() {
return config.getTunnelIdGenerator().generateId();
}
}
}

View File

@ -1,139 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.Map;
import java.util.Map.Entry;
import io.netty.buffer.ChannelBufferFactory;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.ServerSocketChannelConfig;
/**
*/
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();
}
@Override
public int getBacklog() {
return getWrappedConfig().getBacklog();
}
@Override
public int getReceiveBufferSize() {
return getWrappedConfig().getReceiveBufferSize();
}
@Override
public boolean isReuseAddress() {
return getWrappedConfig().isReuseAddress();
}
@Override
public void setBacklog(int backlog) {
getWrappedConfig().setBacklog(backlog);
}
@Override
public void setPerformancePreferences(int connectionTime, int latency,
int bandwidth) {
getWrappedConfig().setPerformancePreferences(connectionTime, latency,
bandwidth);
}
@Override
public void setReceiveBufferSize(int receiveBufferSize) {
getWrappedConfig().setReceiveBufferSize(receiveBufferSize);
}
@Override
public void setReuseAddress(boolean reuseAddress) {
getWrappedConfig().setReuseAddress(reuseAddress);
}
@Override
public ChannelBufferFactory getBufferFactory() {
return getWrappedConfig().getBufferFactory();
}
@Override
public int getConnectTimeoutMillis() {
return getWrappedConfig().getConnectTimeoutMillis();
}
@Override
public ChannelPipelineFactory getPipelineFactory() {
return pipelineFactory;
}
@Override
public void setBufferFactory(ChannelBufferFactory bufferFactory) {
getWrappedConfig().setBufferFactory(bufferFactory);
}
@Override
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
getWrappedConfig().setConnectTimeoutMillis(connectTimeoutMillis);
}
@Override
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);
}
}
@Override
public void setOptions(Map<String, Object> options) {
for (Entry<String, Object> e: options.entrySet()) {
setOption(e.getKey(), e.getValue());
}
}
@Override
public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) {
this.pipelineFactory = pipelineFactory;
}
public void setTunnelIdGenerator(TunnelIdGenerator tunnelIdGenerator) {
this.tunnelIdGenerator = tunnelIdGenerator;
}
public TunnelIdGenerator getTunnelIdGenerator() {
return tunnelIdGenerator;
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.ServerSocketChannelFactory;
/**
*/
public class HttpTunnelServerChannelFactory implements
ServerSocketChannelFactory {
private final ServerSocketChannelFactory realConnectionFactory;
private final ChannelGroup realConnections;
public HttpTunnelServerChannelFactory(
ServerSocketChannelFactory realConnectionFactory) {
this.realConnectionFactory = realConnectionFactory;
realConnections = new DefaultChannelGroup();
}
@Override
public HttpTunnelServerChannel newChannel(ChannelPipeline pipeline) {
return HttpTunnelServerChannel.create(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;
}
@Override
public void releaseExternalResources() {
realConnections.close().awaitUninterruptibly();
realConnectionFactory.releaseExternalResources();
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.SocketAddress;
import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.socket.ServerSocketChannel;
/**
*/
class HttpTunnelServerChannelSink extends AbstractChannelSink {
private ChannelFutureListener closeHook;
private ServerSocketChannel realChannel;
@Override
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 static final class ChannelFutureProxy implements ChannelFutureListener {
private final ChannelFuture upstreamFuture;
ChannelFutureProxy(ChannelFuture upstreamFuture) {
this.upstreamFuture = upstreamFuture;
}
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
upstreamFuture.setSuccess();
} else {
upstreamFuture.setFailure(future.cause());
}
}
}
public void setRealChannel(ServerSocketChannel realChannel) {
this.realChannel = realChannel;
}
public void setCloseListener(ChannelFutureListener closeHook) {
this.closeHook = closeHook;
}
}

View File

@ -1,248 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.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 io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.ExceptionEvent;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.local.DefaultLocalClientChannelFactory;
import io.netty.channel.local.LocalAddress;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
/**
* An {@link HttpServlet} that proxies an incoming data to the actual server
* and vice versa. Please refer to the
* <a href="package-summary.html#package_description">package summary</a> for
* the detailed usage.
* @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) {
if (logger.isWarnEnabled()) {
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())) {
if (logger.isWarnEnabled()) {
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.cause();
if (logger.isWarnEnabled()) {
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 {
if (logger.isWarnEnabled()) {
logger.warn("Unexpected exception while HTTP tunneling", e.cause());
}
e.channel().close();
}
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static io.netty.channel.socket.http.SaturationStateChange.DESATURATED;
import static io.netty.channel.socket.http.SaturationStateChange.NO_CHANGE;
import static io.netty.channel.socket.http.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.
*
*/
class SaturationManager {
private final AtomicLong desaturationPoint;
private final 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);
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
/**
* 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).
*
*/
enum SaturationStateChange {
NO_CHANGE, DESATURATED, SATURATED
}

View File

@ -1,283 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureAggregator;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.Channels;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.logging.InternalLogger;
import io.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.
*/
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<String, TunnelInfo> tunnelsById;
public ServerMessageSwitch(
HttpTunnelAcceptedChannelFactory newChannelFactory) {
this.newChannelFactory = newChannelFactory;
tunnelIdPrefix = Long.toHexString(new Random().nextLong());
tunnelsById = new ConcurrentHashMap<String, TunnelInfo>();
}
@Override
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;
}
@Override
public boolean isOpenTunnel(String tunnelId) {
TunnelInfo tunnel = tunnelsById.get(tunnelId);
return tunnel != null;
}
@Override
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<QueuedResponse> 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));
}
@Override
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;
}
@Override
public void clientCloseTunnel(String tunnelId) {
TunnelInfo tunnel = tunnelsById.get(tunnelId);
if (tunnel == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("attempt made to close tunnel id " +
tunnelId + " which is unknown or closed");
}
return;
}
tunnel.localChannel.clientClosed();
tunnelsById.remove(tunnelId);
}
@Override
public void serverCloseTunnel(String tunnelId) {
TunnelInfo tunnel = tunnelsById.get(tunnelId);
if (tunnel == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("attempt made to close tunnel id " +
tunnelId + " which is unknown or closed");
}
return;
}
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);
}
@Override
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<ChannelBuffer> 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.channel());
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 static final class RelayedChannelFutureListener implements
ChannelFutureListener {
private final ChannelFuture originalFuture;
RelayedChannelFutureListener(ChannelFuture originalFuture) {
this.originalFuture = originalFuture;
}
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
originalFuture.setSuccess();
} else {
originalFuture.setFailure(future.cause());
}
}
}
private static final class TunnelInfo {
TunnelInfo() {
}
public String tunnelId;
public HttpTunnelAcceptedChannelReceiver localChannel;
public final AtomicReference<Channel> responseChannel =
new AtomicReference<Channel>(null);
public final Queue<QueuedResponse> queuedResponses =
new ConcurrentLinkedQueue<QueuedResponse>();
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;
}
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.ChannelFuture;
/**
* The interface from a HttpTunnelAcceptedChannel to the ServerMessageSwitch.
* This primarily exists for mock object testing purposes.
*/
interface ServerMessageSwitchDownstreamInterface {
void serverCloseTunnel(String tunnelId);
void routeOutboundData(String tunnelId, ChannelBuffer data,
ChannelFuture writeFuture);
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import io.netty.buffer.ChannelBuffer;
import io.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.
*/
interface ServerMessageSwitchUpstreamInterface {
String createTunnel(InetSocketAddress remoteAddress);
boolean isOpenTunnel(String tunnelId);
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).
*/
TunnelStatus routeInboundData(String tunnelId,
ChannelBuffer inboundData);
void pollOutboundData(String tunnelId, Channel responseChannel);
enum TunnelStatus {
ALIVE, CLOSED
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
/**
* This interface is used by the server end of an http tunnel to generate new
* tunnel ids for accepted client connections.
*/
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.
*/
String generateId();
}

View File

@ -1,81 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.SocketAddress;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.ChildChannelStateEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.group.ChannelGroup;
/**
*/
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.channel().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());
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.List;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureAggregator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import io.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.
* <p>
* 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.
*/
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<ChannelBuffer> fragments =
WriteSplitter.split(data, splitThreshold);
ChannelFutureAggregator aggregator =
new ChannelFutureAggregator(e.getFuture());
for (ChannelBuffer fragment: fragments) {
ChannelFuture fragmentFuture =
Channels.future(ctx.channel(), true);
aggregator.addFuture(fragmentFuture);
Channels.write(ctx, fragmentFuture, fragment);
}
}
}
}

View File

@ -1,56 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.ArrayList;
import java.util.List;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
/**
* Provides functionality to split a provided ChannelBuffer into multiple fragments which fit
* under a specified size threshold.
*/
final class WriteSplitter {
public static List<ChannelBuffer> split(ChannelBuffer buffer,
int splitThreshold) {
int listSize = (int) ((float) buffer.readableBytes() / splitThreshold);
ArrayList<ChannelBuffer> fragmentList =
new ArrayList<ChannelBuffer>(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;
}
private WriteSplitter() {
// Unused
}
}

View File

@ -1,115 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* An HTTP-based client-side {@link io.netty.channel.socket.SocketChannel}
* and its corresponding server-side Servlet implementation that make your
* existing server application work in a firewalled network.
*
* <h3>Deploying the HTTP tunnel as a Servlet</h3>
*
* First, {@link io.netty.channel.socket.http.HttpTunnelingServlet} must be
* configured in a <tt>web.xml</tt>.
*
* <pre>
* &lt;?xml version="1.0" encoding="UTF-8"?&gt;
* &lt;web-app xmlns="http://java.sun.com/xml/ns/j2ee"
* xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
* xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
* version="2.4"&gt;
*
* &lt;servlet&gt;
* &lt;servlet-name&gt;NettyTunnelingServlet&lt;/servlet-name&gt;
* &lt;servlet-class&gt;<b>io.netty.channel.socket.http.HttpTunnelingServlet</b>&lt;/servlet-class&gt;
* &lt;!--
* The name of the channel, this should be a registered local channel.
* See LocalTransportRegister.
* --&gt;
* &lt;init-param&gt;
* &lt;param-name&gt;<b>endpoint</b>&lt;/param-name&gt;
* &lt;param-value&gt;<b>local:myLocalServer</b>&lt;/param-value&gt;
* &lt;/init-param&gt;
* &lt;load-on-startup&gt;<b>1</b>&lt;/load-on-startup&gt;
* &lt;/servlet&gt;
*
* &lt;servlet-mapping&gt;
* &lt;servlet-name&gt;NettyTunnelingServlet&lt;/servlet-name&gt;
* &lt;url-pattern&gt;<b>/netty-tunnel</b>&lt;/url-pattern&gt;
* &lt;/servlet-mapping&gt;
* &lt;/web-app&gt;
* </pre>
*
* Second, you have to bind your Netty-based server application in the same
* Servlet context or shared class loader space using the local transport
* (see {@link io.netty.channel.local.LocalServerChannelFactory}.)
* You can use your favorite IoC framework such as JBoss Microcontainer, Guice,
* and Spring to do this. The following example shows how to bind an echo
* server to the endpoint specifed above (<tt>web.xml</tt>) in JBossAS 5:
*
* <pre>
* &lt;bean name="my-local-echo-server"
* class="io.netty.example.http.tunnel.LocalEchoServerRegistration" /&gt;
*
* ...
*
* package io.netty.example.http.tunnel;
* ...
*
* public class LocalEchoServerRegistration {
*
* private final ChannelFactory factory = new DefaultLocalServerChannelFactory();
* private volatile Channel serverChannel;
*
* public void start() {
* ServerBootstrap serverBootstrap = new ServerBootstrap(factory);
* EchoHandler handler = new EchoHandler();
* serverBootstrap.getPipeline().addLast("handler", handler);
*
* // Note that "myLocalServer" is the endpoint which was specified in web.xml.
* serverChannel = serverBootstrap.bind(new LocalAddress("<b>myLocalServer</b>"));
* }
*
* public void stop() {
* serverChannel.close();
* }
* }
* </pre>
*
* <h3>Connecting to the HTTP tunnel</h3>
*
* Once the tunnel has been configured, your client-side application needs only
* a couple lines of changes.
*
* <pre>
* ClientBootstrap b = new ClientBootstrap(
* <b>new HttpTunnelingClientSocketChannelFactory(
* new NioClientSocketChannelFactory(...))</b>);
*
* // Configure the pipeline (or pipeline factory) here.
* ...
*
* // The host name of the HTTP server
* b.setOption(<b>"serverName"</b>, "example.com");
* // The path to the HTTP tunneling Servlet, which was specified in in web.xml
* b.setOption(<b>"serverPath"</b>, "contextPath<b>/netty-tunnel</b>");
* b.connect(new InetSocketAddress("example.com", 80);
* </pre>
*
* For more configuration parameters such as HTTPS options,
* refer to {@link io.netty.channel.socket.http.HttpTunnelingSocketChannelConfig}.
*/
package io.netty.channel.socket.http;

View File

@ -1,255 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.net.InetSocketAddress;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelState;
import io.netty.channel.Channels;
import io.netty.handler.codec.http.HttpRequest;
import io.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;
/**
* Tests an accepted server channel request dispatch
*/
@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);
}
}

View File

@ -1,212 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.Map;
import java.util.Map.Entry;
import io.netty.buffer.ChannelBufferFactory;
import io.netty.buffer.HeapChannelBufferFactory;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.Channels;
import io.netty.channel.socket.SocketChannelConfig;
import io.netty.util.internal.ConversionUtil;
/**
* A face channel config class for use in testing
*/
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() {
@Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline();
}
};
private int writeTimeout = 3000;
@Override
public int getReceiveBufferSize() {
return receiveBufferSize;
}
@Override
public void setReceiveBufferSize(int receiveBufferSize) {
this.receiveBufferSize = receiveBufferSize;
}
@Override
public int getSendBufferSize() {
return sendBufferSize;
}
@Override
public void setSendBufferSize(int sendBufferSize) {
this.sendBufferSize = sendBufferSize;
}
@Override
public int getSoLinger() {
return soLinger;
}
@Override
public void setSoLinger(int soLinger) {
this.soLinger = soLinger;
}
@Override
public int getTrafficClass() {
return trafficClass;
}
@Override
public void setTrafficClass(int trafficClass) {
this.trafficClass = trafficClass;
}
@Override
public boolean isKeepAlive() {
return keepAlive;
}
@Override
public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}
@Override
public boolean isReuseAddress() {
return reuseAddress;
}
@Override
public void setReuseAddress(boolean reuseAddress) {
this.reuseAddress = reuseAddress;
}
@Override
public boolean isTcpNoDelay() {
return tcpNoDelay;
}
@Override
public void setTcpNoDelay(boolean tcpNoDelay) {
this.tcpNoDelay = tcpNoDelay;
}
@Override
public void setPerformancePreferences(int connectionTime, int latency,
int bandwidth) {
// do nothing
}
@Override
public ChannelBufferFactory getBufferFactory() {
return bufferFactory;
}
@Override
public void setBufferFactory(ChannelBufferFactory bufferFactory) {
this.bufferFactory = bufferFactory;
}
@Override
public int getConnectTimeoutMillis() {
return connectTimeout;
}
@Override
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
connectTimeout = connectTimeoutMillis;
}
@Override
public ChannelPipelineFactory getPipelineFactory() {
return pipelineFactory;
}
@Override
public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) {
this.pipelineFactory = pipelineFactory;
}
public int getWriteTimeoutMillis() {
return writeTimeout;
}
public void setWriteTimeoutMillis(int writeTimeoutMillis) {
writeTimeout = writeTimeoutMillis;
}
@Override
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;
}
@Override
public void setOptions(Map<String, Object> options) {
for (Entry<String, Object> e: options.entrySet()) {
setOption(e.getKey(), e.getValue());
}
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.LinkedList;
import java.util.Queue;
import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelPipeline;
/**
* A fake channel sink for use in testing
*/
public class FakeChannelSink extends AbstractChannelSink {
public Queue<ChannelEvent> events = new LinkedList<ChannelEvent>();
@Override
public void eventSunk(ChannelPipeline pipeline, ChannelEvent e)
throws Exception {
events.add(e);
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.ArrayList;
import java.util.List;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.ClientSocketChannelFactory;
import io.netty.channel.socket.SocketChannel;
/**
* A fake client socket channel factory for use in testing
*/
public class FakeClientSocketChannelFactory implements
ClientSocketChannelFactory {
public List<FakeSocketChannel> createdChannels;
public FakeClientSocketChannelFactory() {
createdChannels = new ArrayList<FakeSocketChannel>();
}
@Override
public SocketChannel newChannel(ChannelPipeline pipeline) {
FakeSocketChannel channel =
new FakeSocketChannel(null, this, pipeline,
new FakeChannelSink());
createdChannels.add(channel);
return channel;
}
@Override
public void releaseExternalResources() {
// nothing to do
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static io.netty.channel.Channels.fireChannelBound;
import static io.netty.channel.Channels.fireChannelConnected;
import static io.netty.channel.Channels.fireChannelOpen;
import java.net.InetSocketAddress;
import io.netty.channel.AbstractChannel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.ServerSocketChannelConfig;
/**
* A fake server socket channel for use in testing
*/
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);
}
@Override
public ServerSocketChannelConfig getConfig() {
return config;
}
@Override
public InetSocketAddress getLocalAddress() {
return localAddress;
}
@Override
public InetSocketAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public boolean isBound() {
return bound;
}
@Override
public boolean isConnected() {
return connected;
}
public FakeSocketChannel acceptNewConnection(
InetSocketAddress remoteAddress, ChannelSink sink) throws Exception {
ChannelPipeline newPipeline =
getConfig().getPipelineFactory().pipeline();
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;
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.buffer.ChannelBufferFactory;
import io.netty.buffer.HeapChannelBufferFactory;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.DefaultChannelConfig;
import io.netty.channel.socket.ServerSocketChannelConfig;
/**
* A fake server socket channel config class for use in testing
*/
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();
@Override
public int getBacklog() {
return backlog;
}
@Override
public void setBacklog(int backlog) {
this.backlog = backlog;
}
@Override
public int getReceiveBufferSize() {
return receiveBufferSize;
}
@Override
public void setReceiveBufferSize(int receiveBufferSize) {
this.receiveBufferSize = receiveBufferSize;
}
@Override
public boolean isReuseAddress() {
return reuseAddress;
}
@Override
public void setReuseAddress(boolean reuseAddress) {
this.reuseAddress = reuseAddress;
}
@Override
public void setPerformancePreferences(int connectionTime, int latency,
int bandwidth) {
// ignore
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.ServerSocketChannelFactory;
/**
* A fake server socket channel factory for use in testing
*/
public class FakeServerSocketChannelFactory implements
ServerSocketChannelFactory {
public ChannelSink sink = new FakeChannelSink();
public FakeServerSocketChannel createdChannel;
@Override
public ServerSocketChannel newChannel(ChannelPipeline pipeline) {
createdChannel = new FakeServerSocketChannel(this, pipeline, sink);
return createdChannel;
}
@Override
public void releaseExternalResources() {
// nothing to do
}
}

View File

@ -1,108 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.Channels;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.SocketChannelConfig;
/**
* A fake socket channel for use in testing
*/
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;
}
@Override
public InetSocketAddress getLocalAddress() {
return localAddress;
}
@Override
public SocketChannelConfig getConfig() {
return config;
}
@Override
public InetSocketAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public boolean isBound() {
return bound;
}
@Override
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();
}
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertEquals;
import io.netty.channel.ChannelPipeline;
import io.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;
/**
* Tests HTTP tunnel accepted channel sinks
*/
@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);
}
}

View File

@ -1,293 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import io.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;
/**
* Tests HTTP tunnel client channel config
*/
@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());
}
}
}

View File

@ -1,269 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.buffer.ChannelBuffer;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelState;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import org.junit.Before;
import org.junit.Test;
/**
* Tests HTTP tunnel client channels
*/
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.cause());
}
@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.cause());
}
}

View File

@ -1,122 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.DownstreamMessageEvent;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import org.junit.Before;
import org.junit.Test;
/**
* Tests HTTP tunnel client polling
*/
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();
}
}

View File

@ -1,222 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.buffer.ChannelBuffer;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelState;
import io.netty.channel.Channels;
import io.netty.channel.DownstreamMessageEvent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import org.junit.Before;
import org.junit.Test;
/**
* Tests HTTP tunnel client sending
*/
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();
}
}

View File

@ -1,105 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.socket.ServerSocketChannel;
import io.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;
/**
* Tests HTTP tunnel server channel factories
*/
@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.pipeline());
}
@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);
//
// }
}

View File

@ -1,155 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.ExceptionEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.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;
/**
* Tests HTTP tunnel server channel sinks
*/
@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 virtualFuture = Channels.close(channel);
mockContext.assertIsSatisfied();
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));
}
});
return Channels.bind(channel, toAddress);
}
private final class ExceptionCatcher extends SimpleChannelUpstreamHandler {
ExceptionCatcher() {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
exceptionInPipeline = e.cause();
}
}
}

View File

@ -1,239 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.channel.Channel;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.ChannelState;
import io.netty.channel.Channels;
import io.netty.channel.UpstreamChannelStateEvent;
import io.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;
/**
* Tests HTTP tunnel server channels
*/
@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.channel());
assertEquals(expectedState, checkedEv.getState());
assertEquals(expectedValue, checkedEv.getValue());
}
}

View File

@ -1,481 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.bootstrap.ClientBootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.ClientSocketChannelFactory;
import io.netty.channel.socket.ServerSocketChannelFactory;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* Tests HTTP tunnel soaking
*/
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;
final ExecutorService executor;
final ScheduledExecutorService scheduledExecutor;
final DataSender c2sDataSender = new DataSender("C2S");
final DataSender s2cDataSender = new DataSender("S2C");
final DataVerifier c2sVerifier = new DataVerifier("C2S-Verifier");
final DataVerifier s2cVerifier = new DataVerifier("S2C-Verifier");
private static final 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);
HttpTunnelServerChannelFactory serverTunnelFactory =
new HttpTunnelServerChannelFactory(serverChannelFactory);
serverBootstrap = new ServerBootstrap(serverTunnelFactory);
serverBootstrap.setPipelineFactory(createServerPipelineFactory());
ClientSocketChannelFactory clientChannelFactory =
new NioClientSocketChannelFactory(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() {
@Override
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() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("c2sVerifier", c2sVerifier);
pipeline.addLast("throttleControl", new SendThrottle(
s2cDataSender));
pipeline.addLast("sendStarter",
new SimpleChannelUpstreamHandler() {
@Override
public void channelConnected(
ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
Channel childChannel = e.channel();
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.cause());
return null;
}
HttpTunnelClientChannelConfig config =
(HttpTunnelClientChannelConfig) clientChannelFuture
.channel().getConfig();
config.setWriteBufferHighWaterMark(2 * 1024 * 1024);
config.setWriteBufferLowWaterMark(1024 * 1024);
return (SocketChannel) clientChannelFuture.channel();
}
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 final String name;
private int expectedNext = 0;
private int verifiedBytes = 0;
private final 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();
}
}
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
channels.add(ctx.channel());
}
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.channel().isWritable();
sender.setWriteEnabled(writeEnabled);
}
}
private class DataSender implements Runnable {
private final AtomicReference<Channel> channel =
new AtomicReference<Channel>();
private long totalBytesSent = 0;
private long numWrites = 0;
private long runStartTime = System.currentTimeMillis();
private boolean firstRun = true;
private final AtomicBoolean writeEnabled = new AtomicBoolean(true);
private final AtomicBoolean running = new AtomicBoolean(false);
private final CountDownLatch finishLatch = new CountDownLatch(1);
private final String name;
private final 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 && !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);
}
}
}

View File

@ -1,239 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.bootstrap.ClientBootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Tests HTTP tunnelling
*/
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()));
serverFactory =
new HttpTunnelServerChannelFactory(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool()));
clientBootstrap = new ClientBootstrap(clientFactory);
clientCaptureHandler = new ClientEndHandler();
clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
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() {
@Override
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.channel());
Channel clientChannel = connectFuture.channel();
activeConnections.add(clientChannel);
assertEquals(serverChannel.getLocalAddress(),
clientChannel.getRemoteAddress());
assertTrue(serverEndLatch.await(1000, TimeUnit.MILLISECONDS));
assertNotNull(serverEnd);
// TODO: See if we can do something about it
//
// Fails on windows, seems like ipv6 is the problem here.
//
// Failed tests: testConnectClientToServer(io.netty.channel.socket.http.HttpTunnelTest): expected:</0:0:0:0:0:0:0:0:51570> but was:</192.168.210.195:51570>
// 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.channel();
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.channel();
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.channel();
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();
}
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.ChannelFuture;
/**
* A mock channel state listener
*/
public class MockChannelStateListener implements HttpTunnelClientWorkerOwner {
public boolean fullyEstablished = false;
public List<ChannelBuffer> messages = new ArrayList<ChannelBuffer>();
public String tunnelId = null;
public String serverHostName = null;
@Override
public void fullyEstablished() {
fullyEstablished = true;
}
@Override
public void onConnectRequest(ChannelFuture connectFuture,
InetSocketAddress remoteAddress) {
// not relevant for test
}
@Override
public void onMessageReceived(ChannelBuffer content) {
messages.add(content);
}
@Override
public void onTunnelOpened(String tunnelId) {
this.tunnelId = tunnelId;
}
@Override
public String getServerHostName() {
return serverHostName;
}
}

View File

@ -1,181 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelState;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.DownstreamMessageEvent;
import io.netty.channel.ExceptionEvent;
import io.netty.channel.UpstreamMessageEvent;
/**
* Test utilities for Netty
*/
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;
}
while (expected.readable()) {
byte expectedByte = expected.readByte();
byte actualByte = actual.readByte();
if (expectedByte != actualByte) {
return false;
}
}
return true;
}
public static List<ChannelBuffer> splitIntoChunks(int chunkSize,
ChannelBuffer... buffers) {
LinkedList<ChannelBuffer> chunks = new LinkedList<ChannelBuffer>();
ArrayList<ChannelBuffer> sourceBuffers = new ArrayList<ChannelBuffer>();
Collections.addAll(sourceBuffers, buffers);
Iterator<ChannelBuffer> 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> T checkIsUpstreamMessageEvent(ChannelEvent event,
Class<T> expectedMessageType) {
assertTrue(event instanceof UpstreamMessageEvent);
UpstreamMessageEvent messageEvent = (UpstreamMessageEvent) event;
assertTrue(expectedMessageType.isInstance(messageEvent.getMessage()));
return expectedMessageType.cast(messageEvent.getMessage());
}
public static <T> T checkIsDownstreamMessageEvent(ChannelEvent event,
Class<T> 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.cause();
}
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;
}
}

View File

@ -1,123 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.List;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import org.junit.Test;
/**
* Tests the Netty utilities
*/
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<ChannelBuffer> 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<ChannelBuffer> 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<ChannelBuffer> 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<ChannelBuffer> 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;
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import io.netty.channel.ChannelDownstreamHandler;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelUpstreamHandler;
/**
* A channel handler that simply sends events upstream or downstream
*/
public class NullChannelHandler implements ChannelUpstreamHandler,
ChannelDownstreamHandler {
@Override
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
throws Exception {
ctx.sendUpstream(e);
}
@Override
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
throws Exception {
ctx.sendDownstream(e);
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.*;
import static io.netty.channel.socket.http.SaturationStateChange.*;
import org.junit.Before;
import org.junit.Test;
/**
* Tests saturation managers
*
*/
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));
}
}

View File

@ -1,183 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.*;
import java.net.InetSocketAddress;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.socket.http.ServerMessageSwitchUpstreamInterface.TunnelStatus;
import io.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;
/**
* Tests server message switching
*/
@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;
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());
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import java.util.LinkedList;
import java.util.Queue;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelUpstreamHandler;
/**
* Tests the upstream event catcher
*/
public class UpstreamEventCatcher implements ChannelUpstreamHandler {
public static final String NAME = "upstreamCatcher";
public Queue<ChannelEvent> events = new LinkedList<ChannelEvent>();
@Override
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
throws Exception {
events.add(e);
}
}

View File

@ -1,156 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
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 io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.Channels;
import io.netty.channel.MessageEvent;
import org.junit.Before;
import org.junit.Test;
/**
* Tests write fragmenting capabilities
*/
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());
}
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.socket.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import org.junit.Test;
/**
* Tests write splitting capabilities
*/
public class WriteSplitterTest {
private static final int SPLIT_THRESHOLD = 1024;
@Test
public void testSplit_bufferUnderThreshold() {
ChannelBuffer buffer = createBufferWithContents(800);
List<ChannelBuffer> fragments =
WriteSplitter.split(buffer, SPLIT_THRESHOLD);
assertNotNull(fragments);
assertEquals(1, fragments.size());
}
@Test
public void testSplit_bufferMatchesThreshold() {
ChannelBuffer buffer = createBufferWithContents(SPLIT_THRESHOLD);
List<ChannelBuffer> 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<ChannelBuffer> 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<ChannelBuffer> 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);
}
}

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2011 The Netty Project
~
~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at:
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
</parent>
<artifactId>netty-transport-rxtx</artifactId>
<packaging>jar</packaging>
<name>Netty/Transport/RXTX</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.rxtx</groupId>
<artifactId>rxtx</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,77 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.rxtx;
import java.net.SocketAddress;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
/**
* A channel to a serial device using the RXTX library.
*/
public class RxtxChannel extends AbstractChannel {
RxtxChannel(final Channel parent, final ChannelFactory factory, final ChannelPipeline pipeline,
final ChannelSink sink) {
super(parent, factory, pipeline, sink);
}
@Override
public ChannelConfig getConfig() {
return ((RxtxChannelSink) getPipeline().getSink()).getConfig();
}
@Override
public boolean isBound() {
return ((RxtxChannelSink) getPipeline().getSink()).isBound();
}
@Override
public boolean isConnected() {
return ((RxtxChannelSink) getPipeline().getSink()).isConnected();
}
@Override
public SocketAddress getLocalAddress() {
return null;
}
@Override
public SocketAddress getRemoteAddress() {
return ((RxtxChannelSink) getPipeline().getSink()).getRemoteAddress();
}
@Override
public ChannelFuture bind(final SocketAddress localAddress) {
throw new UnsupportedOperationException();
}
@Override
public ChannelFuture unbind() {
throw new UnsupportedOperationException();
}
void doSetClosed() {
setClosed();
}
}

View File

@ -1,201 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.rxtx;
import gnu.io.SerialPort;
import io.netty.channel.DefaultChannelConfig;
import io.netty.util.internal.ConversionUtil;
/**
* A configuration class for RXTX device connections.
*/
public class RxtxChannelConfig extends DefaultChannelConfig {
public enum Stopbits {
STOPBITS_1(SerialPort.STOPBITS_1),
STOPBITS_2(SerialPort.STOPBITS_2),
STOPBITS_1_5(SerialPort.STOPBITS_1_5);
private final int value;
Stopbits(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static Stopbits ofValue(int value) {
for (Stopbits stopbit : Stopbits.values()) {
if (stopbit.value == value) {
return stopbit;
}
}
throw new IllegalArgumentException("Unknown value for Stopbits: " + value + ".");
}
}
public enum Databits {
DATABITS_5(SerialPort.DATABITS_5),
DATABITS_6(SerialPort.DATABITS_6),
DATABITS_7(SerialPort.DATABITS_7),
DATABITS_8(SerialPort.DATABITS_8);
private final int value;
Databits(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static Databits ofValue(int value) {
for (Databits databit : Databits.values()) {
if (databit.value == value) {
return databit;
}
}
throw new IllegalArgumentException("Unknown value for Databits: " + value + ".");
}
}
public enum Paritybit {
NONE(SerialPort.PARITY_NONE),
ODD(SerialPort.PARITY_ODD),
EVEN(SerialPort.PARITY_EVEN),
MARK(SerialPort.PARITY_MARK),
SPACE(SerialPort.PARITY_SPACE);
private final int value;
Paritybit(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static Paritybit ofValue(int value) {
for (Paritybit paritybit : Paritybit.values()) {
if (paritybit.value == value) {
return paritybit;
}
}
throw new IllegalArgumentException("Unknown value for paritybit: " + value + ".");
}
}
private int baudrate = 115200;
private boolean dtr;
private boolean rts;
private Stopbits stopbits = RxtxChannelConfig.Stopbits.STOPBITS_1;
private Databits databits = RxtxChannelConfig.Databits.DATABITS_8;
private Paritybit paritybit = RxtxChannelConfig.Paritybit.NONE;
public RxtxChannelConfig() {
// work with defaults ...
}
public RxtxChannelConfig(final int baudrate, final boolean dtr, final boolean rts, final Stopbits stopbits,
final Databits databits, final Paritybit paritybit) {
this.baudrate = baudrate;
this.dtr = dtr;
this.rts = rts;
this.stopbits = stopbits;
this.databits = databits;
this.paritybit = paritybit;
}
@Override
public boolean setOption(final String key, final Object value) {
if (key.equals("baudrate")) {
setBaudrate(ConversionUtil.toInt(value));
return true;
} else if (key.equals("stopbits")) {
setStopbits((Stopbits) value);
return true;
} else if (key.equals("databits")) {
setDatabits((Databits) value);
return true;
} else if (key.equals("paritybit")) {
setParitybit((Paritybit) value);
return true;
} else {
return super.setOption(key, value);
}
}
public void setBaudrate(final int baudrate) {
this.baudrate = baudrate;
}
public void setStopbits(final Stopbits stopbits) {
this.stopbits = stopbits;
}
public void setDatabits(final Databits databits) {
this.databits = databits;
}
private void setParitybit(final Paritybit paritybit) {
this.paritybit = paritybit;
}
public int getBaudrate() {
return baudrate;
}
public Stopbits getStopbits() {
return stopbits;
}
public Databits getDatabits() {
return databits;
}
public Paritybit getParitybit() {
return paritybit;
}
public boolean isDtr() {
return dtr;
}
public void setDtr(final boolean dtr) {
this.dtr = dtr;
}
public boolean isRts() {
return rts;
}
public void setRts(final boolean rts) {
this.rts = rts;
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.rxtx;
import java.util.concurrent.ExecutorService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.internal.ExecutorUtil;
/**
* A {@link ChannelFactory} for creating {@link RxtxChannel} instances.
*/
public class RxtxChannelFactory implements ChannelFactory {
private final ChannelGroup channels = new DefaultChannelGroup("RXTXChannelFactory-ChannelGroup");
private final ExecutorService executor;
public RxtxChannelFactory(ExecutorService executor) {
this.executor = executor;
}
@Override
public Channel newChannel(final ChannelPipeline pipeline) {
RxtxChannelSink sink = new RxtxChannelSink(executor);
RxtxChannel channel = new RxtxChannel(null, this, pipeline, sink);
sink.setChannel(channel);
channels.add(channel);
return channel;
}
@Override
public void releaseExternalResources() {
ChannelGroupFuture close = channels.close();
close.awaitUninterruptibly();
ExecutorUtil.terminate(executor);
}
}

View File

@ -1,332 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.rxtx;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.TooManyListenersException;
import java.util.concurrent.Executor;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.ChannelState;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.DefaultChannelFuture;
import io.netty.channel.MessageEvent;
import io.netty.channel.UpstreamMessageEvent;
/**
* A {@link ChannelSink} implementation of the RXTX support for Netty.
*/
public class RxtxChannelSink extends AbstractChannelSink {
private static class WriteRunnable implements Runnable {
private final DefaultChannelFuture future;
private final RxtxChannelSink channelSink;
private final ChannelBuffer message;
public WriteRunnable(final DefaultChannelFuture future, final RxtxChannelSink channelSink,
final ChannelBuffer message) {
this.future = future;
this.channelSink = channelSink;
this.message = message;
}
@Override
public void run() {
try {
channelSink.outputStream.write(message.array(), message.readerIndex(), message.readableBytes());
channelSink.outputStream.flush();
future.setSuccess();
} catch (Exception e) {
future.setFailure(e);
}
}
}
private static class ConnectRunnable implements Runnable {
private final DefaultChannelFuture channelFuture;
private final RxtxChannelSink channelSink;
ConnectRunnable(final DefaultChannelFuture channelFuture, final RxtxChannelSink channelSink) {
this.channelFuture = channelFuture;
this.channelSink = channelSink;
}
@Override
public void run() {
if (channelSink.closed) {
channelFuture.setFailure(new Exception("Channel is already closed."));
} else {
try {
connectInternal();
channelFuture.setSuccess();
} catch (Exception e) {
channelFuture.setFailure(e);
}
}
}
private void connectInternal()
throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException,
TooManyListenersException {
final CommPort commPort;
try {
final CommPortIdentifier cpi =
CommPortIdentifier.getPortIdentifier(channelSink.remoteAddress.getDeviceAddress());
commPort = cpi.open(this.getClass().getName(), 1000);
} catch (NoSuchPortException e) {
throw e;
} catch (PortInUseException e) {
throw e;
}
channelSink.serialPort = (SerialPort) commPort;
channelSink.serialPort.addEventListener(new RXTXSerialPortEventListener(channelSink));
channelSink.serialPort.notifyOnDataAvailable(true);
channelSink.serialPort.setSerialPortParams(
channelSink.config.getBaudrate(),
channelSink.config.getDatabits().getValue(),
channelSink.config.getStopbits().getValue(),
channelSink.config.getParitybit().getValue()
);
channelSink.serialPort.setDTR(channelSink.config.isDtr());
channelSink.serialPort.setRTS(channelSink.config.isRts());
channelSink.outputStream = new BufferedOutputStream(channelSink.serialPort.getOutputStream());
channelSink.inputStream = new BufferedInputStream(channelSink.serialPort.getInputStream());
}
}
private static class DisconnectRunnable implements Runnable {
private final DefaultChannelFuture channelFuture;
private final RxtxChannelSink channelSink;
public DisconnectRunnable(final DefaultChannelFuture channelFuture, final RxtxChannelSink channelSink) {
this.channelFuture = channelFuture;
this.channelSink = channelSink;
}
@Override
public void run() {
if (channelSink.closed) {
channelFuture.setFailure(new Exception("Channel is already closed."));
} else {
try {
disconnectInternal();
channelSink.channel.doSetClosed();
} catch (Exception e) {
channelFuture.setFailure(e);
}
}
}
private void disconnectInternal() throws Exception {
Exception exception = null;
try {
if (channelSink.inputStream != null) {
channelSink.inputStream.close();
}
} catch (IOException e) {
exception = e;
}
try {
if (channelSink.outputStream != null) {
channelSink.outputStream.close();
}
} catch (IOException e) {
exception = e;
}
if (channelSink.serialPort != null) {
channelSink.serialPort.removeEventListener();
channelSink.serialPort.close();
}
channelSink.inputStream = null;
channelSink.outputStream = null;
channelSink.serialPort = null;
if (exception != null) {
throw exception;
}
}
}
private final Executor executor;
final RxtxChannelConfig config;
RxtxChannel channel;
public RxtxChannelSink(final Executor executor) {
this.executor = executor;
config = new RxtxChannelConfig();
}
public boolean isConnected() {
return inputStream != null && outputStream != null;
}
public RxtxDeviceAddress getRemoteAddress() {
return remoteAddress;
}
public boolean isBound() {
return false;
}
public ChannelConfig getConfig() {
return config;
}
public void setChannel(final RxtxChannel channel) {
this.channel = channel;
}
private static class RXTXSerialPortEventListener implements SerialPortEventListener {
private final RxtxChannelSink channelSink;
public RXTXSerialPortEventListener(final RxtxChannelSink channelSink) {
this.channelSink = channelSink;
}
@Override
public void serialEvent(final SerialPortEvent event) {
switch (event.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
try {
if (channelSink.inputStream != null && channelSink.inputStream.available() > 0) {
int available = channelSink.inputStream.available();
byte[] buffer = new byte[available];
int read = channelSink.inputStream.read(buffer);
if (read > 0) {
ChannelBuffer channelBuffer = ChannelBuffers.wrappedBuffer(buffer, 0, read);
UpstreamMessageEvent upstreamMessageEvent = new UpstreamMessageEvent(
channelSink.channel,
channelBuffer,
channelSink.getRemoteAddress()
);
channelSink.channel.getPipeline().sendUpstream(upstreamMessageEvent);
}
}
} catch (IOException e) {
Channels.fireExceptionCaught(channelSink.channel, e);
channelSink.channel.close();
}
break;
}
}
}
RxtxDeviceAddress remoteAddress;
BufferedOutputStream outputStream;
BufferedInputStream inputStream;
SerialPort serialPort;
volatile boolean closed;
@Override
public void eventSunk(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception {
final ChannelFuture future = e.getFuture();
if (e instanceof ChannelStateEvent) {
final ChannelStateEvent stateEvent = (ChannelStateEvent) e;
final ChannelState state = stateEvent.getState();
final Object value = stateEvent.getValue();
switch (state) {
case OPEN:
if (Boolean.FALSE.equals(value)) {
executor.execute(new DisconnectRunnable((DefaultChannelFuture) future, this));
}
break;
case BOUND:
throw new UnsupportedOperationException();
case CONNECTED:
if (value != null) {
remoteAddress = (RxtxDeviceAddress) value;
executor.execute(new ConnectRunnable((DefaultChannelFuture) future, this));
} else {
executor.execute(new DisconnectRunnable((DefaultChannelFuture) future, this));
}
break;
case INTEREST_OPS:
throw new UnsupportedOperationException();
}
} else if (e instanceof MessageEvent) {
final MessageEvent event = (MessageEvent) e;
if (event.getMessage() instanceof ChannelBuffer) {
executor.execute(
new WriteRunnable((DefaultChannelFuture) future, this, (ChannelBuffer) event.getMessage())
);
} else {
throw new IllegalArgumentException(
"Only ChannelBuffer objects are supported to be written onto the RXTXChannelSink! "
+ "Please check if the encoder pipeline is configured correctly."
);
}
}
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.rxtx;
import java.net.SocketAddress;
/**
* A {@link SocketAddress} subclass to wrap the serial port address of a RXTX
* device (e.g. COM1, /dev/ttyUSB0).
*/
public class RxtxDeviceAddress extends SocketAddress {
private static final long serialVersionUID = -2907820090993709523L;
private final String deviceAddress;
/**
*
* @param deviceAddress the address of the device (e.g. COM1, /dev/ttyUSB0, ...)
*/
public RxtxDeviceAddress(String deviceAddress) {
this.deviceAddress = deviceAddress;
}
public String getDeviceAddress() {
return deviceAddress;
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* A serial and parallel port communication transport based on <a href="http://rxtx.qbang.org/">RXTX</a>.
*/
package io.netty.channel.rxtx;

View File

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2011 The Netty Project
~
~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at:
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
</parent>
<artifactId>netty-transport-sctp</artifactId>
<packaging>jar</packaging>
<name>Netty/Transport/SCTP</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<excludes>
<!-- Exclude the com.sun.nio stuff as this is included in the jdk if its supported by the running os -->
<exclude>**/com/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,42 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public class AbstractNotificationHandler<T> implements NotificationHandler<T> {
static {
UnsupportedOperatingSystemException.raise();
}
public HandlerResult handleNotification(AssociationChangeNotification notification, Object o) {
return null;
}
public HandlerResult handleNotification(Notification notification, Object o) {
return null;
}
public HandlerResult handleNotification(PeerAddressChangeNotification notification, Object o) {
return null;
}
public HandlerResult handleNotification(SendFailedNotification notification, Object o) {
return null;
}
public HandlerResult handleNotification(ShutdownNotification notification, Object o) {
return null;
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public class Association {
static {
UnsupportedOperatingSystemException.raise();
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public abstract class AssociationChangeNotification implements Notification {
static {
UnsupportedOperatingSystemException.raise();
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
/**
* TODO Document me.
*
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
*/
public enum HandlerResult {
CONTINUE, RETURN;
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
import java.net.SocketAddress;
public abstract class MessageInfo {
static {
UnsupportedOperatingSystemException.raise();
}
public static MessageInfo createOutgoing(Association association, SocketAddress address, int streamNumber) {
return null;
}
public abstract SocketAddress address();
public abstract int streamNumber();
public abstract MessageInfo streamNumber(int streamNumber);
public abstract int payloadProtocolID();
public abstract MessageInfo payloadProtocolID(int ppid);
public abstract boolean isComplete();
public abstract boolean isUnordered();
public abstract MessageInfo unordered(boolean b);
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public interface Notification {
Association association();
}

View File

@ -1,19 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public interface NotificationHandler<T> {
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public abstract class PeerAddressChangeNotification implements Notification {
static {
UnsupportedOperatingSystemException.raise();
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Set;
public abstract class SctpChannel extends AbstractSelectableChannel {
static {
UnsupportedOperatingSystemException.raise();
}
public static SctpChannel open() throws IOException {
return null;
}
protected SctpChannel(SelectorProvider provider) {
super(provider);
}
public abstract <T> T getOption(SctpSocketOption<T> name) throws IOException;
public abstract <T> SctpChannel setOption(SctpSocketOption<T> name, T value) throws IOException;
public abstract Set<SocketAddress> getAllLocalAddresses() throws IOException;
public abstract Set<SocketAddress> getRemoteAddresses() throws IOException;
public abstract Association association() throws IOException;
public abstract SctpChannel bind(SocketAddress local) throws IOException;
public abstract boolean connect(SocketAddress remote) throws IOException;
public abstract boolean finishConnect() throws IOException;
public abstract SctpChannel bindAddress(InetAddress inetAddress) throws IOException;
public abstract SctpChannel unbindAddress(InetAddress inetAddress) throws IOException;
public abstract <T> MessageInfo receive(ByteBuffer dst, T attachment, NotificationHandler<T> handler) throws IOException;
public abstract int send(ByteBuffer src, MessageInfo messageInfo) throws IOException;
public abstract Set<SctpSocketOption<?>> supportedOptions();
}

View File

@ -1,50 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Set;
public abstract class SctpServerChannel extends AbstractSelectableChannel {
static {
UnsupportedOperatingSystemException.raise();
}
public static SctpServerChannel open() throws IOException {
return null;
}
protected SctpServerChannel(SelectorProvider provider) {
super(provider);
}
public abstract <T> T getOption(SctpSocketOption<T> name) throws IOException;
public abstract <T> SctpChannel setOption(SctpSocketOption<T> name, T value) throws IOException;
public abstract Set<SocketAddress> getAllLocalAddresses() throws IOException;
public abstract SctpServerChannel bind(SocketAddress local) throws IOException;
public abstract SctpServerChannel bind(SocketAddress local, int backlog) throws IOException;
public abstract SctpServerChannel bindAddress(InetAddress inetAddress) throws IOException;
public abstract SctpServerChannel unbindAddress(InetAddress inetAddress) throws IOException;
public abstract SctpChannel accept() throws IOException;
}

View File

@ -1,21 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public interface SctpSocketOption<T> {
String name();
Class<T> type();
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
import java.net.SocketAddress;
public class SctpStandardSocketOptions {
static {
UnsupportedOperatingSystemException.raise();
}
public static final SctpSocketOption<Boolean> SCTP_DISABLE_FRAGMENTS = null;
public static final SctpSocketOption<Boolean> SCTP_EXPLICIT_COMPLETE = null;
public static final SctpSocketOption<Integer> SCTP_FRAGMENT_INTERLEAVE = null;
public static final SctpSocketOption<InitMaxStreams> SCTP_INIT_MAXSTREAMS = null;
public static final SctpSocketOption<Boolean> SCTP_NODELAY = null;
public static final SctpSocketOption<SocketAddress> SCTP_PRIMARY_ADDR = null;
public static final SctpSocketOption<SocketAddress> SCTP_SET_PEER_PRIMARY_ADDR = null;
public static final SctpSocketOption<Integer> SO_LINGER = null;
public static final SctpSocketOption<Integer> SO_RCVBUF = null;
public static final SctpSocketOption<Integer> SO_SNDBUF = null;
public static class InitMaxStreams {
public static InitMaxStreams create(int i, int i1) {
return null;
}
public int maxInStreams() {
return 0;
}
public int maxOutStreams() {
return 0;
}
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public abstract class SendFailedNotification implements Notification {
static {
UnsupportedOperatingSystemException.raise();
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public abstract class ShutdownNotification implements Notification {
static {
UnsupportedOperatingSystemException.raise();
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sun.nio.sctp;
public class UnsupportedOperatingSystemException extends RuntimeException {
private static final long serialVersionUID = -221782446524784377L;
public static void raise() {
throw new UnsupportedOperatingSystemException();
}
public UnsupportedOperatingSystemException() {
super();
}
public UnsupportedOperatingSystemException(String message) {
super(message);
}
public UnsupportedOperatingSystemException(String message, Throwable cause) {
super(message, cause);
}
public UnsupportedOperatingSystemException(Throwable cause) {
super(cause);
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* This package is only included to let SCTP also compile on non-unix operation systems.
*
* <strong>This will not get included in the generated jar!</strong>
*/
package com.sun.nio.sctp;

View File

@ -1,187 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import com.sun.nio.sctp.SctpChannel;
import java.util.Map;
import io.netty.channel.AdaptiveReceiveBufferSizePredictorFactory;
import io.netty.channel.ChannelException;
import io.netty.channel.ReceiveBufferSizePredictor;
import io.netty.channel.ReceiveBufferSizePredictorFactory;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
import io.netty.util.internal.ConversionUtil;
/**
* The default {@link io.netty.channel.socket.nio.NioSocketChannelConfig} implementation for SCTP.
*/
class DefaultNioSctpChannelConfig extends DefaultSctpChannelConfig implements NioSctpChannelConfig {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(DefaultNioSctpChannelConfig.class);
private static final ReceiveBufferSizePredictorFactory DEFAULT_PREDICTOR_FACTORY =
new AdaptiveReceiveBufferSizePredictorFactory();
private volatile int writeBufferHighWaterMark = 64 * 1024;
private volatile int writeBufferLowWaterMark = 32 * 1024;
private volatile ReceiveBufferSizePredictor predictor;
private volatile ReceiveBufferSizePredictorFactory predictorFactory = DEFAULT_PREDICTOR_FACTORY;
private volatile int writeSpinCount = 16;
DefaultNioSctpChannelConfig(SctpChannel channel) {
super(channel);
}
@Override
public void setOptions(Map<String, Object> options) {
super.setOptions(options);
if (getWriteBufferHighWaterMark() < getWriteBufferLowWaterMark()) {
// Recover the integrity of the configuration with a sensible value.
setWriteBufferLowWaterMark0(getWriteBufferHighWaterMark() >>> 1);
if (logger.isWarnEnabled()) {
// Notify the user about misconfiguration.
logger.warn(
"writeBufferLowWaterMark cannot be greater than " +
"writeBufferHighWaterMark; setting to the half of the " +
"writeBufferHighWaterMark.");
}
}
}
@Override
public boolean setOption(String key, Object value) {
if (super.setOption(key, value)) {
return true;
}
if (key.equals("writeBufferHighWaterMark")) {
setWriteBufferHighWaterMark0(ConversionUtil.toInt(value));
} else if (key.equals("writeBufferLowWaterMark")) {
setWriteBufferLowWaterMark0(ConversionUtil.toInt(value));
} else if (key.equals("writeSpinCount")) {
setWriteSpinCount(ConversionUtil.toInt(value));
} else if (key.equals("receiveBufferSizePredictorFactory")) {
setReceiveBufferSizePredictorFactory((ReceiveBufferSizePredictorFactory) value);
} else if (key.equals("receiveBufferSizePredictor")) {
setReceiveBufferSizePredictor((ReceiveBufferSizePredictor) value);
} else {
return false;
}
return true;
}
@Override
public int getWriteBufferHighWaterMark() {
return writeBufferHighWaterMark;
}
@Override
public void setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
if (writeBufferHighWaterMark < getWriteBufferLowWaterMark()) {
throw new IllegalArgumentException(
"writeBufferHighWaterMark cannot be less than " +
"writeBufferLowWaterMark (" + getWriteBufferLowWaterMark() + "): " +
writeBufferHighWaterMark);
}
setWriteBufferHighWaterMark0(writeBufferHighWaterMark);
}
private void setWriteBufferHighWaterMark0(int writeBufferHighWaterMark) {
if (writeBufferHighWaterMark < 0) {
throw new IllegalArgumentException(
"writeBufferHighWaterMark: " + writeBufferHighWaterMark);
}
this.writeBufferHighWaterMark = writeBufferHighWaterMark;
}
@Override
public int getWriteBufferLowWaterMark() {
return writeBufferLowWaterMark;
}
@Override
public void setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
if (writeBufferLowWaterMark > getWriteBufferHighWaterMark()) {
throw new IllegalArgumentException(
"writeBufferLowWaterMark cannot be greater than " +
"writeBufferHighWaterMark (" + getWriteBufferHighWaterMark() + "): " +
writeBufferLowWaterMark);
}
setWriteBufferLowWaterMark0(writeBufferLowWaterMark);
}
private void setWriteBufferLowWaterMark0(int writeBufferLowWaterMark) {
if (writeBufferLowWaterMark < 0) {
throw new IllegalArgumentException(
"writeBufferLowWaterMark: " + writeBufferLowWaterMark);
}
this.writeBufferLowWaterMark = writeBufferLowWaterMark;
}
@Override
public int getWriteSpinCount() {
return writeSpinCount;
}
@Override
public void setWriteSpinCount(int writeSpinCount) {
if (writeSpinCount <= 0) {
throw new IllegalArgumentException(
"writeSpinCount must be a positive integer.");
}
this.writeSpinCount = writeSpinCount;
}
@Override
public ReceiveBufferSizePredictor getReceiveBufferSizePredictor() {
ReceiveBufferSizePredictor predictor = this.predictor;
if (predictor == null) {
try {
this.predictor = predictor = getReceiveBufferSizePredictorFactory().getPredictor();
} catch (Exception e) {
throw new ChannelException(
"Failed to create a new " +
ReceiveBufferSizePredictor.class.getSimpleName() + '.',
e);
}
}
return predictor;
}
@Override
public void setReceiveBufferSizePredictor(
ReceiveBufferSizePredictor predictor) {
if (predictor == null) {
throw new NullPointerException("predictor");
}
this.predictor = predictor;
}
@Override
public ReceiveBufferSizePredictorFactory getReceiveBufferSizePredictorFactory() {
return predictorFactory;
}
@Override
public void setReceiveBufferSizePredictorFactory(ReceiveBufferSizePredictorFactory predictorFactory) {
if (predictorFactory == null) {
throw new NullPointerException("predictorFactory");
}
this.predictorFactory = predictorFactory;
}
}

View File

@ -1,170 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import static io.netty.channel.ChannelOption.*;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelOption;
import io.netty.channel.DefaultChannelConfig;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.sun.nio.sctp.SctpChannel;
import com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams;
/**
* The default {@link NioSocketChannelConfig} implementation for SCTP.
*/
class DefaultSctpChannelConfig extends DefaultChannelConfig implements SctpChannelConfig {
private final SctpChannel channel;
DefaultSctpChannelConfig(SctpChannel channel) {
if (channel == null) {
throw new NullPointerException("channel");
}
this.channel = channel;
}
@Override
public Map<ChannelOption<?>, Object> getOptions() {
// TODO: Investigate if other SCTP options such as SCTP_PRIMARY_ADDR can be exposed.
return getOptions(super.getOptions(), SO_RCVBUF, SO_SNDBUF, SCTP_NODELAY, SCTP_INIT_MAXSTREAMS);
}
@Override
public <T> T getOption(ChannelOption<T> option) {
if (option == SO_RCVBUF) {
return (T) Integer.valueOf(getReceiveBufferSize());
}
if (option == SO_SNDBUF) {
return (T) Integer.valueOf(getSendBufferSize());
}
if (option == SCTP_NODELAY) {
return (T) Boolean.valueOf(isSctpNoDelay());
}
if (option == SCTP_INIT_MAXSTREAMS) {
InitMaxStreams ims = getInitMaxStreams();
if (ims == null) {
return null;
}
List<Integer> values = new ArrayList<Integer>(2);
values.add(ims.maxInStreams());
values.add(ims.maxOutStreams());
@SuppressWarnings("unchecked")
T ret = (T) values;
return ret;
}
return super.getOption(option);
}
@Override
public <T> boolean setOption(ChannelOption<T> option, T value) {
validate(option, value);
if (option == SO_RCVBUF) {
setReceiveBufferSize((Integer) value);
} else if (option == SO_SNDBUF) {
setSendBufferSize((Integer) value);
} else if (option == SCTP_NODELAY) {
setSctpNoDelay((Boolean) value);
} else if (option == SCTP_INIT_MAXSTREAMS) {
@SuppressWarnings("unchecked")
List<Integer> values = (List<Integer>) value;
setInitMaxStreams(InitMaxStreams.create(values.get(0), values.get(1)));
} else {
return super.setOption(option, value);
}
return true;
}
@Override
public boolean isSctpNoDelay() {
try {
return channel.getOption(com.sun.nio.sctp.SctpStandardSocketOptions.SCTP_NODELAY);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setSctpNoDelay(boolean sctpNoDelay) {
try {
channel.setOption(com.sun.nio.sctp.SctpStandardSocketOptions.SCTP_NODELAY, sctpNoDelay);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public int getSendBufferSize() {
try {
return channel.getOption(com.sun.nio.sctp.SctpStandardSocketOptions.SO_SNDBUF);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setSendBufferSize(int sendBufferSize) {
try {
channel.setOption(com.sun.nio.sctp.SctpStandardSocketOptions.SO_SNDBUF, sendBufferSize);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public int getReceiveBufferSize() {
try {
return channel.getOption(com.sun.nio.sctp.SctpStandardSocketOptions.SO_RCVBUF);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setReceiveBufferSize(int receiveBufferSize) {
try {
channel.setOption(com.sun.nio.sctp.SctpStandardSocketOptions.SO_RCVBUF, receiveBufferSize);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public InitMaxStreams getInitMaxStreams() {
try {
return channel.getOption(com.sun.nio.sctp.SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setInitMaxStreams(InitMaxStreams initMaxStreams) {
try {
channel.setOption(com.sun.nio.sctp.SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS, initMaxStreams);
} catch (IOException e) {
throw new ChannelException(e);
}
}
}

View File

@ -1,128 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import static com.sun.nio.sctp.SctpStandardSocketOptions.*;
import io.netty.channel.ChannelException;
import io.netty.channel.DefaultServerChannelConfig;
import io.netty.util.internal.ConversionUtil;
import java.io.IOException;
/**
* The default {@link io.netty.channel.socket.ServerSocketChannelConfig} implementation for SCTP.
*/
public class DefaultSctpServerChannelConfig extends DefaultServerChannelConfig
implements SctpServerChannelConfig {
private final com.sun.nio.sctp.SctpServerChannel serverChannel;
private volatile int backlog;
/**
* Creates a new instance.
*/
public DefaultSctpServerChannelConfig(com.sun.nio.sctp.SctpServerChannel serverChannel) {
if (serverChannel == null) {
throw new NullPointerException("serverChannel");
}
this.serverChannel = serverChannel;
}
@Override
public boolean setOption(String key, Object value) {
if (super.setOption(key, value)) {
return true;
}
if (key.equals("sctpInitMaxStreams")) {
final Integer maxInOutStreams = ConversionUtil.toInt(value);
setInitMaxStreams(InitMaxStreams.create(maxInOutStreams, maxInOutStreams));
} else if (key.equals("backlog")) {
setBacklog(ConversionUtil.toInt(value));
} else {
return false;
}
return true;
}
@Override
public int getSendBufferSize() {
try {
return serverChannel.getOption(SO_SNDBUF);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setSendBufferSize(int sendBufferSize) {
try {
serverChannel.setOption(SO_SNDBUF, sendBufferSize);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public int getReceiveBufferSize() {
try {
return serverChannel.getOption(SO_RCVBUF);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setReceiveBufferSize(int receiveBufferSize) {
try {
serverChannel.setOption(SO_RCVBUF, receiveBufferSize);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public InitMaxStreams getInitMaxStreams() {
try {
return serverChannel.getOption(SCTP_INIT_MAXSTREAMS);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public void setInitMaxStreams(InitMaxStreams initMaxStreams) {
try {
serverChannel.setOption(SCTP_INIT_MAXSTREAMS, initMaxStreams);
} catch (IOException e) {
throw new ChannelException(e);
}
}
@Override
public int getBacklog() {
return backlog;
}
@Override
public void setBacklog(int backlog) {
if (backlog < 0) {
throw new IllegalArgumentException("backlog: " + backlog);
}
this.backlog = backlog;
}
}

View File

@ -1,95 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import io.netty.channel.ReceiveBufferSizePredictor;
import io.netty.channel.ReceiveBufferSizePredictorFactory;
import io.netty.channel.socket.nio.NioChannelConfig;
/**
* A {@link io.netty.channel.sctp.SctpChannelConfig} for a NIO SCTP/IP {@link io.netty.channel.sctp.SctpChannel}.
*
* <h3>Available options</h3>
*
* In addition to the options provided by {@link io.netty.channel.ChannelConfig} and
* {@link io.netty.channel.sctp.SctpChannelConfig}, {@link io.netty.channel.sctp.NioSctpChannelConfig} allows the
* following options in the option map:
*
* <table border="1" cellspacing="0" cellpadding="6">
* <tr>
* <th>Name</th><th>Associated setter method</th>
* </tr><tr>
* <td>{@code "writeBufferHighWaterMark"}</td><td>{@link #setWriteBufferHighWaterMark(int)}</td>
* </tr><tr>
* <td>{@code "writeBufferLowWaterMark"}</td><td>{@link #setWriteBufferLowWaterMark(int)}</td>
* </tr><tr>
* <td>{@code "writeSpinCount"}</td><td>{@link #setWriteSpinCount(int)}</td>
* </tr><tr>
* <td>{@code "receiveBufferSizePredictor"}</td><td>{@link #setReceiveBufferSizePredictor(io.netty.channel.ReceiveBufferSizePredictor)}</td>
* </tr><tr>
* <td>{@code "receiveBufferSizePredictorFactory"}</td><td>{@link #setReceiveBufferSizePredictorFactory(io.netty.channel.ReceiveBufferSizePredictorFactory)}</td>
* </tr>
* </table>
*/
public interface NioSctpChannelConfig extends SctpChannelConfig, NioChannelConfig {
/**
* Sets the maximum loop count for a write operation until
* {@link java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer)} returns a non-zero value.
* It is similar to what a spin lock is used for in concurrency programming.
* It improves memory utilization and write throughput depending on
* the platform that JVM runs on. The default value is {@code 16}.
*
* @throws IllegalArgumentException
* if the specified value is {@code 0} or less than {@code 0}
*/
void setWriteSpinCount(int writeSpinCount);
/**
* Returns the {@link io.netty.channel.ReceiveBufferSizePredictor} which predicts the
* number of readable bytes in the socket receive buffer. The default
* predictor is <tt>{@link io.netty.channel.AdaptiveReceiveBufferSizePredictor}(64, 1024, 65536)</tt>.
*/
ReceiveBufferSizePredictor getReceiveBufferSizePredictor();
/**
* Sets the {@link io.netty.channel.ReceiveBufferSizePredictor} which predicts the
* number of readable bytes in the socket receive buffer. The default
* predictor is <tt>{@link io.netty.channel.AdaptiveReceiveBufferSizePredictor}(64, 1024, 65536)</tt>.
*/
void setReceiveBufferSizePredictor(ReceiveBufferSizePredictor predictor);
/**
* Returns the {@link io.netty.channel.ReceiveBufferSizePredictorFactory} which creates a new
* {@link io.netty.channel.ReceiveBufferSizePredictor} when a new channel is created and
* no {@link io.netty.channel.ReceiveBufferSizePredictor} was set. If no predictor was set
* for the channel, {@link #setReceiveBufferSizePredictor(io.netty.channel.ReceiveBufferSizePredictor)}
* will be called with the new predictor. The default factory is
* <tt>{@link io.netty.channel.AdaptiveReceiveBufferSizePredictorFactory}(64, 1024, 65536)</tt>.
*/
ReceiveBufferSizePredictorFactory getReceiveBufferSizePredictorFactory();
/**
* Sets the {@link io.netty.channel.ReceiveBufferSizePredictor} which creates a new
* {@link io.netty.channel.ReceiveBufferSizePredictor} when a new channel is created and
* no {@link io.netty.channel.ReceiveBufferSizePredictor} was set. If no predictor was set
* for the channel, {@link #setReceiveBufferSizePredictor(io.netty.channel.ReceiveBufferSizePredictor)}
* will be called with the new predictor. The default factory is
* <tt>{@link io.netty.channel.AdaptiveReceiveBufferSizePredictorFactory}(64, 1024, 65536)</tt>.
*/
void setReceiveBufferSizePredictorFactory(
ReceiveBufferSizePredictorFactory predictorFactory);
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import com.sun.nio.sctp.SctpChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import static io.netty.channel.Channels.*;
/**
*/
final class SctpAcceptedChannel extends SctpChannelImpl {
SctpAcceptedChannel(
ChannelFactory factory, ChannelPipeline pipeline,
Channel parent, ChannelSink sink,
SctpChannel socket, SctpWorker worker) {
super(parent, factory, pipeline, sink, socket, worker);
setConnected();
fireChannelOpen(this);
fireChannelBound(this, getLocalAddress());
fireChannelConnected(this, getRemoteAddress());
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelState;
import io.netty.channel.DownstreamChannelStateEvent;
import java.net.InetAddress;
public class SctpBindAddressEvent extends DownstreamChannelStateEvent {
/**
* Creates a new instance.
*/
public SctpBindAddressEvent(Channel channel, ChannelFuture future, InetAddress localAddress) {
super(channel, future, ChannelState.INTEREST_OPS, localAddress);
}
@Override
public InetAddress getValue() {
return (InetAddress) super.getValue();
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import com.sun.nio.sctp.Association;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Set;
/**
*/
public interface SctpChannel extends Channel {
/**
* Return the primary local address of the SCTP channel.
*/
@Override
InetSocketAddress getLocalAddress();
/**
* Return all local addresses of the SCTP channel.
*/
Set<InetSocketAddress> getAllLocalAddresses();
/**
* Returns the configuration of this channel.
*/
@Override
NioSctpChannelConfig getConfig();
/**
* Return the primary remote address of the SCTP channel.
*/
@Override
InetSocketAddress getRemoteAddress();
/**
* Return all remote addresses of the SCTP channel.
*/
Set<InetSocketAddress> getAllRemoteAddresses();
/**
* Bind a multi-homing address to the already bound channel
*/
ChannelFuture bindAddress(InetAddress localAddress);
/**
* Unbind a multi-homing address from a already established channel
*/
ChannelFuture unbindAddress(InetAddress localAddress);
/**
* Get the underlying SCTP association
*/
Association association();
}

View File

@ -1,84 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import static com.sun.nio.sctp.SctpStandardSocketOptions.*;
import io.netty.channel.ChannelConfig;
/**
* A {@link io.netty.channel.ChannelConfig} for a {@link io.netty.channel.sctp.SctpChannel}.
* <p/>
* <h3>Available options</h3>
* <p/>
* In addition to the options provided by {@link io.netty.channel.ChannelConfig},
* {@link io.netty.channel.sctp.SctpChannelConfig} allows the following options in the option map:
* <p/>
* <table border="1" cellspacing="0" cellpadding="6">
* <tr>
* <th>Name</th><th>Associated setter method</th>
* </tr><tr>
* <td>{@code "sctpNoDelay"}</td><td>{@link #setSctpNoDelay(boolean)}}</td>
* </tr><tr>
* <td>{@code "receiveBufferSize"}</td><td>{@link #setReceiveBufferSize(int)}</td>
* </tr><tr>
* <td>{@code "sendBufferSize"}</td><td>{@link #setSendBufferSize(int)}</td>
* </tr><tr>
* <td>{@code "sctpInitMaxStreams"}</td><td>{@link #setInitMaxStreams(InitMaxStreams)}</td>
* </tr>
* </table>
*/
public interface SctpChannelConfig extends ChannelConfig {
/**
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_NODELAY}</a> option.
*/
boolean isSctpNoDelay();
/**
* Sets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_NODELAY}</a> option.
*/
void setSctpNoDelay(boolean sctpNoDelay);
/**
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_SNDBUF}</a> option.
*/
int getSendBufferSize();
/**
* Sets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_SNDBUF}</a> option.
*/
void setSendBufferSize(int sendBufferSize);
/**
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_RCVBUF}</a> option.
*/
int getReceiveBufferSize();
/**
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_RCVBUF}</a> option.
*/
void setReceiveBufferSize(int receiveBufferSize);
/**
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_INIT_MAXSTREAMS}</a> option.
*/
InitMaxStreams getInitMaxStreams();
/**
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_INIT_MAXSTREAMS}</a> option.
*/
void setInitMaxStreams(InitMaxStreams initMaxStreams);
}

View File

@ -1,230 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import static io.netty.channel.Channels.future;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.MessageEvent;
import io.netty.channel.sctp.SctpSendBufferPool.SctpSendBuffer;
import io.netty.channel.socket.nio.AbstractNioChannel;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import com.sun.nio.sctp.Association;
/**
*/
class SctpChannelImpl extends AbstractNioChannel implements SctpChannel {
private static final int ST_OPEN = 0;
private static final int ST_BOUND = 1;
private static final int ST_CONNECTED = 2;
private static final int ST_CLOSED = -1;
volatile int state = ST_OPEN;
private final NioSctpChannelConfig config;
final SctpNotificationHandler notificationHandler = new SctpNotificationHandler(this);
public SctpChannelImpl(Channel parent, ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink,
com.sun.nio.sctp.SctpChannel channel, SctpWorker worker) {
super(parent, factory, pipeline, sink, worker, new SctpJdkChannel(channel));
config = new DefaultNioSctpChannelConfig(channel);
getCloseFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
state = ST_CLOSED;
}
});
}
Queue<MessageEvent> getWriteBufferQueue() {
return writeBufferQueue;
}
Object getWriteLock() {
return writeLock;
}
Object getInterestedOpsLock() {
return interestOpsLock;
}
void setWriteSuspended(boolean writeSuspended) {
this.writeSuspended = writeSuspended;
}
boolean getWriteSuspended() {
return writeSuspended;
}
void setInWriteNowLoop(boolean inWriteNowLoop) {
this.inWriteNowLoop = inWriteNowLoop;
}
MessageEvent getCurrentWriteEvent() {
return currentWriteEvent;
}
void setCurrentWriteEvent(MessageEvent currentWriteEvent) {
this.currentWriteEvent = currentWriteEvent;
}
int getRawInterestOps() {
return super.getInterestOps();
}
void setRawInterestOpsNow(int interestOps) {
super.setInterestOpsNow(interestOps);
}
SctpSendBuffer getCurrentWriteBuffer() {
return (SctpSendBuffer) currentWriteBuffer;
}
void setCurrentWriteBuffer(SctpSendBuffer currentWriteBuffer) {
this.currentWriteBuffer = currentWriteBuffer;
}
@Override
public SctpWorker getWorker() {
return (SctpWorker) super.getWorker();
}
@Override
public NioSctpChannelConfig getConfig() {
return config;
}
@Override
public SctpJdkChannel getJdkChannel() {
return (SctpJdkChannel) super.getJdkChannel();
}
@Override
public Set<InetSocketAddress> getAllLocalAddresses() {
try {
final Set<SocketAddress> allLocalAddresses = getJdkChannel().getChannel().getAllLocalAddresses();
final Set<InetSocketAddress> addresses = new HashSet<InetSocketAddress>(allLocalAddresses.size());
for (SocketAddress socketAddress: allLocalAddresses) {
addresses.add((InetSocketAddress) socketAddress);
}
return addresses;
} catch (Throwable t) {
return Collections.emptySet();
}
}
@Override
public Set<InetSocketAddress> getAllRemoteAddresses() {
try {
final Set<SocketAddress> allLocalAddresses = getJdkChannel().getChannel().getRemoteAddresses();
final Set<InetSocketAddress> addresses = new HashSet<InetSocketAddress>(allLocalAddresses.size());
for (SocketAddress socketAddress: allLocalAddresses) {
addresses.add((InetSocketAddress) socketAddress);
}
return addresses;
} catch (Throwable t) {
return Collections.emptySet();
}
}
@Override
public ChannelFuture bindAddress(InetAddress localAddress) {
ChannelFuture future = future(this);
pipeline().sendDownstream(new SctpBindAddressEvent(this, future, localAddress));
return future;
}
@Override
public ChannelFuture unbindAddress(InetAddress localAddress) {
ChannelFuture future = future(this);
pipeline().sendDownstream(new SctpUnbindAddressEvent(this, future, localAddress));
return future;
}
@Override
public Association association() {
try {
return getJdkChannel().getChannel().association();
} catch (Throwable e) {
return null;
}
}
@Override
public boolean isOpen() {
return state >= ST_OPEN;
}
@Override
public boolean isBound() {
return state >= ST_BOUND;
}
@Override
public boolean isConnected() {
return state == ST_CONNECTED;
}
final void setBound() {
assert state == ST_OPEN : "Invalid state: " + state;
state = ST_BOUND;
}
protected final void setConnected() {
if (state != ST_CLOSED) {
state = ST_CONNECTED;
}
}
@Override
protected boolean setClosed() {
return super.setClosed();
}
@Override
protected WriteRequestQueue createRequestQueue() {
return new WriteRequestQueue() {
@Override
protected int getMessageSize(MessageEvent e) {
Object m = e.getMessage();
if (m instanceof SctpFrame) {
return ((SctpFrame) m).getPayloadBuffer().readableBytes();
}
return 0;
}
};
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import static io.netty.channel.Channels.*;
import java.io.IOException;
import com.sun.nio.sctp.SctpChannel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
/**
*/
final class SctpClientChannel extends SctpChannelImpl {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(SctpClientChannel.class);
private static SctpChannel newChannael() {
SctpChannel underlayingChannel;
try {
underlayingChannel = SctpChannel.open();
} catch (IOException e) {
throw new ChannelException("Failed to open a sctp channel.", e);
}
boolean success = false;
try {
underlayingChannel.configureBlocking(false);
success = true;
} catch (IOException e) {
throw new ChannelException("Failed to enter non-blocking mode.", e);
} finally {
if (!success) {
try {
underlayingChannel.close();
} catch (IOException e) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.",
e);
}
}
}
}
return underlayingChannel;
}
volatile ChannelFuture connectFuture;
volatile boolean boundManually;
// Does not need to be volatile as it's accessed by only one thread.
long connectDeadlineNanos;
SctpClientChannel(
ChannelFactory factory, ChannelPipeline pipeline,
ChannelSink sink, SctpWorker worker) {
super(null, factory, pipeline, sink, newChannael(), worker);
fireChannelOpen(this);
}
}

View File

@ -1,159 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import static io.netty.channel.Channels.fireChannelBound;
import static io.netty.channel.Channels.fireExceptionCaught;
import static io.netty.channel.Channels.succeededFuture;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelState;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.MessageEvent;
import io.netty.channel.socket.nio.AbstractNioChannelSink;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException;
/**
*/
class SctpClientPipelineSink extends AbstractNioChannelSink {
@Override
public void eventSunk(
ChannelPipeline pipeline, ChannelEvent e) throws Exception {
if (e instanceof ChannelStateEvent) {
ChannelStateEvent event = (ChannelStateEvent) e;
SctpClientChannel channel =
(SctpClientChannel) event.channel();
ChannelFuture future = event.getFuture();
ChannelState state = event.getState();
Object value = event.getValue();
switch (state) {
case OPEN:
if (Boolean.FALSE.equals(value)) {
channel.getWorker().close(channel, future);
}
break;
case BOUND:
if (value != null) {
bind(channel, future, (SocketAddress) value);
} else {
channel.getWorker().close(channel, future);
}
break;
case CONNECTED:
if (value != null) {
connect(channel, future, (SocketAddress) value);
} else {
channel.getWorker().close(channel, future);
}
break;
case INTEREST_OPS:
if (event instanceof SctpBindAddressEvent) {
SctpBindAddressEvent bindAddressEvent = (SctpBindAddressEvent) event;
bindAddress(channel, bindAddressEvent.getFuture(), bindAddressEvent.getValue());
} else if (event instanceof SctpUnbindAddressEvent) {
SctpUnbindAddressEvent unbindAddressEvent = (SctpUnbindAddressEvent) event;
unbindAddress(channel, unbindAddressEvent.getFuture(), unbindAddressEvent.getValue());
} else {
channel.getWorker().setInterestOps(channel, future, ((Integer) value).intValue());
}
break;
}
} else if (e instanceof MessageEvent) {
MessageEvent event = (MessageEvent) e;
SctpChannelImpl channel = (SctpChannelImpl) event.channel();
boolean offered = channel.getWriteBufferQueue().offer(event);
assert offered;
channel.getWorker().writeFromUserCode(channel);
}
}
private void bind(
SctpClientChannel channel, ChannelFuture future,
SocketAddress localAddress) {
try {
channel.getJdkChannel().bind(localAddress);
channel.boundManually = true;
channel.setBound();
future.setSuccess();
fireChannelBound(channel, channel.getLocalAddress());
} catch (Throwable t) {
future.setFailure(t);
fireExceptionCaught(channel, t);
}
}
private void bindAddress(
SctpClientChannel channel, ChannelFuture future,
InetAddress localAddress) {
try {
channel.getJdkChannel().getChannel().bindAddress(localAddress);
future.setSuccess();
} catch (Throwable t) {
future.setFailure(t);
fireExceptionCaught(channel, t);
}
}
private void unbindAddress(
SctpClientChannel channel, ChannelFuture future,
InetAddress localAddress) {
try {
channel.getJdkChannel().getChannel().unbindAddress(localAddress);
future.setSuccess();
} catch (Throwable t) {
future.setFailure(t);
fireExceptionCaught(channel, t);
}
}
private void connect(
final SctpClientChannel channel, final ChannelFuture cf,
SocketAddress remoteAddress) {
try {
channel.getJdkChannel().connect(remoteAddress);
channel.getCloseFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f)
throws Exception {
if (!cf.isDone()) {
cf.setFailure(new ClosedChannelException());
}
}
});
cf.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
channel.connectFuture = cf;
channel.getWorker().registerWithWorker(channel, cf);
} catch (Throwable t) {
cf.setFailure(t);
fireExceptionCaught(channel, t);
channel.getWorker().close(channel, succeededFuture(channel));
}
}
}

View File

@ -1,130 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.socket.nio.SelectorUtil;
import io.netty.channel.socket.nio.WorkerPool;
import io.netty.util.ExternalResourceReleasable;
import java.util.concurrent.Executor;
/**
* A {@link io.netty.channel.socket.ClientSocketChannelFactory} which creates a client-side NIO-based
* {@link io.netty.channel.socket.SocketChannel}. It utilizes the non-blocking I/O mode which was
* introduced with NIO to serve many number of concurrent connections
* efficiently.
*
* <h3>How threads work</h3>
* <p>
* There are two types of threads in a {@link SctpClientSocketChannelFactory};
* one is boss thread and the other is worker thread.
*
* <h4>Boss thread</h4>
* <p>
* One {@link SctpClientSocketChannelFactory} has one boss thread. It makes
* a connection attempt on request. Once a connection attempt succeeds,
* the boss thread passes the connected {@link io.netty.channel.Channel} to one of the worker
* threads that the {@link SctpClientSocketChannelFactory} manages.
*
* <h4>Worker threads</h4>
* <p>
* One {@link SctpClientSocketChannelFactory} can have one or more worker
* threads. A worker thread performs non-blocking read and write for one or
* more {@link io.netty.channel.Channel}s in a non-blocking mode.
*
* <h3>Life cycle of threads and graceful shutdown</h3>
* <p>
* All threads are acquired from the {@link java.util.concurrent.Executor}s which were specified
* when a {@link SctpClientSocketChannelFactory} was created. A boss thread is
* acquired from the {@code bossExecutor}, and worker threads are acquired from
* the {@code workerExecutor}. Therefore, you should make sure the specified
* {@link java.util.concurrent.Executor}s are able to lend the sufficient number of threads.
* It is the best bet to specify {@linkplain java.util.concurrent.Executors#newCachedThreadPool() a cached thread pool}.
* <p>
* Both boss and worker threads are acquired lazily, and then released when
* there's nothing left to process. All the related resources such as
* {@link java.nio.channels.Selector} are also released when the boss and worker threads are
* released. Therefore, to shut down a service gracefully, you should do the
* following:
*
* <ol>
* <li>close all channels created by the factory usually using
* {@link io.netty.channel.group.ChannelGroup#close()}, and</li>
* <li>call {@link #releaseExternalResources()}.</li>
* </ol>
*
* Please make sure not to shut down the executor until all channels are
* closed. Otherwise, you will end up with a {@link java.util.concurrent.RejectedExecutionException}
* and the related resources might not be released properly.
*
* @apiviz.landmark
*/
public class SctpClientSocketChannelFactory implements ChannelFactory {
private final WorkerPool<SctpWorker> workerPool;
private final ChannelSink sink;
/**
* Creates a new instance. Calling this constructor is same with calling
* {@link #SctpClientSocketChannelFactory(java.util.concurrent.Executor, java.util.concurrent.Executor, int)} with 2 *
* the number of available processors in the machine. The number of
* available processors is obtained by {@link Runtime#availableProcessors()}.
*
* @param workerExecutor
* the {@link java.util.concurrent.Executor} which will execute the I/O worker threads
*/
public SctpClientSocketChannelFactory(Executor workerExecutor) {
this(workerExecutor, SelectorUtil.DEFAULT_IO_THREADS);
}
/**
* Creates a new instance.
*
* @param workerExecutor
* the {@link java.util.concurrent.Executor} which will execute the I/O worker threads
* @param workerCount
* the maximum number of I/O worker threads
*/
public SctpClientSocketChannelFactory(Executor workerExecutor,
int workerCount) {
this(new SctpWorkerPool(workerExecutor, workerCount, true));
}
public SctpClientSocketChannelFactory(WorkerPool<SctpWorker> workerPool) {
if (workerPool == null) {
throw new NullPointerException("workerPool");
}
this.workerPool = workerPool;
sink = new SctpClientPipelineSink();
}
@Override
public SctpChannel newChannel(ChannelPipeline pipeline) {
return new SctpClientChannel(this, pipeline, sink, workerPool.nextWorker());
}
@Override
public void releaseExternalResources() {
if (workerPool instanceof ExternalResourceReleasable) {
((ExternalResourceReleasable) workerPool).releaseExternalResources();
}
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.sctp;
import com.sun.nio.sctp.MessageInfo;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
/**
*/
public final class SctpFrame {
private final int streamIdentifier;
private final int protocolIdentifier;
private final ChannelBuffer payloadBuffer;
private MessageInfo msgInfo;
/**
* Essential data that is being carried within SCTP Data Chunk
* @param protocolIdentifier of payload
* @param streamIdentifier that you want to send the payload
* @param payloadBuffer channel buffer
*/
public SctpFrame(int protocolIdentifier, int streamIdentifier, ChannelBuffer payloadBuffer) {
this.protocolIdentifier = protocolIdentifier;
this.streamIdentifier = streamIdentifier;
this.payloadBuffer = payloadBuffer;
}
public SctpFrame(MessageInfo msgInfo, ChannelBuffer payloadBuffer) {
this.msgInfo = msgInfo;
this.streamIdentifier = msgInfo.streamNumber();
this.protocolIdentifier = msgInfo.payloadProtocolID();
this.payloadBuffer = payloadBuffer;
}
public int getStreamIdentifier() {
return streamIdentifier;
}
public int getProtocolIdentifier() {
return protocolIdentifier;
}
public ChannelBuffer getPayloadBuffer() {
if (payloadBuffer.readable()) {
return payloadBuffer.slice();
} else {
return ChannelBuffers.EMPTY_BUFFER;
}
}
public MessageInfo getMessageInfo() {
return msgInfo;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SctpFrame sctpFrame = (SctpFrame) o;
if (protocolIdentifier != sctpFrame.protocolIdentifier) {
return false;
}
if (streamIdentifier != sctpFrame.streamIdentifier) {
return false;
}
if (!payloadBuffer.equals(sctpFrame.payloadBuffer)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = streamIdentifier;
result = 31 * result + protocolIdentifier;
result = 31 * result + payloadBuffer.hashCode();
return result;
}
@Override
public String toString() {
return new StringBuilder().
append("SctpFrame{").
append("streamIdentifier=").
append(streamIdentifier).
append(", protocolIdentifier=").
append(protocolIdentifier).
append(", payloadBuffer=").
append(ChannelBuffers.hexDump(getPayloadBuffer())).
append('}').toString();
}
}

Some files were not shown because too many files have changed in this diff Show More