All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.nls.client.transport.netty4.NettyWebSocketClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Alibaba Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.nls.client.transport.netty4;

import java.net.URI;
import java.util.concurrent.TimeUnit;

import com.alibaba.nls.client.protocol.Constant;
import com.alibaba.nls.client.transport.Connection;
import com.alibaba.nls.client.transport.ConnectionListener;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
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.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author zhishen.ml
 * @since 2017/11/27
 *
 */
public final class NettyWebSocketClient {
    private static final Logger logger = LoggerFactory.getLogger(NettyWebSocketClient.class);
    static final long DEFAULT_SHUTDOWN_TIMEOUT = 2;
    private final int handshakeTimeout;
    private final int heartbeatInterval;
    private final URI websocketURI;
    private int port;
    private SslContext sslCtx;
    EventLoopGroup group = new NioEventLoopGroup(0);
    Bootstrap bootstrap = new Bootstrap();

    public NettyWebSocketClient(final String uriStr) throws Exception {
        handshakeTimeout = Integer.parseInt(System.getProperty("nls.ws.handshake.timeout", "10"));
        heartbeatInterval = Integer.parseInt(System.getProperty("nls.ws.heartbeat.interval", "0"));
        websocketURI = new URI(uriStr);
        final boolean ssl = "wss".equalsIgnoreCase(websocketURI.getScheme());
        port = websocketURI.getPort();
        if (ssl) {
            sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
            if (port == -1) {
                port = 443;
            }
        } else {
            if (port == -1) {
                port = 80;
            }
        }
        final String isCompression = System.getProperty("nls.ws.compression", "false");
        bootstrap.option(ChannelOption.TCP_NODELAY, true)
            .group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() {
            @Override
            protected void initChannel(SocketChannel ch) {
                ChannelPipeline p = ch.pipeline();
                if (sslCtx != null) {
                    p.addLast(sslCtx.newHandler(ch.alloc(), websocketURI.getHost(), 443));
                }
                if ("true".equalsIgnoreCase(isCompression)) {
                    p.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192),
                        WebSocketClientCompressionHandler.INSTANCE);
                } else {
                    p.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192));
                }

                if (heartbeatInterval > 0) {
                    // 双方都没有发送消息超过一定时间后触发超时时间发送ping消息(WebSocketClientHandler.userEventTriggered)
                    p.addLast(new IdleStateHandler(0, 0, heartbeatInterval));
                }
                p.addLast("hookedHandler", new WebSocketClientHandler());
            }
        });

    }

    public Connection connect(String token, ConnectionListener listener, int connectionTimeout) throws Exception {
        return connect(token,listener,connectionTimeout,websocketURI);
    }

    public Connection connect(String token, ConnectionListener listener, int connectionTimeout, URI newUri) throws Exception {
        URI connectUri = null;
        if (newUri == null){
            connectUri = websocketURI;
        }else {
            connectUri = newUri;
        }
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout);
        HttpHeaders httpHeaders = new DefaultHttpHeaders();
        httpHeaders.set(Constant.HEADER_TOKEN, token);
        String authHeader = System.getenv("HTTP_HEADER_AUTHORIZATION");
        if (authHeader != null) {
            httpHeaders.set("Authorization", authHeader);
        }
        WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory
            .newHandshaker(connectUri, WebSocketVersion.V13, null, true, httpHeaders, 65536 * 3);
        long start=System.currentTimeMillis();
        Channel channel = bootstrap.connect(connectUri.getHost(), port).sync().channel();
        long connectingTime=System.currentTimeMillis()-start;
        logger.debug("websocket channel is established after sync,connectionId:{} ,use {}", channel.id(),connectingTime);
        WebSocketClientHandler handler = (WebSocketClientHandler)channel.pipeline().get("hookedHandler");
        handler.setListener(listener);
        handler.setHandshaker(handshaker);
        handshaker.handshake(channel);
        start=System.currentTimeMillis();
        waitHandshake(handler.handshakeFuture(), channel);
        long handshakeTime=System.currentTimeMillis()-start;
        logger.debug("websocket connection is established after handshake,connectionId:{},use {}", channel.id(),handshakeTime);
        return new NettyConnection(channel,connectingTime,handshakeTime);
    }

    public void shutdown() {
        group.shutdownGracefully();
    }

    public void shutdown(long quietTime, TimeUnit unit) {
        group.schedule(new Runnable() {
            @Override
            public void run() {
                group.shutdownGracefully(DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
            }
        }, quietTime, unit);
    }

    private void waitHandshake(ChannelFuture handshakeFuture, Channel channel) throws Exception {
        if (handshakeFuture.await(handshakeTimeout, TimeUnit.SECONDS)) {
            if(!handshakeFuture.isSuccess()) {
                throw new Exception("connect failure!" + handshakeFuture.cause());
            }
            return;
        }
        // 握手超时后关闭连接
        if (channel.isActive()){
            channel.close();
        }
        if (handshakeFuture.cause() != null) {
            throw new Exception("Handshake timeout!", handshakeFuture.cause());
        } else {
            throw new Exception("Handshake timeout!");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy