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!");
}
}
}