/** * Copyright 2015 Santhosh Kumar Tekuri * * The JLibs authors license this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package jlibs.wamp4j.netty; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.websocketx.*; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import jlibs.wamp4j.Util; import jlibs.wamp4j.spi.ConnectListener; import jlibs.wamp4j.spi.NamedThreadFactory; import jlibs.wamp4j.spi.WAMPClientEndpoint; import java.net.URI; /** * @author Santhosh Kumar Tekuri */ public class NettyClientEndpoint extends NettyEndpoint implements WAMPClientEndpoint{ public NettyClientEndpoint(){ super(NamedThreadFactory.CLIENT_THREAD_FACTORY); } @Override public void connect(final URI uri, final ConnectListener listener, final String... subProtocols){ final SslContext sslContext; if("wss".equals(uri.getScheme())){ try{ if(sslSettings==null){ sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); }else{ sslContext = SslContextBuilder.forClient() .trustManager(sslSettings.trustCertChainFile) .keyManager(sslSettings.certificateFile, sslSettings.keyFile, sslSettings.keyPassword) .build(); } }catch(Throwable thr){ listener.onError(thr); return; } }else if("ws".equals(uri.getScheme())) sslContext = null; else throw new IllegalArgumentException("invalid protocol: "+uri.getScheme()); final int port = uri.getPort()==-1 ? (sslContext==null ? 80 : 443) : uri.getPort(); Bootstrap bootstrap = new Bootstrap() .group(eventLoopGroup) .channel(NioSocketChannel.class) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .option(ChannelOption.MAX_MESSAGES_PER_READ, 50000) .option(ChannelOption.WRITE_SPIN_COUNT, 50000) .handler(new ChannelInitializer<SocketChannel>(){ @Override protected void initChannel(SocketChannel ch) throws Exception{ if(sslContext!=null) ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), uri.getHost(), port)); WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, Util.toString(subProtocols), false, new DefaultHttpHeaders()); ch.pipeline().addLast( new HttpClientCodec(), new HttpObjectAggregator(8192), new WebSocketClientProtocolHandler(handshaker){ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception{ super.exceptionCaught(ctx, cause); listener.onError(cause); } }, new HandshakeListener(handshaker, listener) ); } }); bootstrap.connect(uri.getHost(), port).addListener(new ChannelFutureListener(){ @Override public void operationComplete(ChannelFuture future) throws Exception{ if(!future.isSuccess()){ assert !future.channel().isOpen(); listener.onError(future.cause()); } } }); } private static class HandshakeListener extends SimpleChannelInboundHandler<Object>{ private final WebSocketClientHandshaker handshaker; private final ConnectListener connectListener; public HandshakeListener(WebSocketClientHandshaker handshaker, ConnectListener connectListener){ this.handshaker = handshaker; this.connectListener = connectListener; } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg){} @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception{ if(handshaker.isHandshakeComplete()) super.exceptionCaught(ctx, cause); else{ connectListener.onError(cause); ctx.close(); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{ if(evt == WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE){ NettyWebSocket webSocket = new NettyWebSocket(null, handshaker.actualSubprotocol()); ctx.pipeline().addLast("ws-aggregator", new WebSocketFrameAggregator(16 * 1024 * 1024)); // 16MB ctx.pipeline().addLast("websocket", webSocket); ctx.pipeline().remove(this); webSocket.channelActive(ctx); connectListener.onConnect(webSocket); }else ctx.fireUserEventTriggered(evt); } } }