基於netty的http client實現
阿新 • • 發佈:2021-07-01
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>syb</groupId> <artifactId>httpClientNetty</artifactId> <version>1.0</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> </dependencies> <build> <finalName>httpClientNetty</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
啟動類
裝配netty,併發送一個http request
package syb.httpClientNetty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import syb.httpClientNetty.httpclient.HttpChannelInitializer; @Component public class AppLifecycle implements SmartLifecycle { private Logger logger = LoggerFactory.getLogger(getClass()); private boolean isRunning = false; @Autowired private HttpChannelInitializer cinit; @Override public void start() { logger.info("app start..."); EventLoopGroup workGroup = null; String host = "192.168.5.3"; int port = 9898; try { workGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap() .group(workGroup) .option(ChannelOption.SO_KEEPALIVE, true) .channel(NioSocketChannel.class) .handler(cinit); ChannelFuture f = bootstrap.connect(host, port).sync(); Channel ch = f.channel(); DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test"); request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); request.headers().set(HttpHeaderNames.HOST, host); ch.writeAndFlush(request).sync(); ch.closeFuture().sync(); } catch (Exception e) { logger.error("", e); } finally { if (workGroup != null) { workGroup.shutdownGracefully(); } } isRunning = true; } @Override public void stop() { try { } catch (Exception e) { logger.error("", e); } isRunning = false; } @Override public boolean isRunning() { return isRunning; } }
通道初始化-載入通道處理器
package syb.httpClientNetty.httpclient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; /** * channel初始化器。負責裝配channel的入站處理器。 */ @Component public class HttpChannelInitializer extends io.netty.channel.ChannelInitializer<NioSocketChannel> { @Autowired private SslHandlerFactory sslHandlerFactory; @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline().addLast(sslHandlerFactory.getSslHandler()); ch.pipeline().addLast(new HttpClientCodec()); ch.pipeline().addLast(new HttpObjectAggregator(1024 * 10 * 1024)); ch.pipeline().addLast(new ClientHandler()); } }
建立SslHandler
package syb.httpClientNetty.httpclient;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Component;
import io.netty.handler.ssl.SslHandler;
@Component
public class SslHandlerFactory {
private static String keyStorePassword = "baicells";
public SslHandler getSslHandler() throws Exception {
return createSslHandler();
}
private SslHandler createSslHandler() throws Exception {
// 載入domain proxy證書
KeyStore clientKeyStore = loadDpKeyStore();
// 生成key mgr
KeyManager[] kms = createKeyMgr(clientKeyStore);
// 生成trust mgr
TrustManager[] tms = createTrustMgr();
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
// 初始化SSL Context
sslContext.init(kms, tms, new SecureRandom());
// SSL密碼套件
String[] ciperSuites = new String[] { "TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" };
SSLEngine engine = sslContext.createSSLEngine();
engine.setUseClientMode(true);
engine.setEnabledCipherSuites(ciperSuites);
SslHandler sslHandler = new SslHandler(engine);
return sslHandler;
}
private KeyStore loadDpKeyStore() throws Exception {
KeyStore keyStore;
InputStream in = null;
ApplicationHome home = new ApplicationHome();
try {
String path = home.getDir().getAbsolutePath() + File.separator + "client.jks";
in = new FileInputStream(path);
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(in, keyStorePassword.toCharArray());
} catch (FileNotFoundException e) {
throw new FileNotFoundException(e.getMessage());
} catch (Exception e) {
throw new Exception(e);
} finally {
if (in != null) {
in.close();
}
}
return keyStore;
}
/**
* 建立key mgr
*/
private KeyManager[] createKeyMgr(KeyStore keyStore) throws Exception {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, keyStorePassword.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
return kms;
}
/**
* 建立trust mgr
*/
private TrustManager[] createTrustMgr() throws Exception {
MyX509TrustMgr trustMgr = null;
try {
PublicKey publicKey = getCaPublicKey();
trustMgr = new MyX509TrustMgr(publicKey);
} catch (FileNotFoundException e) {
throw new FileNotFoundException();
} catch (Exception e) {
throw new Exception(e);
}
TrustManager[] tms = { trustMgr };
return tms;
}
private PublicKey getCaPublicKey() throws Exception {
PublicKey publicKey;
InputStream in = null;
ApplicationHome home = new ApplicationHome();
try {
String certPathForCa = home.getDir().getAbsolutePath() + File.separator + "trust.pem";
in = new FileInputStream(certPathForCa);
X509Certificate caCert = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(in);// 載入證書
publicKey = caCert .getPublicKey();// 獲取證書的public key
} catch (Exception e) {
throw new Exception(e);
} finally {
if (in != null) {
in.close();
}
}
return publicKey;
}
}
自定義的X509TrustManager
package syb.httpClientNetty.httpclient;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyX509TrustMgr implements X509TrustManager {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private PublicKey caPublicKey = null;
public MyX509TrustMgr() {
}
public MyX509TrustMgr(PublicKey ca) {
this.caPublicKey = ca;
}
@Override
public void checkClientTrusted(X509Certificate[] ax509certificate, String s) throws CertificateException {
logger.info("checkClientTrusted");
}
@Override
public void checkServerTrusted(X509Certificate[] certList, String authType) throws CertificateException {
// 驗證證書有效期
for (X509Certificate cert : certList) {
cert.checkValidity();
}
// 驗證 CA
checkCa(certList[0], certList[1]);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
/**
* 驗證CA,如果驗證不通過,丟擲異常
*/
private void checkCa(X509Certificate cert, X509Certificate caCert) throws CertificateException {
logger.info("verify ca");
if (caCert == null) {
// 未讀取到ca
logger.error("no ca cert");
throw new CertificateException("48:unknownCA");
}
if (!caCert.getPublicKey().equals(caPublicKey)) {
// 讀取到的ca與信任的ca不匹配
logger.error("ca cert not equal trusted ca cert");
throw new CertificateException("48:unknownCA");
}
String issuerDN = cert.getIssuerDN().toString();
String caDN = caCert.getSubjectDN().toString();
if (!issuerDN.equals(caDN)) {
throw new CertificateException("48:unknownCA");
} else {
try {
cert.verify(caCert.getPublicKey());
} catch (Exception e) {
throw new CertificateException("51:decryptError");
}
}
logger.info("verify ca, ok");
}
}
http response處理器
只是簡單的列印http response資訊。
package syb.httpClientNetty.httpclient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
public class ClientHandler extends ChannelInboundHandlerAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("receive...");
FullHttpResponse response = (FullHttpResponse) msg;
ByteBuf content = response.content();
HttpHeaders headers = response.headers();
logger.info("headers:{}", headers.toString());
byte[] data = new byte[content.readableBytes()];
content.readBytes(data);
logger.info("content:{}", new String(data));
}
}