首先介绍HTTPS(含HTTP)的测试工具类,共有4个,代码如下:
public class HttpsRequestRunnable implements Runnable {
private static Logger logger = LoggerFactory.getLogger(HttpsRequestRunnable.class);
private static final LongAdder counter = new LongAdder();
private String server_url ;
private String strReqJson ;
private String userId ;
private String token ;
public HttpsRequestRunnable(String server_url, String userId, String token,String strReqJson) {
this.server_url = server_url ;
this.strReqJson = strReqJson ;
this.userId = userId ;
this.token = token ;
}
public void run() {
String respStr = HttpsRequestUtil.getFromHttps(server_url, userId, token, strReqJson);
logger.info("respStr:{}",respStr);
counter.increment();
if(counter.intValue()%10==1) {
logger.error("{} resContent {} ", counter.intValue(),respStr);
}
}
}
public class HttpsRequestUtil {
private static Logger logger = LoggerFactory.getLogger(HttpsRequestUtil.class);
private static HttpURLConnection getConn(String server_url) throws Exception {
URL url = new URL(server_url);
if(server_url.toLowerCase().startsWith("https")) {
HttpsURLConnection.setDefaultHostnameVerifier(new NullHostNameVerifier());
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tm, /*new java.security.SecureRandom()*/null);
SSLSocketFactory ssf = sslContext.getSocketFactory();
conn.setSSLSocketFactory(ssf);
return conn ;
}
return (HttpURLConnection) url.openConnection();
}
public static String getFromHttps(String server_url,String userId,String token,String strReqJson) {
String CONTENT_TYPE = "application/json";
StringBuilder sb2 = new StringBuilder();
try {
HttpURLConnection conn = getConn(server_url);
conn.setConnectTimeout(45 * 1000);
conn.setReadTimeout(45 * 1000);
conn.setDoInput(true);// 允许输入
conn.setDoOutput(true);// 允许输出
conn.setUseCaches(false); // 不允许使用缓存
conn.setRequestMethod("POST");
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("charset", "UTF-8");
conn.setRequestProperty("Content-Type", CONTENT_TYPE);
conn.setRequestProperty("userId", userId);
conn.setRequestProperty("token", token);
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
if(StringUtil.isNotEmpty(strReqJson)) {
outStream.write(strReqJson.getBytes());
}
outStream.flush();
outStream.close();
conn.connect();
InputStream in = null;
int resCode = conn.getResponseCode();
logger.debug("resCode {} ", resCode);
if (resCode == 200) {
in = conn.getInputStream();
int ch;
while ((ch = in.read()) != -1) {
sb2.append((char) ch);
}
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return sb2.toString();
}
}
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
class NullHostNameVerifier implements HostnameVerifier {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
}
然后是HTTP/HTTPS单元测试类(其实完全可以将testLocalHttpLogin的内容放入一个普通类的main方法中运行):
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class HttpLocalFunctionTest {
private static Logger logger = LoggerFactory.getLogger(HttpLocalFunctionTest.class);
@Test
public void testLocalHttpLogin() throws Exception {
logger.info("begin2test_testLogin");
//可以支持http和https
String SERVER_URL = "http://127.0.0.1:8888/login" ;
String SERVER_URL = "https://127.0.0.1:8888/login" ;
String reqJson = "{\"name\":\"张三\",\"id\":0}";
String userId = StringUtil.getRandomCode(5);
String token = StringUtil.getRandomCode(15);
Thread t = new Thread(new HttpsRequestRunnable(SERVER_URL, userId,token, reqJson));
t.start();
t.join();
}
}
接下来是WebSocket的模拟客户端。
需要引入 java_websocket.jar,pom.xml 如下:
<dependency>
<groupId>org.java-websocketgroupId>
<artifactId>Java-WebSocketartifactId>
<version>1.3.9version>
dependency>
普通ws测试类对应的Java代码如下:
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 此测试类模拟 WebSocket 的客户端对服务器做压力测试
* 注意:此类无法在创建连接的同时发送userId和token信息,导致校验无法通过。
*/
public class CommonWsClientTest extends WebSocketClient {
private static Logger logger = LoggerFactory.getLogger(CommonWsClientTest.class);
public CommonWsClientTest( URI serverUri , Draft draft ) {
super( serverUri, draft );
}
public CommonWsClientTest( URI serverURI ) {
super( serverURI );
}
public CommonWsClientTest( URI serverUri, Map httpHeaders ) {
super(serverUri, httpHeaders);
}
@Override
public void onOpen( ServerHandshake handshakedata ) {
String msg = "{\"msgType\":0,\"msgId\":0}" ;
logger.info("onOpen:"+msg);
send(msg);
}
@Override
public void onMessage( String message ) {
logger.info("receiveMsg:{}",message);
}
@Override
public void onClose( int code, String reason, boolean remote ) {
}
@Override
public void onError( Exception ex ) {
ex.printStackTrace();
}
public static void process() throws URISyntaxException {
//请特别注意,此种方式如果连接本地,请使用127.0.0.1 而不要使用localhost 否则有时无法连接。
String url = "ws://127.0.0.1:9999/yourappname/push" ;
CommonWsClientTest c = new CommonWsClientTest(new URI(url));
c.connect();
}
public static void main( String[] args ) {
process();
try {
Thread.sleep(5000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
wss模拟客户端,支持压力测试:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/**
* 此测试类模拟 WebSecuritySocket 的客户端对服务器做压力测试
* 注意:此类无法在创建连接的同时发送[ HTTP header] userId和token信息,导致校验无法通过。
*/
public class SSLWsClientStressTest {
private static String url = "wss://www.yourdns.com:9999/yourappname/push";
private static String KEYSTORE = "D:\\workspace\\netserver\\keysecurity\\hansmachine.jks";
private static String STOREPASSWORD = "123456";
private static String KEYPASSWORD = "123456";
public static void main(String[] args) {
String clientNumber = "1";
if (args.length > 0) {
clientNumber = args[0];
}
if (args.length > 1) {
url = args[1];
}
// ws and wss 的网址 不能以 / 结尾否则会报错。
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
System.out.println("-----SSLClientExample-------");
System.out.println("clientNumber:" + clientNumber);
System.out.println("KEYSTORE:" + KEYSTORE);
System.out.println("url:" + url);
for (int i = 0; i < Integer.parseInt(clientNumber); i++) {
new Thread(new Runnable() {
public void run() {
try {
process();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
if (i % 100 == 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
Thread.sleep(5000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static SSLContext sslContext = null ;
public static void process() throws Exception {
String STORETYPE = "JKS";
WebSocketHeartBeatClient chatclient = new WebSocketHeartBeatClient(new URI(url));
initSSLContext(STORETYPE);
SSLSocketFactory factory = sslContext.getSocketFactory();
chatclient.setSocket(factory.createSocket());
chatclient.connectBlocking();
String msg = "{\"msgType\":0,\"msgId\":0}";
chatclient.send(msg);
Thread.sleep(5000000);
}
private static void initSSLContext(String STORETYPE) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, UnrecoverableKeyException, KeyManagementException {
if(sslContext!=null) {
return ;
}
KeyStore ks = KeyStore.getInstance(STORETYPE);
File kf = new File(KEYSTORE);
ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, KEYPASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
SSLContext localsslContext = null;
localsslContext = SSLContext.getInstance("TLS");
localsslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslContext = localsslContext;
}
}
import java.net.URI;
import java.util.concurrent.atomic.LongAdder;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
public class WebSocketHeartBeatClient extends WebSocketClient {
private static LongAdder count = new LongAdder();
public WebSocketHeartBeatClient( URI serverUri ) {
super( serverUri );
}
@Override
public void onOpen( ServerHandshake handshakedata ) {
//缺点:这里无法模拟发送http header信息?
//handshakedata.
}
@Override
public void onMessage( String message ) {
//System.out.println( "got: " + message );
count.increment();
int c = count.intValue();
if(c%50==0) {
System.out.println("ReceiveMsgCount:"+c);
}
}
@Override
public void onClose( int code, String reason, boolean remote ) {
//System.out.println( "Disconnected" );
}
@Override
public void onError( Exception ex ) {
ex.printStackTrace();
}
}
以上两个方法都是使用的第三方客户端工具java_websocket,一般情况下够用了,但是如果考虑到安全性,
服务器端有时候会要求客户端在握手的时候发送[ HTTP header] userId和token信息进行校验,
如果校验失败服务器端拒绝连接。
此时需要Netty出马模拟客户端:
import java.net.URI;
import java.util.concurrent.atomic.LongAdder;
import javax.net.ssl.SSLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yjzx.server.common.util.ConfigConstants;
import com.yjzx.server.common.util.StringUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
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.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
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.SslHandler;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
/**
* 目前已经实现在建立连接的时候同时发送http header 信息,
* 然后发送一个心跳包信息。
*/
public final class WebSocketSecurityClient {
private static Logger logger = LoggerFactory.getLogger(WebSocketSecurityClient.class);
private static final String path = "/yourappname/push";
private static LongAdder count = new LongAdder();
public static void sendMessage(String json) throws Exception {
String host = "www.yourdns.com";
int port = 9999;
String url = "wss://" + host + ":" + port + path ;
sendMessage(url,json);
}
/**
* websocket 信息发送,支持 ws:// 方式和 wss:// 方式
* 样例1:wss://www.abc.com:9999/yourdns/server
* 样例2:ws://www.abc.com:9999/yourdns/server
* @param url
* @param json
* @throws Exception
*/
public static void sendMessage(String url, String json) throws Exception {
// 如果多一个斜线就会--没反应,也不报错! wss://www.abc.com:9999//yourappname/server
URI uri = new URI(url);
String host = uri.getHost();
int port = uri.getPort();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
//握手时发送头信息,供服务器校验
DefaultHttpHeaders customHeaders = new DefaultHttpHeaders();
String userId = "9999" ;
customHeaders.add("userId", userId);
customHeaders.add("token", "ABCD9876");
final WebSocketClientHandler webSocketClientHandler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, customHeaders));
b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if(url.toLowerCase().startsWith("wss")) {
p.addLast("ssl", getSslHandler(ch,host,port));
logger.info("initChannel_AddSSLHandlerFinished");
}
p.addLast(new HttpClientCodec());
p.addLast(new HttpObjectAggregator(8192));
p.addLast(WebSocketClientCompressionHandler.INSTANCE);
p.addLast(webSocketClientHandler);
}
});
Channel clientSocketChannel = b.connect(uri.getHost(), port).sync().channel();
webSocketClientHandler.handshakeFuture().sync();
count.increment();
int c = count.intValue();
if(c%20==0) {
logger.warn("count:{}",c);
}
//服务器端与手机(设备端)约定是5分钟(300s)通过websocket发送一次心跳包
//故此处模拟此场景。
for (int i = 1; i <= 5; i++) {
WebSocketFrame frame2 = new TextWebSocketFrame(json);
clientSocketChannel.writeAndFlush(frame2);
logger.info("begin2sleep 300s");
Thread.sleep(300*1000);
}
// 不要退出
Thread.sleep(5000000);
} finally {
group.shutdownGracefully();
}
}
private static SslHandler getSslHandler(SocketChannel ch, String host,int port) {
SslContext sslCtx;
try {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
return sslCtx.newHandler(ch.alloc(), host, port);
} catch (SSLException e) {
e.printStackTrace();
return null ;
}
}
}
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.util.CharsetUtil;
public class WebSocketClientHandler extends SimpleChannelInboundHandler
下面这个才是测试类,当然也可以将测试方法的内容放在普通类的main方法中执行,效果是一样的
package com.yjzx.client.websocket.netty;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 因为本地netty作为客户端启动的时候也开了好几个线程,导致使用netty作为wss客户端的时候无法开更多线程连接服务器
* 但是此方式可以作为功能测试。
*/
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING )
public class WssIntercomFunctionTest {
private static Logger logger = LoggerFactory.getLogger(WssIntercomFunctionTest.class);
@Test
public void test200SendHeartBeatInWssOrWs() throws Exception {
Thread.sleep(3000);
String msg = "{\"msgType\":0,\"msgId\":0}" ;
logger.info("msg:{}",msg);
//支持 SSL 加密websocket
String url = "wss://www.yourdns.com:9999/yourappname/push" ;
//也可以支持websocket方式
String url = "ws://www.yourdns.com:9999/yourappname/push" ;
WebSocketSecurityClient.sendMessage(url,msg);
}
}