diff --git a/README.md b/README.md index 814f6589e0..4e9e5ed0ed 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,5 @@ Netty is an asynchronous event-driven network application framework for rapid de - __master__ branch contains code for Netty 4.x -- __3.2__ branch contains code for Netty 3.x +- __3__ branch contains code for Netty 3.x diff --git a/all/pom.xml b/all/pom.xml index ad6b5d7a77..a85be0c8d4 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty diff --git a/buffer/pom.xml b/buffer/pom.xml index 0fec61d47b..17574e422b 100644 --- a/buffer/pom.xml +++ b/buffer/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-buffer diff --git a/codec-http/pom.xml b/codec-http/pom.xml index c1ae7cef75..7c369d4d0d 100644 --- a/codec-http/pom.xml +++ b/codec-http/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-codec-http @@ -34,6 +34,11 @@ netty-codec ${project.version} + + ${project.groupId} + netty-handler + ${project.version} + diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index a3ca41229f..fdf2a17f2a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -18,6 +18,8 @@ package io.netty.handler.codec.http.websocketx; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpHeaders.Values; @@ -168,7 +170,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { ChannelFuture future = channel.write(request); - channel.pipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder()); + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + ChannelPipeline p = future.channel().pipeline(); + p.replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder()); + } + }); return future; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index bb1a33c29c..05bff21925 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -17,6 +17,8 @@ package io.netty.handler.codec.http.websocketx; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpHeaders.Values; @@ -147,7 +149,13 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { ChannelFuture future = channel.write(request); - channel.pipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true)); + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + ChannelPipeline p = future.channel().pipeline(); + p.replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true)); + } + }); return future; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 68c8446fd5..a5a3668eaa 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -17,6 +17,8 @@ package io.netty.handler.codec.http.websocketx; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpHeaders.Values; @@ -147,7 +149,13 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { ChannelFuture future = channel.write(request); - channel.pipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true)); + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + ChannelPipeline p = future.channel().pipeline(); + p.replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true)); + } + }); return future; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java index d76fa6a995..d677e4fed3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java @@ -22,6 +22,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.HttpChunkAggregator; @@ -171,16 +172,21 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { } // Upgrade the connection and send the handshake response. - ChannelPipeline p = channel.pipeline(); - if (p.get(HttpChunkAggregator.class) != null) { - p.remove(HttpChunkAggregator.class); - } - p.replace(HttpRequestDecoder.class, "wsdecoder", - new WebSocket00FrameDecoder(getMaxFramePayloadLength())); - ChannelFuture future = channel.write(res); - p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder()); + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + ChannelPipeline p = future.channel().pipeline(); + if (p.get(HttpChunkAggregator.class) != null) { + p.remove(HttpChunkAggregator.class); + } + p.replace(HttpRequestDecoder.class, "wsdecoder", + new WebSocket00FrameDecoder(getMaxFramePayloadLength())); + + p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder()); + } + }); return future; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index f4be1b93a0..3cd32e7f82 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -147,14 +147,19 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { ChannelFuture future = channel.write(res); // Upgrade the connection and send the handshake response. - ChannelPipeline p = channel.pipeline(); - if (p.get(HttpChunkAggregator.class) != null) { - p.remove(HttpChunkAggregator.class); - } + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + ChannelPipeline p = future.channel().pipeline(); + if (p.get(HttpChunkAggregator.class) != null) { + p.remove(HttpChunkAggregator.class); + } - p.replace(HttpRequestDecoder.class, "wsdecoder", - new WebSocket08FrameDecoder(true, allowExtensions, getMaxFramePayloadLength())); - p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false)); + p.replace(HttpRequestDecoder.class, "wsdecoder", + new WebSocket08FrameDecoder(true, allowExtensions, getMaxFramePayloadLength())); + p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false)); + } + }); return future; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index 95737bd23e..cad8aba9ce 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -147,14 +147,19 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { ChannelFuture future = channel.write(res); // Upgrade the connection and send the handshake response. - ChannelPipeline p = channel.pipeline(); - if (p.get(HttpChunkAggregator.class) != null) { - p.remove(HttpChunkAggregator.class); - } + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + ChannelPipeline p = future.channel().pipeline(); + if (p.get(HttpChunkAggregator.class) != null) { + p.remove(HttpChunkAggregator.class); + } - p.replace(HttpRequestDecoder.class, "wsdecoder", - new WebSocket13FrameDecoder(true, allowExtensions, getMaxFramePayloadLength())); - p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false)); + p.replace(HttpRequestDecoder.class, "wsdecoder", + new WebSocket13FrameDecoder(true, allowExtensions, getMaxFramePayloadLength())); + p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false)); + } + }); return future; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java new file mode 100644 index 0000000000..e1bdc80198 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.util.AttributeKey; + +/** + * Handles WebSocket control frames (Close, Ping, Pong) and data frames (Text and Binary) are passed + * to the next handler in the pipeline. + */ +public class WebSocketServerProtocolHandler extends ChannelInboundMessageHandlerAdapter { + + private static final AttributeKey HANDSHAKER_ATTR_KEY = + new AttributeKey(WebSocketServerHandshaker.class.getName()); + + private final String websocketPath; + private final String subprotocols; + private final boolean allowExtensions; + + public WebSocketServerProtocolHandler(String websocketPath) { + this(websocketPath, null, false); + } + + public WebSocketServerProtocolHandler(String websocketPath, String subprotocols) { + this(websocketPath, subprotocols, false); + } + + public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions) { + this.websocketPath = websocketPath; + this.subprotocols = subprotocols; + this.allowExtensions = allowExtensions; + } + + @Override + public void afterAdd(ChannelHandlerContext ctx) { + ChannelPipeline cp = ctx.pipeline(); + if (cp.get(WebSocketServerProtocolHandshakeHandler.class) == null) { + // Add the WebSocketHandshakeHandler before this one. + ctx.pipeline().addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(), + new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols, allowExtensions)); + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { + if (frame instanceof CloseWebSocketFrame) { + WebSocketServerHandshaker handshaker = WebSocketServerProtocolHandler.getHandshaker(ctx); + handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame); + return; + } else if (frame instanceof PingWebSocketFrame) { + ctx.channel().write(new PongWebSocketFrame(frame.getBinaryData())); + return; + } + + ctx.nextInboundMessageBuffer().add(frame); + ctx.fireInboundBufferUpdated(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (cause instanceof WebSocketHandshakeException) { + DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST); + response.setContent(Unpooled.wrappedBuffer(cause.getMessage().getBytes())); + ctx.channel().write(response).addListener(ChannelFutureListener.CLOSE); + } else { + ctx.close(); + } + } + + static WebSocketServerHandshaker getHandshaker(ChannelHandlerContext ctx) { + return ctx.attr(HANDSHAKER_ATTR_KEY).get(); + } + + static void setHandshaker(ChannelHandlerContext ctx, WebSocketServerHandshaker handshaker) { + ctx.attr(HANDSHAKER_ATTR_KEY).set(handshaker); + } + + static ChannelHandler forbiddenHttpRequestResponder() { + return new ChannelInboundMessageHandlerAdapter() { + @Override + public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!(msg instanceof WebSocketFrame)) { + DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN); + ctx.channel().write(response); + } else { + ctx.nextInboundMessageBuffer().add(msg); + ctx.fireInboundBufferUpdated(); + } + } + }; + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java new file mode 100644 index 0000000000..9bf620fcd3 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.ssl.SslHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; + +/** + * Handles the HTTP handshake (the HTTP Upgrade request) + */ +public class WebSocketServerProtocolHandshakeHandler extends ChannelInboundMessageHandlerAdapter { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(WebSocketServerProtocolHandshakeHandler.class); + private final String websocketPath; + private final String subprotocols; + private final boolean allowExtensions; + + public WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols, + boolean allowExtensions) { + this.websocketPath = websocketPath; + this.subprotocols = subprotocols; + this.allowExtensions = allowExtensions; + } + + @Override + public void messageReceived(final ChannelHandlerContext ctx, HttpRequest req) throws Exception { + if (req.getMethod() != GET) { + sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN)); + return; + } + + final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( + getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols, allowExtensions); + final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); + if (handshaker == null) { + wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); + } else { + final ChannelFuture handshakeFuture = handshaker.handshake(ctx.channel(), req); + handshakeFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + ctx.fireExceptionCaught(future.cause()); + } + } + }); + WebSocketServerProtocolHandler.setHandshaker(ctx, handshaker); + ctx.pipeline().replace(this, "WS403Responder", + WebSocketServerProtocolHandler.forbiddenHttpRequestResponder()); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.error("Exception Caught", cause); + ctx.close(); + } + + private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) { + ChannelFuture f = ctx.channel().write(res); + if (!isKeepAlive(req) || res.getStatus().getCode() != 200) { + f.addListener(ChannelFutureListener.CLOSE); + } + } + + private static String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) { + String protocol = "ws"; + if (cp.get(SslHandler.class) != null) { + // SSL in use so use Secure WebSockets + protocol = "wss"; + } + return protocol + "://" + req.getHeader(HttpHeaders.Names.HOST) + path; + } + +} diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java new file mode 100644 index 0000000000..0bec4da3b5 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version + * 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package io.netty.handler.codec.http.websocketx; + +import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.HttpHeaders.Names; + +public class WebSocketRequestBuilder { + + private HttpVersion httpVersion; + private HttpMethod method; + private String uri; + private String host; + private String upgrade; + private String connection; + private String key; + private String origin; + private WebSocketVersion version; + + public WebSocketRequestBuilder httpVersion(HttpVersion httpVersion) { + this.httpVersion = httpVersion; + return this; + } + + public WebSocketRequestBuilder method(HttpMethod method) { + this.method = method; + return this; + } + + public WebSocketRequestBuilder uri(String uri) { + this.uri = uri; + return this; + } + + public WebSocketRequestBuilder host(String host) { + this.host = host; + return this; + } + + public WebSocketRequestBuilder upgrade(String upgrade) { + this.upgrade = upgrade; + return this; + } + + public WebSocketRequestBuilder connection(String connection) { + this.connection = connection; + return this; + } + + public WebSocketRequestBuilder key(String key) { + this.key = key; + return this; + } + + public WebSocketRequestBuilder origin(String origin) { + this.origin = origin; + return this; + } + + public WebSocketRequestBuilder version13() { + this.version = WebSocketVersion.V13; + return this; + } + + public WebSocketRequestBuilder version8() { + this.version = WebSocketVersion.V08; + return this; + } + + public WebSocketRequestBuilder version00() { + this.version = null; + return this; + } + + public WebSocketRequestBuilder noVersion() { + return this; + } + + public HttpRequest build() { + HttpRequest req = new DefaultHttpRequest(httpVersion, method, uri); + if (host != null) { + req.setHeader(Names.HOST, host); + } + if (upgrade != null) { + req.setHeader(Names.UPGRADE, upgrade); + } + if (connection != null) { + req.setHeader(Names.CONNECTION, connection); + } + if (key != null) { + req.setHeader(Names.SEC_WEBSOCKET_KEY, key); + } + if (origin != null) { + req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, origin); + } + if (version != null) { + req.setHeader(Names.SEC_WEBSOCKET_VERSION, version.toHttpHeaderValue()); + } + return req; + } + + public static HttpRequest sucessful() { + return new WebSocketRequestBuilder().httpVersion(HTTP_1_1) + .method(HttpMethod.GET) + .uri("/test") + .host("server.example.com") + .upgrade(WEBSOCKET.toLowerCase()) + .key("dGhlIHNhbXBsZSBub25jZQ==") + .origin("http://example.com") + .version13() + .build(); + } +} diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java new file mode 100644 index 0000000000..d75ae96f5e --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version + * 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package io.netty.handler.codec.http.websocketx; + +import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; +import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; +import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import io.netty.buffer.MessageBuf; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import io.netty.channel.ChannelOutboundMessageHandlerAdapter; +import io.netty.channel.embedded.EmbeddedMessageChannel; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; + +import org.junit.Test; + +public class WebSocketServerProtocolHandlerTest { + + @Test + public void testHttpUpgradeRequest() throws Exception { + EmbeddedMessageChannel ch = createChannel(new MockOutboundHandler()); + ChannelHandlerContext handshakerCtx = ch.pipeline().context(WebSocketServerProtocolHandshakeHandler.class); + + writeUpgradeRequest(ch); + + assertEquals(SWITCHING_PROTOCOLS, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus()); + assertNotNull(WebSocketServerProtocolHandler.getHandshaker(handshakerCtx)); + } + + @Test + public void testSubsequentHttpRequestsAfterUpgradeShouldReturn403() throws Exception { + EmbeddedMessageChannel ch = createChannel(new MockOutboundHandler()); + + writeUpgradeRequest(ch); + assertEquals(SWITCHING_PROTOCOLS, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus()); + + ch.writeInbound(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); + assertEquals(FORBIDDEN, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus()); + } + + @Test + public void testHttpUpgradeRequestInvalidUpgradeHeader() { + EmbeddedMessageChannel ch = createChannel(); + HttpRequest httpRequest = new WebSocketRequestBuilder().httpVersion(HTTP_1_1) + .method(HttpMethod.GET) + .uri("/test") + .connection("Upgrade") + .version00() + .upgrade("BogusSocket") + .build(); + + ch.writeInbound(httpRequest); + + HttpResponse response = getHttpResponse(ch); + assertEquals(HttpResponseStatus.BAD_REQUEST, response.getStatus()); + assertEquals("not a WebSocket handshake request: missing upgrade", getResponseMessage(response)); + + } + + @Test + public void testHttpUpgradeRequestMissingWSKeyHeader() { + EmbeddedMessageChannel ch = createChannel(); + HttpRequest httpRequest = new WebSocketRequestBuilder().httpVersion(HTTP_1_1) + .method(HttpMethod.GET) + .uri("/test") + .key(null) + .connection("Upgrade") + .upgrade(WEBSOCKET.toLowerCase()) + .version13() + .build(); + + ch.writeInbound(httpRequest); + + HttpResponse response = getHttpResponse(ch); + assertEquals(HttpResponseStatus.BAD_REQUEST, response.getStatus()); + assertEquals("not a WebSocket request: missing key", getResponseMessage(response)); + } + + @Test + public void testHandleTextFrame() { + CustomTextFrameHandler customTextFrameHandler = new CustomTextFrameHandler(); + EmbeddedMessageChannel ch = createChannel(customTextFrameHandler); + writeUpgradeRequest(ch); + // Removing the HttpRequestDecoder as we are writing a TextWebSocketFrame so decoding is not neccessary. + ch.pipeline().remove(HttpRequestDecoder.class); + + ch.writeInbound(new TextWebSocketFrame("payload")); + + assertEquals("processed: payload", customTextFrameHandler.getContent()); + } + + private EmbeddedMessageChannel createChannel() { + return createChannel(null); + } + + private EmbeddedMessageChannel createChannel(ChannelHandler handler) { + return new EmbeddedMessageChannel( + new WebSocketServerProtocolHandler("/test", null, false), + new HttpRequestDecoder(), + new HttpResponseEncoder(), + new MockOutboundHandler(), + handler); + } + + private void writeUpgradeRequest(EmbeddedMessageChannel ch) { + ch.writeInbound(WebSocketRequestBuilder.sucessful()); + } + + private String getResponseMessage(HttpResponse response) { + return new String(response.getContent().array()); + } + + private HttpResponse getHttpResponse(EmbeddedMessageChannel ch) { + MessageBuf outbound = ch.pipeline().context(MockOutboundHandler.class).outboundMessageBuffer(); + return (HttpResponse) outbound.poll(); + } + + private static class MockOutboundHandler extends ChannelOutboundMessageHandlerAdapter { + @Override + public void flush(ChannelHandlerContext ctx, ChannelFuture future) throws Exception { + //NoOp + } + } + + private static class CustomTextFrameHandler extends ChannelInboundMessageHandlerAdapter { + private String content; + + @Override + public void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { + content = "processed: " + msg.getText(); + } + + public String getContent() { + return content; + } + + } + +} diff --git a/codec/pom.xml b/codec/pom.xml index 872b70d6c2..e795254a0a 100644 --- a/codec/pom.xml +++ b/codec/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-codec diff --git a/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java b/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java index ef802be984..1fe097bf7b 100644 --- a/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java @@ -69,7 +69,7 @@ public abstract class ByteToMessageDecoder ByteBuf in = ctx.inboundByteBuffer(); boolean decoded = false; - for (;;) { + while (in.readable()) { try { int oldInputLength = in.readableBytes(); O o = decode(ctx, in); diff --git a/codec/src/test/java/io/netty/handler/codec/DelimiterBasedFrameDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/DelimiterBasedFrameDecoderTest.java new file mode 100644 index 0000000000..61f216614c --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/DelimiterBasedFrameDecoderTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec; + +import static org.junit.Assert.*; + +import java.nio.charset.Charset; + +import org.junit.Test; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedByteChannel; + +public class DelimiterBasedFrameDecoderTest { + + @Test + public void testMultipleLinesStrippedDelimiters() { + EmbeddedByteChannel ch = new EmbeddedByteChannel(new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter())); + ch.writeInbound(Unpooled.copiedBuffer("TestLine\r\ng\r\n", Charset.defaultCharset())); + assertEquals("TestLine", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertEquals("g", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertNull(ch.readInbound()); + } + + @Test + public void testIncompleteLinesStrippedDelimiters() { + EmbeddedByteChannel ch = new EmbeddedByteChannel(new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter())); + ch.writeInbound(Unpooled.copiedBuffer("Test", Charset.defaultCharset())); + assertNull(ch.readInbound()); + ch.writeInbound(Unpooled.copiedBuffer("Line\r\ng\r\n", Charset.defaultCharset())); + assertEquals("TestLine", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertEquals("g", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertNull(ch.readInbound()); + } + + @Test + public void testMultipleLines() { + EmbeddedByteChannel ch = new EmbeddedByteChannel(new DelimiterBasedFrameDecoder(8192, false, Delimiters.lineDelimiter())); + ch.writeInbound(Unpooled.copiedBuffer("TestLine\r\ng\r\n", Charset.defaultCharset())); + assertEquals("TestLine\r\n", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertEquals("g\r\n", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertNull(ch.readInbound()); + } + + @Test + public void testIncompleteLines() { + EmbeddedByteChannel ch = new EmbeddedByteChannel(new DelimiterBasedFrameDecoder(8192, false, Delimiters.lineDelimiter())); + ch.writeInbound(Unpooled.copiedBuffer("Test", Charset.defaultCharset())); + assertNull(ch.readInbound()); + ch.writeInbound(Unpooled.copiedBuffer("Line\r\ng\r\n", Charset.defaultCharset())); + assertEquals("TestLine\r\n", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertEquals("g\r\n", ((ByteBuf)ch.readInbound()).toString(Charset.defaultCharset())); + assertNull(ch.readInbound()); + } +} diff --git a/common/pom.xml b/common/pom.xml index 1bb68454f4..6e641f43ef 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-common diff --git a/example/pom.xml b/example/pom.xml index 02c9ba450d..aeae757e55 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-example diff --git a/example/src/main/java/io/netty/example/discard/DiscardClient.java b/example/src/main/java/io/netty/example/discard/DiscardClient.java index 10742a5bd6..d7837d2f4f 100644 --- a/example/src/main/java/io/netty/example/discard/DiscardClient.java +++ b/example/src/main/java/io/netty/example/discard/DiscardClient.java @@ -39,7 +39,7 @@ public class DiscardClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new DiscardClientHandler(firstMessageSize)); diff --git a/example/src/main/java/io/netty/example/discard/DiscardServer.java b/example/src/main/java/io/netty/example/discard/DiscardServer.java index 2b1678c520..df560a3f26 100644 --- a/example/src/main/java/io/netty/example/discard/DiscardServer.java +++ b/example/src/main/java/io/netty/example/discard/DiscardServer.java @@ -37,7 +37,7 @@ public class DiscardServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new ChannelInitializer() { @Override diff --git a/example/src/main/java/io/netty/example/echo/EchoClient.java b/example/src/main/java/io/netty/example/echo/EchoClient.java index c0853454ae..4d1e924328 100644 --- a/example/src/main/java/io/netty/example/echo/EchoClient.java +++ b/example/src/main/java/io/netty/example/echo/EchoClient.java @@ -50,7 +50,7 @@ public class EchoClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer() { diff --git a/example/src/main/java/io/netty/example/echo/EchoServer.java b/example/src/main/java/io/netty/example/echo/EchoServer.java index 638ba8bb49..d4776638fc 100644 --- a/example/src/main/java/io/netty/example/echo/EchoServer.java +++ b/example/src/main/java/io/netty/example/echo/EchoServer.java @@ -43,7 +43,7 @@ public class EchoServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .localAddress(new InetSocketAddress(port)) .childOption(ChannelOption.TCP_NODELAY, true) diff --git a/example/src/main/java/io/netty/example/factorial/FactorialClient.java b/example/src/main/java/io/netty/example/factorial/FactorialClient.java index 6c17c5ec3a..bb700d5b18 100644 --- a/example/src/main/java/io/netty/example/factorial/FactorialClient.java +++ b/example/src/main/java/io/netty/example/factorial/FactorialClient.java @@ -40,7 +40,7 @@ public class FactorialClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new FactorialClientInitializer(count)); diff --git a/example/src/main/java/io/netty/example/factorial/FactorialServer.java b/example/src/main/java/io/netty/example/factorial/FactorialServer.java index 1c68264ef0..de07935b64 100644 --- a/example/src/main/java/io/netty/example/factorial/FactorialServer.java +++ b/example/src/main/java/io/netty/example/factorial/FactorialServer.java @@ -35,7 +35,7 @@ public class FactorialServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new FactorialServerInitializer()); diff --git a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServer.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServer.java index f228653d83..314c3c99f6 100644 --- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServer.java +++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServer.java @@ -31,7 +31,7 @@ public class HttpStaticFileServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new HttpStaticFileServerInitializer()); diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java index bff8050742..7f54373053 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java @@ -65,7 +65,7 @@ public class HttpSnoopClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .handler(new HttpSnoopClientInitializer(ssl)) .remoteAddress(new InetSocketAddress(host, port)); diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopServer.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopServer.java index aed54afc01..b7b53b6d55 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopServer.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopServer.java @@ -40,7 +40,7 @@ public class HttpSnoopServer { try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .childHandler(new HttpSnoopServerInitializer()) .localAddress(new InetSocketAddress(port)); diff --git a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java index 95f42c9a19..d1d45ba35c 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java @@ -36,7 +36,7 @@ public class AutobahnServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new AutobahnServerInitializer()); diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java index b0d7fe5260..34e28e8617 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java +++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java @@ -84,7 +84,7 @@ public class WebSocketClient { uri, WebSocketVersion.V13, null, false, customHeaders); b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(uri.getHost(), uri.getPort()) .handler(new ChannelInitializer() { @Override diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java index d57a97f29e..e40a8ff6e5 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java @@ -51,7 +51,7 @@ public class WebSocketServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new WebSocketServerInitializer()); diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java index b8a8bb4240..d0d697ed47 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java @@ -50,7 +50,7 @@ public class WebSocketSslServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new WebSocketSslServerInitializer()); diff --git a/example/src/main/java/io/netty/example/localecho/LocalEcho.java b/example/src/main/java/io/netty/example/localecho/LocalEcho.java index 7a1410d73e..c49a31e95d 100644 --- a/example/src/main/java/io/netty/example/localecho/LocalEcho.java +++ b/example/src/main/java/io/netty/example/localecho/LocalEcho.java @@ -50,7 +50,7 @@ public class LocalEcho { // are handled by the same event loop thread which drives a certain socket channel // to reduce the communication latency between socket channels and local channels. sb.group(new LocalEventLoopGroup()) - .channel(new LocalServerChannel()) + .channel(LocalServerChannel.class) .localAddress(addr) .handler(new ChannelInitializer() { @Override @@ -68,7 +68,7 @@ public class LocalEcho { }); cb.group(new NioEventLoopGroup()) // NIO event loops are also OK - .channel(new LocalChannel()) + .channel(LocalChannel.class) .remoteAddress(addr) .handler(new ChannelInitializer() { @Override diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java index 4e2a4ad5a1..18590c147a 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java @@ -46,7 +46,7 @@ public class LocalTimeClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new LocalTimeClientInitializer()); diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeServer.java b/example/src/main/java/io/netty/example/localtime/LocalTimeServer.java index 14411f43fb..bcfebcb490 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeServer.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeServer.java @@ -35,7 +35,7 @@ public class LocalTimeServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new LocalTimeServerInitializer()); diff --git a/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java b/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java index 8ac8a8e712..759055a6b2 100644 --- a/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java +++ b/example/src/main/java/io/netty/example/objectecho/ObjectEchoClient.java @@ -44,7 +44,7 @@ public class ObjectEchoClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new ChannelInitializer() { @Override diff --git a/example/src/main/java/io/netty/example/objectecho/ObjectEchoServer.java b/example/src/main/java/io/netty/example/objectecho/ObjectEchoServer.java index 20adddb6d9..df0c2c2283 100644 --- a/example/src/main/java/io/netty/example/objectecho/ObjectEchoServer.java +++ b/example/src/main/java/io/netty/example/objectecho/ObjectEchoServer.java @@ -40,7 +40,7 @@ public class ObjectEchoServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new ChannelInitializer() { @Override diff --git a/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java b/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java index 6037e83a32..fc6d6b6c39 100644 --- a/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java +++ b/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java @@ -40,7 +40,7 @@ public class PortUnificationServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new ChannelInitializer() { @Override diff --git a/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java b/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java index f5f2e33512..a8b926c6d7 100644 --- a/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java +++ b/example/src/main/java/io/netty/example/proxy/HexDumpProxy.java @@ -40,7 +40,7 @@ public class HexDumpProxy { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(localPort) .childHandler(new HexDumpProxyInitializer(remoteHost, remotePort)); diff --git a/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java b/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java index 49e7925392..f365cec7f2 100644 --- a/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java +++ b/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java @@ -45,7 +45,7 @@ public class HexDumpProxyFrontendHandler extends ChannelInboundByteHandlerAdapte // Start the connection attempt. Bootstrap b = new Bootstrap(); b.group(inboundChannel.eventLoop()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(remoteHost, remotePort) .handler(new HexDumpProxyBackendHandler(inboundChannel)); diff --git a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java index aaeb49217b..2b18435c98 100644 --- a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java +++ b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentClient.java @@ -44,7 +44,7 @@ public class QuoteOfTheMomentClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioDatagramChannel()) + .channel(NioDatagramChannel.class) .localAddress(new InetSocketAddress(0)) .option(ChannelOption.SO_BROADCAST, true) .handler(new QuoteOfTheMomentClientHandler()); diff --git a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentServer.java b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentServer.java index cf9abc02b2..0c67c18007 100644 --- a/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentServer.java +++ b/example/src/main/java/io/netty/example/qotm/QuoteOfTheMomentServer.java @@ -40,7 +40,7 @@ public class QuoteOfTheMomentServer { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioDatagramChannel()) + .channel(NioDatagramChannel.class) .localAddress(new InetSocketAddress(port)) .option(ChannelOption.SO_BROADCAST, true) .handler(new QuoteOfTheMomentServerHandler()); diff --git a/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java b/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java index fa877e8de5..b76371d325 100644 --- a/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java +++ b/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java @@ -15,6 +15,7 @@ */ package io.netty.example.sctp; +import io.netty.bootstrap.AbstractBootstrap; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; @@ -52,7 +53,7 @@ public class NioSctpEchoClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSctpChannel()) + .channel(NioSctpChannel.class) .option(ChannelOption.SCTP_NODELAY, true) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer() { diff --git a/example/src/main/java/io/netty/example/sctp/NioSctpEchoServer.java b/example/src/main/java/io/netty/example/sctp/NioSctpEchoServer.java index 29348a42de..2a6a9ba280 100644 --- a/example/src/main/java/io/netty/example/sctp/NioSctpEchoServer.java +++ b/example/src/main/java/io/netty/example/sctp/NioSctpEchoServer.java @@ -43,7 +43,7 @@ public class NioSctpEchoServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioSctpServerChannel()) + .channel(NioSctpServerChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .localAddress(new InetSocketAddress(port)) .childOption(ChannelOption.SCTP_NODELAY, true) diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatClient.java b/example/src/main/java/io/netty/example/securechat/SecureChatClient.java index 76099b1fab..0c0b2acb25 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatClient.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatClient.java @@ -42,7 +42,7 @@ public class SecureChatClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new SecureChatClientInitializer()); diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatServer.java b/example/src/main/java/io/netty/example/securechat/SecureChatServer.java index 3731eda2df..7de8517004 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatServer.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatServer.java @@ -35,7 +35,7 @@ public class SecureChatServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new SecureChatServerInitializer()); diff --git a/example/src/main/java/io/netty/example/telnet/TelnetClient.java b/example/src/main/java/io/netty/example/telnet/TelnetClient.java index befac65b8a..29e57bad5a 100644 --- a/example/src/main/java/io/netty/example/telnet/TelnetClient.java +++ b/example/src/main/java/io/netty/example/telnet/TelnetClient.java @@ -41,7 +41,7 @@ public class TelnetClient { Bootstrap b = new Bootstrap(); try { b.group(new NioEventLoopGroup()) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new TelnetClientInitializer()); diff --git a/example/src/main/java/io/netty/example/telnet/TelnetServer.java b/example/src/main/java/io/netty/example/telnet/TelnetServer.java index 784260bf6a..49b62a1a32 100644 --- a/example/src/main/java/io/netty/example/telnet/TelnetServer.java +++ b/example/src/main/java/io/netty/example/telnet/TelnetServer.java @@ -34,7 +34,7 @@ public class TelnetServer { ServerBootstrap b = new ServerBootstrap(); try { b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) - .channel(new NioServerSocketChannel()) + .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new TelnetServerPipelineFactory()); diff --git a/example/src/main/java/io/netty/example/uptime/UptimeClient.java b/example/src/main/java/io/netty/example/uptime/UptimeClient.java index ea5c434442..5de19707ae 100644 --- a/example/src/main/java/io/netty/example/uptime/UptimeClient.java +++ b/example/src/main/java/io/netty/example/uptime/UptimeClient.java @@ -59,7 +59,7 @@ public class UptimeClient { Bootstrap configureBootstrap(Bootstrap b, EventLoopGroup g) { b.group(g) - .channel(new NioSocketChannel()) + .channel(NioSocketChannel.class) .remoteAddress(host, port) .handler(new ChannelInitializer() { @Override diff --git a/handler/pom.xml b/handler/pom.xml index ab6c19f39e..b1f902e878 100644 --- a/handler/pom.xml +++ b/handler/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-handler diff --git a/pom.xml b/pom.xml index c2214cc9a5..20cbf96dc6 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ io.netty netty-parent pom - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT Netty http://netty.io/ diff --git a/tarball/pom.xml b/tarball/pom.xml index e33a9a6405..880371f9aa 100644 --- a/tarball/pom.xml +++ b/tarball/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-tarball diff --git a/testsuite/pom.xml b/testsuite/pom.xml index e68cc22de5..bd7b5e5c23 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-testsuite diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java index 69403301cf..53cdc3665b 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java @@ -15,6 +15,7 @@ */ package io.netty.testsuite.transport.socket; +import io.netty.bootstrap.AbstractBootstrap; import io.netty.bootstrap.Bootstrap; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; @@ -59,7 +60,7 @@ public abstract class AbstractDatagramTest { "Running: %s %d of %d", testName.getMethodName(), ++ i, COMBO.size())); try { Method m = getClass().getDeclaredMethod( - testName.getMethodName(), Bootstrap.class, Bootstrap.class); + testName.getMethodName(), AbstractBootstrap.class, AbstractBootstrap.class); m.invoke(this, sb, cb); } catch (InvocationTargetException ex) { throw ex.getCause(); diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java index b37ddbfd3b..79489ea71f 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java @@ -16,7 +16,7 @@ package io.netty.testsuite.transport.socket; import static org.junit.Assert.*; -import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.AbstractBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; @@ -40,7 +40,7 @@ public class DatagramMulticastTest extends AbstractDatagramTest { run(); } - public void testMulticast(Bootstrap sb, Bootstrap cb) throws Throwable { + public void testMulticast(AbstractBootstrap sb, AbstractBootstrap cb) throws Throwable { MulticastTestHandler mhandler = new MulticastTestHandler(); sb.handler(new ChannelInboundMessageHandlerAdapter() { diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java index 3388d9d9ba..e8603816aa 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java @@ -16,7 +16,7 @@ package io.netty.testsuite.transport.socket; import static org.junit.Assert.*; -import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.AbstractBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; @@ -36,7 +36,7 @@ public class DatagramUnicastTest extends AbstractDatagramTest { run(); } - public void testSimpleSend(Bootstrap sb, Bootstrap cb) throws Throwable { + public void testSimpleSend(AbstractBootstrap sb, AbstractBootstrap cb) throws Throwable { final CountDownLatch latch = new CountDownLatch(1); sb.handler(new ChannelInboundMessageHandlerAdapter() { diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java index 9ec4d6e81c..ab35cb658c 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java @@ -15,8 +15,10 @@ */ package io.netty.testsuite.transport.socket; +import io.netty.bootstrap.AbstractBootstrap.ChannelFactory; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.aio.AioEventLoopGroup; import io.netty.channel.socket.aio.AioServerSocketChannel; @@ -86,14 +88,18 @@ final class SocketTestPermutation { bfs.add(new Factory() { @Override public Bootstrap newInstance() { - return new Bootstrap().group(new NioEventLoopGroup()).channel( - new NioDatagramChannel(InternetProtocolFamily.IPv4)); + return new Bootstrap().group(new NioEventLoopGroup()).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new NioDatagramChannel(InternetProtocolFamily.IPv4); + } + }); } }); bfs.add(new Factory() { @Override public Bootstrap newInstance() { - return new Bootstrap().group(new OioEventLoopGroup()).channel(new OioDatagramChannel()); + return new Bootstrap().group(new OioEventLoopGroup()).channel(OioDatagramChannel.class); } }); @@ -133,17 +139,21 @@ final class SocketTestPermutation { public ServerBootstrap newInstance() { return new ServerBootstrap(). group(new NioEventLoopGroup(), new NioEventLoopGroup()). - channel(new NioServerSocketChannel()); + channel(NioServerSocketChannel.class); } }); list.add(new Factory() { @Override public ServerBootstrap newInstance() { - AioEventLoopGroup parentGroup = new AioEventLoopGroup(); - AioEventLoopGroup childGroup = new AioEventLoopGroup(); - return new ServerBootstrap(). - group(parentGroup, childGroup). - channel(new AioServerSocketChannel(parentGroup, childGroup)); + final AioEventLoopGroup parentGroup = new AioEventLoopGroup(); + final AioEventLoopGroup childGroup = new AioEventLoopGroup(); + return new ServerBootstrap().group(parentGroup, childGroup).channelFactory(new ChannelFactory() { + + @Override + public Channel newChannel() { + return new AioServerSocketChannel(parentGroup, childGroup); + } + }); } }); list.add(new Factory() { @@ -151,7 +161,7 @@ final class SocketTestPermutation { public ServerBootstrap newInstance() { return new ServerBootstrap(). group(new OioEventLoopGroup(), new OioEventLoopGroup()). - channel(new OioServerSocketChannel()); + channel(OioServerSocketChannel.class); } }); @@ -163,20 +173,25 @@ final class SocketTestPermutation { list.add(new Factory() { @Override public Bootstrap newInstance() { - return new Bootstrap().group(new NioEventLoopGroup()).channel(new NioSocketChannel()); + return new Bootstrap().group(new NioEventLoopGroup()).channel(NioSocketChannel.class); } }); list.add(new Factory() { @Override public Bootstrap newInstance() { - AioEventLoopGroup loop = new AioEventLoopGroup(); - return new Bootstrap().group(loop).channel(new AioSocketChannel(loop)); + final AioEventLoopGroup loop = new AioEventLoopGroup(); + return new Bootstrap().group(loop).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new AioSocketChannel(loop); + } + }); } }); list.add(new Factory() { @Override public Bootstrap newInstance() { - return new Bootstrap().group(new OioEventLoopGroup()).channel(new OioSocketChannel()); + return new Bootstrap().group(new OioEventLoopGroup()).channel(OioSocketChannel.class); } }); return list; diff --git a/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java b/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java index 6aa963d130..33c8242c01 100644 --- a/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java +++ b/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java @@ -20,8 +20,8 @@ import java.net.ServerSocket; public class TestUtils { - private final static int START_PORT = 20000; - private final static int END_PORT = 30000; + private static int START_PORT = 20000; + private static int END_PORT = 30000; /** * Return a free port which can be used to bind to @@ -34,6 +34,7 @@ public class TestUtils { ServerSocket socket = new ServerSocket(start); socket.setReuseAddress(true); socket.close(); + START_PORT = start + 1; return start; } catch (IOException e) { // ignore diff --git a/transport/pom.xml b/transport/pom.xml index 296aec1a68..e04bb88f74 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha4-SNAPSHOT + 4.0.0.Alpha5-SNAPSHOT netty-transport diff --git a/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java new file mode 100644 index 0000000000..b2b5dd2e45 --- /dev/null +++ b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java @@ -0,0 +1,256 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.bootstrap; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelException; + +/** + * {@link AbstractBootstrap} is a helper class that makes it easy to bootstrap a {@link Channel}. It support + * method-chaining to provide an easy way to configure the {@link AbstractBootstrap}. + * + */ +public abstract class AbstractBootstrap> { + private EventLoopGroup group; + private ChannelFactory factory; + private SocketAddress localAddress; + private final Map, Object> options = new LinkedHashMap, Object>(); + private ChannelHandler handler; + + /** + * The {@link EventLoopGroup} which is used to handle all the events for the to-be-creates + * {@link Channel} + */ + @SuppressWarnings("unchecked") + public B group(EventLoopGroup group) { + if (group == null) { + throw new NullPointerException("group"); + } + if (this.group != null) { + throw new IllegalStateException("group set already"); + } + this.group = group; + return (B) this; + } + + /** + * The {@link Class} which is used to create {@link Channel} instances from. + * You either use this or {@link #channelFactory(ChannelFactory)} if your + * {@link Channel} implementation has no no-args constructor. + */ + public B channel(Class channelClass) { + if (channelClass == null) { + throw new NullPointerException("channelClass"); + } + return channelFactory(new BootstrapChannelFactory(channelClass)); + } + + /** + * {@link ChannelFactory} which is used to create {@link Channel} instances from + * when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)} + * is not working for you because of some more complex needs. If your {@link Channel} implementation + * has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for + * simplify your code. + */ + @SuppressWarnings("unchecked") + public B channelFactory(ChannelFactory factory) { + if (factory == null) { + throw new NullPointerException("factory"); + } + if (this.factory != null) { + throw new IllegalStateException("factory set already"); + } + + this.factory = factory; + return (B) this; + } + + /** + * The {@link SocketAddress} which is used to bind the local "end" to. + * + */ + @SuppressWarnings("unchecked") + public B localAddress(SocketAddress localAddress) { + this.localAddress = localAddress; + return (B) this; + } + + /** + * See {@link #localAddress(SocketAddress)} + */ + public B localAddress(int port) { + return localAddress(new InetSocketAddress(port)); + } + + /** + * See {@link #localAddress(SocketAddress)} + */ + public B localAddress(String host, int port) { + return localAddress(new InetSocketAddress(host, port)); + } + + /** + * See {@link #localAddress(SocketAddress)} + */ + public B localAddress(InetAddress host, int port) { + return localAddress(new InetSocketAddress(host, port)); + } + + /** + * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got + * created. Use a value of null to remove a previous set {@link ChannelOption}. + */ + @SuppressWarnings("unchecked") + public B option(ChannelOption option, T value) { + if (option == null) { + throw new NullPointerException("option"); + } + if (value == null) { + options.remove(option); + } else { + options.put(option, value); + } + return (B) this; + } + + /** + * Shutdown the {@link AbstractBootstrap} and the {@link EventLoopGroup} which is + * used by it. Only call this if you don't share the {@link EventLoopGroup} + * between different {@link AbstractBootstrap}'s. + */ + public void shutdown() { + if (group != null) { + group.shutdown(); + } + } + + /** + * Validate all the parameters. Sub-classes may override this, but should + * call the super method in that case. + */ + protected void validate() { + if (group == null) { + throw new IllegalStateException("group not set"); + } + if (factory == null) { + throw new IllegalStateException("factory not set"); + } + } + + protected final void validate(ChannelFuture future) { + if (future == null) { + throw new NullPointerException("future"); + } + validate(); + } + + /** + * Create a new {@link Channel} and bind it. + */ + public ChannelFuture bind() { + validate(); + Channel channel = factory().newChannel(); + return bind(channel.newFuture()); + } + + /** + * the {@link ChannelHandler} to use for serving the requests. + */ + @SuppressWarnings("unchecked") + public B handler(ChannelHandler handler) { + if (handler == null) { + throw new NullPointerException("handler"); + } + this.handler = handler; + return (B) this; + } + + protected static boolean ensureOpen(ChannelFuture future) { + if (!future.channel().isOpen()) { + // Registration was successful but the channel was closed due to some failure in + // handler. + future.setFailure(new ChannelException("initialization failure")); + return false; + } + return true; + } + + /** + * Bind the {@link Channel} of the given {@link ChannelFactory}. + */ + public abstract ChannelFuture bind(ChannelFuture future); + + protected final SocketAddress localAddress() { + return localAddress; + } + + protected final ChannelFactory factory() { + return factory; + } + + protected final ChannelHandler handler() { + return handler; + } + + protected final EventLoopGroup group() { + return group; + } + + protected final Map, Object> options() { + return options; + } + + private final class BootstrapChannelFactory implements ChannelFactory { + private final Class clazz; + + BootstrapChannelFactory(Class clazz) { + this.clazz = clazz; + } + + @Override + public Channel newChannel() { + try { + return clazz.newInstance(); + } catch (Throwable t) { + throw new ChannelException("Unable to create Channel from class " + clazz, t); + } + } + + } + + /** + * Factory that is responsible to create new {@link Channel}'s on {@link AbstractBootstrap#bind()} + * requests. + * + */ + public interface ChannelFactory { + /** + * {@link Channel} to use in the {@link AbstractBootstrap} + */ + Channel newChannel(); + } +} diff --git a/transport/src/main/java/io/netty/bootstrap/Bootstrap.java b/transport/src/main/java/io/netty/bootstrap/Bootstrap.java index 501ec522f2..a466274120 100644 --- a/transport/src/main/java/io/netty/bootstrap/Bootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/Bootstrap.java @@ -16,13 +16,11 @@ package io.netty.bootstrap; import io.netty.channel.Channel; -import io.netty.channel.ChannelException; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; @@ -30,111 +28,53 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Map.Entry; -public class Bootstrap { +/** + * A {@link Bootstrap} that makes it easy to bootstrap a {@link Channel} to use + * for clients. + * + */ +public class Bootstrap extends AbstractBootstrap { private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class); - - private final Map, Object> options = new LinkedHashMap, Object>(); - private EventLoopGroup group; - private Channel channel; - private ChannelHandler handler; - private SocketAddress localAddress; private SocketAddress remoteAddress; - public Bootstrap group(EventLoopGroup group) { - if (group == null) { - throw new NullPointerException("group"); - } - if (this.group != null) { - throw new IllegalStateException("group set already"); - } - this.group = group; - return this; - } - - public Bootstrap channel(Channel channel) { - if (channel == null) { - throw new NullPointerException("channel"); - } - if (this.channel != null) { - throw new IllegalStateException("channel set already"); - } - this.channel = channel; - return this; - } - - public Bootstrap option(ChannelOption option, T value) { - if (option == null) { - throw new NullPointerException("option"); - } - if (value == null) { - options.remove(option); - } else { - options.put(option, value); - } - return this; - } - - public Bootstrap handler(ChannelHandler handler) { - if (handler == null) { - throw new NullPointerException("handler"); - } - this.handler = handler; - return this; - } - - public Bootstrap localAddress(SocketAddress localAddress) { - this.localAddress = localAddress; - return this; - } - - public Bootstrap localAddress(int port) { - localAddress = new InetSocketAddress(port); - return this; - } - - public Bootstrap localAddress(String host, int port) { - localAddress = new InetSocketAddress(host, port); - return this; - } - - public Bootstrap localAddress(InetAddress host, int port) { - localAddress = new InetSocketAddress(host, port); - return this; - } + /** + * The {@link SocketAddress} to connect to once the {@link #connect()} method + * is called. + */ public Bootstrap remoteAddress(SocketAddress remoteAddress) { this.remoteAddress = remoteAddress; return this; } + /** + * See {@link #remoteAddress(SocketAddress)} + */ public Bootstrap remoteAddress(String host, int port) { remoteAddress = new InetSocketAddress(host, port); return this; } + /** + * See {@link #remoteAddress(SocketAddress)} + */ public Bootstrap remoteAddress(InetAddress host, int port) { remoteAddress = new InetSocketAddress(host, port); return this; } - public ChannelFuture bind() { - validate(); - return bind(channel.newFuture()); - } - + @Override public ChannelFuture bind(ChannelFuture future) { validate(future); - if (localAddress == null) { + if (localAddress() == null) { throw new IllegalStateException("localAddress not set"); } try { - init(); + init(future.channel()); } catch (Throwable t) { future.setFailure(t); return future; @@ -144,14 +84,21 @@ public class Bootstrap { return future; } - return channel.bind(localAddress, future).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + return future.channel().bind(localAddress(), future).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } + /** + * Connect a {@link Channel} to the remote peer. + */ public ChannelFuture connect() { validate(); + Channel channel = factory().newChannel(); return connect(channel.newFuture()); } + /** + * See {@link #connect()} + */ public ChannelFuture connect(ChannelFuture future) { validate(future); if (remoteAddress == null) { @@ -159,7 +106,7 @@ public class Bootstrap { } try { - init(); + init(future.channel()); } catch (Throwable t) { future.setFailure(t); return future; @@ -169,15 +116,16 @@ public class Bootstrap { return future; } - if (localAddress == null) { - channel.connect(remoteAddress, future); + if (localAddress() == null) { + future.channel().connect(remoteAddress, future); } else { - channel.connect(remoteAddress, localAddress, future); + future.channel().connect(remoteAddress, localAddress(), future); } return future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } - private void init() throws Exception { + @SuppressWarnings("unchecked") + private void init(Channel channel) throws Exception { if (channel.isActive()) { throw new IllegalStateException("channel already active:: " + channel); } @@ -189,9 +137,9 @@ public class Bootstrap { } ChannelPipeline p = channel.pipeline(); - p.addLast(handler); + p.addLast(handler()); - for (Entry, Object> e: options.entrySet()) { + for (Entry, Object> e: options().entrySet()) { try { if (!channel.config().setOption((ChannelOption) e.getKey(), e.getValue())) { logger.warn("Unknown channel option: " + e); @@ -201,45 +149,34 @@ public class Bootstrap { } } - group.register(channel).syncUninterruptibly(); + group().register(channel).syncUninterruptibly(); } - private static boolean ensureOpen(ChannelFuture future) { - if (!future.channel().isOpen()) { - // Registration was successful but the channel was closed due to some failure in - // handler. - future.setFailure(new ChannelException("initialization failure")); - return false; - } - return true; - } - - public void shutdown() { - if (group != null) { - group.shutdown(); - } - } - - private void validate() { - if (group == null) { - throw new IllegalStateException("group not set"); - } - if (channel == null) { - throw new IllegalStateException("channel not set"); - } - if (handler == null) { + @Override + protected void validate() { + super.validate(); + if (handler() == null) { throw new IllegalStateException("handler not set"); } } - private void validate(ChannelFuture future) { - if (future == null) { - throw new NullPointerException("future"); - } - - if (future.channel() != channel) { - throw new IllegalArgumentException("future.channel() must be the same channel."); - } + /** + * Create a new {@link Bootstrap} using this "full-setup" {@link Bootstrap} as template. + * Only the given parameters are replaced, the rest is configured exactly the same way as the template. + */ + public Bootstrap newBootstrap(SocketAddress localAddress, SocketAddress remoteAddress, ChannelHandler handler) { validate(); + Bootstrap cb = new Bootstrap().handler(handler).channelFactory(factory()).group(group()) + .localAddress(localAddress).remoteAddress(remoteAddress); + cb.options().putAll(options()); + return cb; + } + + /** + * Create a new {@link Bootstrap} using this "full-setup" {@link Bootstrap} as template. + * Only the given parameters are replaced, the rest is configured exactly the same way as the template. + */ + public Bootstrap newBootstrap(SocketAddress localAddress, SocketAddress remoteAddress) { + return newBootstrap(localAddress, remoteAddress, handler()); } } diff --git a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java index 3b86b808c8..c57b4b2afc 100644 --- a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java @@ -17,32 +17,35 @@ package io.netty.bootstrap; import io.netty.buffer.MessageBuf; import io.netty.buffer.Unpooled; + import io.netty.channel.Channel; -import io.netty.channel.ChannelException; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInboundMessageHandler; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; +import io.netty.channel.socket.SocketChannel; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import io.netty.util.NetworkConstants; -import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; -public class ServerBootstrap { +/** + * {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel} + * + */ +public class ServerBootstrap extends AbstractBootstrap { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class); private static final InetSocketAddress DEFAULT_LOCAL_ADDR = new InetSocketAddress(NetworkConstants.LOCALHOST, 0); @@ -54,62 +57,55 @@ public class ServerBootstrap { } }; - private final Map, Object> parentOptions = new LinkedHashMap, Object>(); private final Map, Object> childOptions = new LinkedHashMap, Object>(); - private EventLoopGroup parentGroup; private EventLoopGroup childGroup; - private ServerChannel channel; private ChannelHandler handler; private ChannelHandler childHandler; - private SocketAddress localAddress; + /** + * Specify the {@link EventLoopGroup} which is used for the parent (acceptor) and the child (client). + */ + @Override public ServerBootstrap group(EventLoopGroup group) { - if (group == null) { - throw new NullPointerException("group"); - } - if (parentGroup != null) { - throw new IllegalStateException("parentGroup set already"); - } - parentGroup = group; - childGroup = group; - return this; + return group(group, group); } + /** + * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These + * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link SocketChannel} and + * {@link Channel}'s. + */ public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { - if (parentGroup == null) { - throw new NullPointerException("parentGroup"); + super.group(parentGroup); + if (childGroup == null) { + throw new NullPointerException("childGroup"); } - if (this.parentGroup != null) { - throw new IllegalStateException("parentGroup set already"); + if (this.childGroup != null) { + throw new IllegalStateException("childGroup set already"); } - this.parentGroup = parentGroup; this.childGroup = childGroup; return this; } - public ServerBootstrap channel(ServerChannel channel) { - if (channel == null) { - throw new NullPointerException("channel"); + /** + * The {@link Class} which is used to create the {@link ServerChannel} from (for the acceptor). + */ + @Override + public ServerBootstrap channel(Class channelClass) { + if (channelClass == null) { + throw new NullPointerException("channelClass"); } - if (this.channel != null) { - throw new IllegalStateException("channel set already"); + if (!ServerChannel.class.isAssignableFrom(channelClass)) { + throw new IllegalArgumentException(); } - this.channel = channel; - return this; - } - - public ServerBootstrap option(ChannelOption parentOption, T value) { - if (parentOption == null) { - throw new NullPointerException("parentOption"); - } - if (value == null) { - parentOptions.remove(parentOption); - } else { - parentOptions.put(parentOption, value); - } - return this; + return super.channel(channelClass); } + /** + * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created + * (after the acceptor accepted the {@link Channel}). Use a value of null to remove a previous set + * {@link ChannelOption}. + */ public ServerBootstrap childOption(ChannelOption childOption, T value) { if (childOption == null) { throw new NullPointerException("childOption"); @@ -122,11 +118,9 @@ public class ServerBootstrap { return this; } - public ServerBootstrap handler(ChannelHandler handler) { - this.handler = handler; - return this; - } - + /** + * Set the {@link ChannelHandler} which is used to server the request for the {@link Channel}'s. + */ public ServerBootstrap childHandler(ChannelHandler childHandler) { if (childHandler == null) { throw new NullPointerException("childHandler"); @@ -135,36 +129,10 @@ public class ServerBootstrap { return this; } - public ServerBootstrap localAddress(SocketAddress localAddress) { - if (localAddress == null) { - throw new NullPointerException("localAddress"); - } - this.localAddress = localAddress; - return this; - } - - public ServerBootstrap localAddress(int port) { - localAddress = new InetSocketAddress(port); - return this; - } - - public ServerBootstrap localAddress(String host, int port) { - localAddress = new InetSocketAddress(host, port); - return this; - } - - public ServerBootstrap localAddress(InetAddress host, int port) { - localAddress = new InetSocketAddress(host, port); - return this; - } - - public ChannelFuture bind() { - validate(); - return bind(channel.newFuture()); - } - + @Override public ChannelFuture bind(ChannelFuture future) { validate(future); + Channel channel = future.channel(); if (channel.isActive()) { future.setFailure(new IllegalStateException("channel already bound: " + channel)); return future; @@ -179,75 +147,57 @@ public class ServerBootstrap { } try { - channel.config().setOptions(parentOptions); + channel.config().setOptions(options()); } catch (Exception e) { future.setFailure(e); return future; } - ChannelPipeline p = channel.pipeline(); + ChannelPipeline p = future.channel().pipeline(); if (handler != null) { p.addLast(handler); } p.addLast(acceptor); - ChannelFuture f = parentGroup.register(channel).awaitUninterruptibly(); + ChannelFuture f = group().register(channel).awaitUninterruptibly(); if (!f.isSuccess()) { future.setFailure(f.cause()); return future; } - if (!channel.isOpen()) { - // Registration was successful but the channel was closed due to some failure in - // handler. - future.setFailure(new ChannelException("initialization failure")); + if (!ensureOpen(future)) { return future; } - channel.bind(localAddress, future).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + channel.bind(localAddress(), future).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); return future; } + @Override public void shutdown() { - if (parentGroup != null) { - parentGroup.shutdown(); - } + super.shutdown(); if (childGroup != null) { childGroup.shutdown(); } } - private void validate() { - if (parentGroup == null) { - throw new IllegalStateException("parentGroup not set"); - } - if (channel == null) { - throw new IllegalStateException("channel not set"); - } + @Override + protected void validate() { + super.validate(); if (childHandler == null) { throw new IllegalStateException("childHandler not set"); } if (childGroup == null) { logger.warn("childGroup is not set. Using parentGroup instead."); - childGroup = parentGroup; + childGroup = group(); } - if (localAddress == null) { + if (localAddress() == null) { logger.warn("localAddress is not set. Using " + DEFAULT_LOCAL_ADDR + " instead."); - localAddress = DEFAULT_LOCAL_ADDR; + localAddress(DEFAULT_LOCAL_ADDR); } } - private void validate(ChannelFuture future) { - if (future == null) { - throw new NullPointerException("future"); - } - - if (future.channel() != channel) { - throw new IllegalArgumentException("future.channel() must be the same channel."); - } - validate(); - } private class Acceptor extends ChannelInboundHandlerAdapter implements ChannelInboundMessageHandler { @@ -257,6 +207,7 @@ public class ServerBootstrap { return Unpooled.messageBuffer(); } + @SuppressWarnings("unchecked") @Override public void inboundBufferUpdated(ChannelHandlerContext ctx) { MessageBuf in = ctx.inboundMessageBuffer(); diff --git a/transport/src/main/java/io/netty/channel/ChannelFuture.java b/transport/src/main/java/io/netty/channel/ChannelFuture.java index 98dca98a07..a464d04bcb 100644 --- a/transport/src/main/java/io/netty/channel/ChannelFuture.java +++ b/transport/src/main/java/io/netty/channel/ChannelFuture.java @@ -129,7 +129,7 @@ import java.util.concurrent.TimeUnit; * connect timeout should be configured via a transport-specific option: *
  * // BAD - NEVER DO THIS
- * {@link ClientBootstrap} b = ...;
+ * {@link Bootstrap} b = ...;
  * {@link ChannelFuture} f = b.connect(...);
  * f.awaitUninterruptibly(10, TimeUnit.SECONDS);
  * if (f.isCancelled()) {
@@ -143,7 +143,7 @@ import java.util.concurrent.TimeUnit;
  * }
  *
  * // GOOD
- * {@link ClientBootstrap} b = ...;
+ * {@link Bootstrap} b = ...;
  * // Configure the connect timeout option.
  * b.setOption("connectTimeoutMillis", 10000);
  * {@link ChannelFuture} f = b.connect(...);
diff --git a/transport/src/main/java/io/netty/channel/ChannelHandler.java b/transport/src/main/java/io/netty/channel/ChannelHandler.java
index 358c413d90..436b0f18b8 100644
--- a/transport/src/main/java/io/netty/channel/ChannelHandler.java
+++ b/transport/src/main/java/io/netty/channel/ChannelHandler.java
@@ -15,7 +15,6 @@
  */
 package io.netty.channel;
 
-import io.netty.bootstrap.Bootstrap;
 import io.netty.channel.group.ChannelGroup;
 
 import java.lang.annotation.Documented;
@@ -87,7 +86,7 @@ import java.nio.channels.Channels;
  * the confidential information:
  * 
  * // Create a new handler instance per channel.
- * // See {@link Bootstrap#setPipelineFactory(ChannelPipelineFactory)}.
+ * // See {@link ClientBootstrap#setPipelineFactory(ChannelPipelineFactory)}.
  * public class DataServerPipelineFactory implements {@link ChannelPipelineFactory} {
  *     public {@link ChannelPipeline} getPipeline() {
  *         return {@link Channels}.pipeline(new DataServerHandler());
diff --git a/transport/src/main/java/io/netty/channel/socket/SctpChannel.java b/transport/src/main/java/io/netty/channel/socket/SctpChannel.java
index 0fab200cdc..89015cb443 100644
--- a/transport/src/main/java/io/netty/channel/socket/SctpChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/SctpChannel.java
@@ -19,9 +19,7 @@ import com.sun.nio.sctp.Association;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
 
-import java.io.IOException;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.util.Set;
 
@@ -72,10 +70,21 @@ public interface SctpChannel extends Channel {
     @Override
     SocketAddress remoteAddress();
 
-
     /**
      * Return all remote addresses of the SCTP server channel.
      * Please note that, it will return more than one address if the remote is using multi-homing.
      */
     Set allRemoteAddresses();
+
+    /**
+     * Bind a address to the already bound channel to enable multi-homing.
+     * The Channel bust be bound and yet to be connected.
+     */
+    ChannelFuture bindAddress(InetAddress localAddress);
+
+    /**
+     *  Unbind the address from channel's multi-homing address list.
+     *  The address should be added already in multi-homing address list.
+     */
+    ChannelFuture unbindAddress(InetAddress localAddress);
 }
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java b/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java
index e8c643b224..113db6d8b9 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java
@@ -253,6 +253,9 @@ final class NioEventLoop extends SingleThreadEventLoop {
 
     private void processSelectedKeys() {
         Set selectedKeys = selector.selectedKeys();
+        // check if the set is empty and if so just return to not create garbage by
+        // creating a new Iterator every time even if there is nothing to process.
+        // See https://github.com/netty/netty/issues/597
         if (selectedKeys.isEmpty()) {
             return;
         }
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioSctpChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioSctpChannel.java
index 9b4c8f08c8..1efe2ff586 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioSctpChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioSctpChannel.java
@@ -25,6 +25,7 @@ import io.netty.buffer.MessageBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelException;
+import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelMetadata;
 import io.netty.channel.socket.DefaultSctpChannelConfig;
 import io.netty.channel.socket.SctpChannelConfig;
@@ -34,6 +35,7 @@ import io.netty.logging.InternalLogger;
 import io.netty.logging.InternalLoggerFactory;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.SelectionKey;
@@ -48,6 +50,7 @@ public class NioSctpChannel extends AbstractNioMessageChannel implements io.nett
 
     private final SctpChannelConfig config;
 
+    @SuppressWarnings("rawtypes")
     private final NotificationHandler notificationHandler;
 
     private static SctpChannel newSctpChannel() {
@@ -215,6 +218,7 @@ public class NioSctpChannel extends AbstractNioMessageChannel implements io.nett
         javaChannel().close();
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     protected int doReadMessages(MessageBuf buf) throws Exception {
         SctpChannel ch = javaChannel();
@@ -276,4 +280,59 @@ public class NioSctpChannel extends AbstractNioMessageChannel implements io.nett
         }
         return 1;
     }
+
+    @Override
+    public ChannelFuture bindAddress(InetAddress localAddress) {
+        ChannelFuture future = newFuture();
+        doBindAddress(localAddress, future);
+        return future;
+    }
+
+    void doBindAddress(final InetAddress localAddress, final ChannelFuture future) {
+        if (eventLoop().inEventLoop()) {
+            try {
+                javaChannel().bindAddress(localAddress);
+                future.setSuccess();
+                // TODO: Do we want to fire an event ?
+            } catch (Throwable t) {
+                future.setFailure(t);
+                pipeline().fireExceptionCaught(t);
+            }
+        } else {
+            eventLoop().execute(new Runnable() {
+                @Override
+                public void run() {
+                    doBindAddress(localAddress, future);
+                }
+            });
+        }
+    }
+
+    @Override
+    public ChannelFuture unbindAddress(InetAddress localAddress) {
+        ChannelFuture future = newFuture();
+        doUnbindAddress(localAddress, future);
+        return future;
+    }
+
+    void doUnbindAddress(final InetAddress localAddress, final ChannelFuture future) {
+        if (eventLoop().inEventLoop()) {
+            try {
+                javaChannel().unbindAddress(localAddress);
+                future.setSuccess();
+                // TODO: Do we want to fire an event ?
+            } catch (Throwable t) {
+                future.setFailure(t);
+                pipeline().fireExceptionCaught(t);
+            }
+        } else {
+            eventLoop().execute(new Runnable() {
+                @Override
+                public void run() {
+                    doUnbindAddress(localAddress, future);
+                }
+            });
+        }
+    }
+
 }
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java
index 6c45ad8fcb..dca7040bc6 100755
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java
@@ -205,7 +205,10 @@ public class OioDatagramChannel extends AbstractOioMessageChannel
         DatagramPacket p = (DatagramPacket) buf.poll();
         ByteBuf data = p.data();
         int length = data.readableBytes();
-        tmpPacket.setSocketAddress(p.remoteAddress());
+        InetSocketAddress remote = p.remoteAddress();
+        if (remote != null) {
+            tmpPacket.setSocketAddress(remote);
+        }
         if (data.hasArray()) {
             tmpPacket.setData(data.array(), data.arrayOffset() + data.readerIndex(), length);
         } else {
diff --git a/transport/src/test/java/io/netty/channel/local/LocalChannelRegistryTest.java b/transport/src/test/java/io/netty/channel/local/LocalChannelRegistryTest.java
index b7eef46d2d..c617844f7e 100644
--- a/transport/src/test/java/io/netty/channel/local/LocalChannelRegistryTest.java
+++ b/transport/src/test/java/io/netty/channel/local/LocalChannelRegistryTest.java
@@ -43,12 +43,12 @@ public class LocalChannelRegistryTest {
             ServerBootstrap sb = new ServerBootstrap();
 
             cb.group(new LocalEventLoopGroup())
-              .channel(new LocalChannel())
+              .channel(LocalChannel.class)
               .remoteAddress(addr)
               .handler(new TestHandler());
 
             sb.group(new LocalEventLoopGroup())
-              .channel(new LocalServerChannel())
+              .channel(LocalServerChannel.class)
               .localAddress(addr)
               .childHandler(new ChannelInitializer() {
                   @Override
diff --git a/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java b/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java
index 6e5c339ccc..84ebb499c9 100644
--- a/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java
+++ b/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java
@@ -58,7 +58,7 @@ public class LocalTransportThreadModelTest {
         // Configure a test server
         sb = new ServerBootstrap();
         sb.group(new LocalEventLoopGroup())
-          .channel(new LocalServerChannel())
+          .channel(LocalServerChannel.class)
           .localAddress(LocalAddress.ANY)
           .childHandler(new ChannelInitializer() {
               @Override