gRPC基于JDK的TLS通信
首先根据这里分享的链接 OpenSSL Certificate Authority(http://my.oschina.net/xinxingegeya/blog?catalog=3452471)实现一个私有的CA,并使用这个CA来签发证书。
定义gRPC 客户端和服务器端通信的protobuf文件,
syntax = "proto3"; package helloworld; option java_multiple_files = true; option java_package = "com.usoft.grpc.example.helloworld"; option java_outer_classname = "HelloWorldProto"; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloResponse) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloResponse { string message = 1; }
下面来看服务器端如何编写,主要代码如下,
private void start() throws Exception { // 加载服务器端的证书 File cert = Utils.loadCert("www.helloworld.com.cert.pem"); // 加载服务器端的密钥文件 File key = Utils.loadCert("www.helloworld.com.key.pkcs8.pem"); // 通过服务器端的证书和密钥文件构件SslContextBuilder SslContextBuilder sslContextBuilder = GrpcSslContexts.forServer(cert, key); sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.JDK); //使用netty 作为gRPC的服务器 final EventLoopGroup boss = new NioEventLoopGroup(); final EventLoopGroup worker = new NioEventLoopGroup(); final Class<? extends ServerChannel> channelType = NioServerSocketChannel.class; //addService(GreeterGrpc.bindService(new HelloWorldServiceImpl())) 添加服务接口的实现类,绑定该服务 //启动服务器 server = NettyServerBuilder.forPort(port).bossEventLoopGroup(boss) .workerEventLoopGroup(worker).channelType(channelType) .addService(GreeterGrpc.bindService(new HelloWorldServiceImpl())) .sslContext(sslContextBuilder.build()).build().start(); logger.info("Server started, listening on " + port); // 注册服务器钩子 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // Use stderr here since the logger may have been reset by its JVM shutdown hook. System.err.println( "*** shutting down gRPC server since JVM is shutting down"); GreeterServer.this.stop(); System.err.println("*** server shut down"); } }); }
下面来看客户端的实现,主要代码如下,
package com.usoft; import com.usoft.grpc.example.helloworld.GreeterGrpc; import com.usoft.grpc.example.helloworld.HelloRequest; import com.usoft.grpc.example.helloworld.HelloResponse; import io.grpc.ManagedChannel; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NegotiationType; import io.grpc.netty.NettyChannelBuilder; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** * Created by xinxingegeya on 16/1/2. */ public class GreeterClientTest { private static final Logger logger = Logger .getLogger(GreeterClientTest.class.getName()); private String address = "www.helloworld.com:8888"; private ManagedChannel channel; private GreeterGrpc.GreeterBlockingStub greeterBlockingStub; private GreeterGrpc.GreeterStub greeterStub; @Before public void setup() throws IOException { // It's a Netty transport. NegotiationType negotiationType = NegotiationType.TLS; // 客户端加载 证书链,来验证服务器端的证书是否可信 File cert = Utils.loadCert("ca-chain.cert.pem"); SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient() .trustManager(cert); sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder, SslProvider.JDK); SslContext sslContext = sslContextBuilder.build(); final EventLoopGroup group = new NioEventLoopGroup(); final Class<? extends io.netty.channel.Channel> channelType = NioSocketChannel.class; // 构建和服务器端通信的 channel,基于netty。 NettyChannelBuilder builder = NettyChannelBuilder .forAddress(Utils.parseSocketAddress(address)).eventLoopGroup(group) .channelType(channelType).negotiationType(negotiationType) .sslContext(sslContext); channel = builder.build(); greeterBlockingStub = GreeterGrpc.newBlockingStub(channel); greeterStub = GreeterGrpc.newStub(channel); } @After public void shutdown() throws InterruptedException { channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } // 通过stub来和服务器端进行通信 @Test public void testGreeter() { HelloRequest request = HelloRequest.newBuilder().setName("liyanxin") .build(); HelloResponse resp1 = greeterBlockingStub.sayHello(request); HelloResponse resp2 = greeterBlockingStub.sayHello(request); HelloResponse resp3 = greeterBlockingStub.sayHello(request); HelloResponse resp4 = greeterBlockingStub.sayHello(request); System.out.println(resp1.getMessage()); System.out.println(resp2.getMessage()); System.out.println(resp3.getMessage()); System.out.println(resp4.getMessage()); } }
需要注意的是 ,不论启动客户端还是服务器都需要这样来配置 JVM 参数,
java -Xbootclasspath/p:<path_to_alpn_boot_jar> ...
path_to_alpa_boot_jar是一个 jar 的路径,这个jar包jetty-alpa-boot.jar http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions
什么是 ALPN,
http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-introduction
https://zlb.me/2013/07/19/npn-and-alpn/
http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-01
详细代码请见http://git.oschina.net/xinxingegeya/gRPC-sample
==========END==========