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:
parent
f4a19886d3
commit
528b5c4328
@ -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>
|
||||
|
@ -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>
|
||||
|
3
pom.xml
3
pom.xml
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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>
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <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">
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>NettyTunnelingServlet</servlet-name>
|
||||
* <servlet-class><b>io.netty.channel.socket.http.HttpTunnelingServlet</b></servlet-class>
|
||||
* <!--
|
||||
* The name of the channel, this should be a registered local channel.
|
||||
* See LocalTransportRegister.
|
||||
* -->
|
||||
* <init-param>
|
||||
* <param-name><b>endpoint</b></param-name>
|
||||
* <param-value><b>local:myLocalServer</b></param-value>
|
||||
* </init-param>
|
||||
* <load-on-startup><b>1</b></load-on-startup>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>NettyTunnelingServlet</servlet-name>
|
||||
* <url-pattern><b>/netty-tunnel</b></url-pattern>
|
||||
* </servlet-mapping>
|
||||
* </web-app>
|
||||
* </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>
|
||||
* <bean name="my-local-echo-server"
|
||||
* class="io.netty.example.http.tunnel.LocalEchoServerRegistration" />
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* 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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -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> {
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user