Java-gRPC-Python开发环境安装配置
配置开发环境,跑通从Java EE Tomcat Web应用程序通过gRPC调用服务器Python程序的例子HelloWorld(gRPC自带),然后配置好SSL加密连接及调用用户验证,后面有时间再写一个调用业务功能的例子。因为是严谨的紧耦合方式,建立通道的过程有点繁琐,每增加一个功能函数就要改一堆源码走一串流程,使用上就没有嵌入Shiny APP 或Rserve调用R程序方便,或者用R通过Reticulate调用Python更方便一点。Anyway,架构图上所有的通道都调通了,任督二脉全线贯通,这是一个有竞争力的开源解决方案,显然并不局限于开源产品,商业产品也是可以参考的,主要的价值在于为有效盘活存量的软硬件与数据资产提供了高性价比的解决方案。道理越辩越明,以理服人,按需选用。
可以访问在线示例地址具体了解一下。

一、gRPC Python入门
安装,参阅资料。
# python -m pip install grpcio
# python -m pip install grpcio-tools
(base) [root@VM-4-12-centos notebook]# python -m pip install grpcio-tools
Looking in indexes: http://mirrors.tencentyun.com/pypi/simple
Collecting grpcio-tools
Downloading http://mirrors.tencentyun.com/pypi/packages/3a/51/aeb8418cb5e336aa8df2e6ea55bdab3e041b39a8aa2850df81fcc279aaf5/grpcio_tools-1.51.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.4 MB)
|████████████████████████████████| 2.4 MB 459 kB/s
Collecting grpcio>=1.51.1
Downloading http://mirrors.tencentyun.com/pypi/packages/fc/62/bccba142a6ad670727fb329579d18ab44e8b3585b23068baa0b7196b86b6/grpcio-1.51.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.8 MB)
|████████████████████████████████| 4.8 MB 376 kB/s
Collecting protobuf<5.0dev,>=4.21.6
Downloading http://mirrors.tencentyun.com/pypi/packages/4f/e0/9e8b77d79996f86e1ecf6622a693b5b116ea392f23c3758de5e902ba84ad/protobuf-4.21.10-cp37-abi3-manylinux2014_x86_64.whl (408 kB)
|████████████████████████████████| 408 kB 514 kB/s
Requirement already satisfied: setuptools in /usr/lib64/anaconda3/lib/python3.9/site-packages (from grpcio-tools) (58.0.4)
Installing collected packages: protobuf, grpcio, grpcio-tools
Attempting uninstall: protobuf
Found existing installation: protobuf 3.20.1
Uninstalling protobuf-3.20.1:
Successfully uninstalled protobuf-3.20.1
Attempting uninstall: grpcio
Found existing installation: grpcio 1.45.0
Uninstalling grpcio-1.45.0:
Successfully uninstalled grpcio-1.45.0
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
tensorboard 2.11.0 requires protobuf<4,>=3.9.2, but you have protobuf 4.21.10 which is incompatible.
Successfully installed grpcio-1.51.1 grpcio-tools-1.51.1 protobuf-4.21.10
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
在两个终端窗口中分别运行例子,要先安装git以便下载源码,具体程序就不列出来了。
# yum install git
# git clone -b v1.50.0 --depth 1 https://github.com/grpc/grpc
# cd grpc/examples/python/helloworld
# python greeter_server.py
# python greeter_client.py


开发自己的例子:
1、建立工作目录
# mkdir /home/jean/grpcTest
# cd /home/jean/grpcTest
2、定义服务器接口原型
# vi helloworld.proto
在第一行指定接口原型的语法版本‘syntax = “proto3”;’,参阅资料。这里使用与后面Java版相同的proto文件,否则与Java客户端两边对接会报UNIMPLEMENTED的错误。
// Copyright 2015 The gRPC Authors
//
// 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.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
3、编译服务器接口原型并产生服务器程序框架
一定要用“-I”参数指定接口原型目录,最后的接口原型文件相对于该目录,参阅资料 。
python -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. helloworld.proto
生成的gRPC stub文件:
helloworld_pb2.pyi包含了request/response对象的接口定义。
helloworld_pb2.py包含了request/response对象的实现。
helloworld_pb2_grpc.py包含了client/server对象的定义。
greeter_server.py实现服务器对象,先直接拷贝使用,我增加了一行输出客户端的名字。
greeter_client.py实现客户端对象,先直接拷贝使用。


二、gRPC Java 入门
安装gradle ,参阅资料。
# mkdir /opt/gradle
# cd /opt/gradle
# wget https://downloads.gradle-dn.com/distributions/gradle-7.6-bin.zip
# unzip -d /opt/gradle gradle-7.6-bin.zip
# vi /etc/profile
export PATH=$PATH:/opt/gradle/gradle-7.6/bin
# gradle -v
下载Java例子。
# git clone -b v1.51.0 --depth 1 https://github.com/grpc/grpc-java
编译例子
# cd grpc-java/examples
# ./gradlew installDist
在两个终端窗口中分别运行服务器与客户端
# ./build/install/examples/bin/hello-world-server
# ./build/install/examples/bin/hello-world-client


三、运行Python的服务器,Java的客户端,连接O.K.


四、建立gRPC Maven Jave项目
参阅资料。
1、下载Java例子到PC端。
> git clone -b v1.51.0 --depth 1 https://github.com/grpc/grpc-java
2、建立Maven服务器项目
Open Eclipse →File →new →other →Maven Project
Java Build Path → Libriaries→ JRE System Library[jdk11]
1)pom.xml。
<dependencies>一节是最小依赖了,软件都更新到了最新的版本。
<build>一节是要用Maven生成Java gRPC的stub源码。Eclipse Dynamic Web项目里没有Maven build的功能,要先建立Maven Java程序项目生成stub源码,再拷贝过去(说明:Tomcat Web项目也可以由Maven建立与管理,这样可以直接在Tomcat Web项目中build生成stub源码,并编译打包成war文件发布测试,参阅资料,此处是为了说明测试的过程)。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jean</groupId>
<artifactId>grpcserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gRPCServer</name>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.7</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.0</version>
</dependency>
<!-- Removed from JDK11 and above, need to be added here -->
<!-- https://blog.csdn.net/ml863606/article/details/109202246?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-109202246-blog-120904477.pc_relevant_3mothn_strategy_recovery&spm=1001.2101.3001.4242.1&utm_relevant_index=3 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<defaultGoal>clean generate-sources compile install</defaultGoal>
<plugins>
<!-- compile proto file into java files -->
<plugin>
<groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.11.1</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<includeMavenTypes>direct</includeMavenTypes>
<inputDirectories>
<include>src/main/resources</include>
</inputDirectories>
<outputTargets>
<outputTarget>
<type>java</type>
<outputDirectory>src/main/java</outputDirectory>
</outputTarget>
<outputTarget>
<type>grpc-java</type>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0</pluginArtifact>
<outputDirectory>src/main/java</outputDirectory>
</outputTarget>
</outputTargets>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

项目根目录->右键->Maven->Update Project,下载依赖包。只下载一次,可能需要点时间。
2)helloworld.proto与Python版接口保持一样,增加了一些Java上的选项设置。Python版的proto文件要更新为与Java版的一致,并重新编译生成接口stub文件。
这个文件放在上面src/main/resources 定义的目录下。
pom.xml->右键-> Run As Maven Build,生成 gRPC的stub类源码,输出在上面定义的目录src/main/java下。因为pom.xml中定义了,直接按“Run”按钮执行即可。

3)拷贝HellowWorldServer.java到项目源码中。
HellowWorldServer.java->右键-> Run As Java Applicaion

4)用BloomRPC客户端测试gRPC服务器。
主页,下载,Windows版。点击运行BloomRPC-Setup-1.5.3.exe。
在Proto中按“+”打开上面的Java版helloworld.proto接口定义文件。
在右面“Env”中输入RPC服务器的网址与端口localhost:50051
点击左面的方法,在右面Editor页json格式参数中输入参数的值,按运行按钮调用,Response页会看到返回结果。

3、建立Maven gRPC客户端项目
Open Eclipse →File →new →other →Maven Project
Java Build Path → Libriaries→ JRE System Library[jdk11]
1)、pom.xml,拷贝服务器项目pom.xml中的与的内容。
项目根目录->右键->Maven->Update Project
2)、拷贝服务器项目生成的stub Java源码。
package io.grpc.examples.helloworld:
GreeterGrpc.java, HelloReply.java, HelloReplyOrBuilder.java, HelloRequest.java, HelloRequestOrBuilder.java, HelloWorldProto.java
3)、拷贝HellowWorldClient.java到项目中。
HellowWorldClient.java->右键-> Run As Java Applicaion

4)、测试Java gRPC Client到服务器 Python gRPC Server的调用。
A、服务器防火墙上打开TCP端口50051。
B、用BloomRPC客户端测试gRPC服务器。更改 Env的服务器地址为 http://jeanye.cn:50051,测试SayHello()调用。
C、更改HellowWorldClient.java的连接地址:
String target = "124.223.110.20:50051";
HellowWorldClient.java->右键-> Run As Java Applicaion


五、建立gRPC Tomcat Web项目
1、建立项目
Open Eclipse →File →new →Dynamic Web Project
在项目properties中设置为Tomcat 7.0 & JDK11
2、添加依赖包
打开前面建立gRPC客户端项目的pom.xml文件,点击Dependency Hierarchy页,把Resolved Dependencies页中显示的包,从本地Maven Repository中逐个import到项目的/WebContent/WEB-INF/lib目录下,编译和运行gRPC Java客户端程序都需要它们。Maven Repository目录一般是 C:\Users\username\.m2\repository。
animal-sniffer-annotations-1.21.jar
annotations-4.1.1.4.jar
checker-qual-3.12.0.jar
error_prone_annotations-2.14.0.jar
failureaccess-1.0.1.jar
grpc-api-1.51.0.jar
grpc-auth-1.51.0.jar
grpc-context-1.51.0.jar
grpc-core-1.51.0.jar
grpc-netty-shaded-1.51.0.jar
grpc-protobuf-1.51.0.jar
grpc-protobuf-lite-1.15.1.jar
grpc-stub-1.51.0.jar
gson-2.9.0.jar
guava-20.0.jar
guava-31.1-android.jar
j2objc-annotations-1.3.jar
javax.annotation-api-1.3.2.jar
jsr305-3.0.2.jar
listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
opencensus-api-0.12.3.jar
opencensus-contrib-grpc-metrics-0.12.3.jar
perfmark-api-0.25.0.jar
proto-google-common-protos-2.9.0.jar
protobuf-java-3.21.7.jar

3、拷贝gRPC客户端项目的gRPC stub源码文件到Web项目源码中,也可以直接在gRPC客户端项目生成,这样就不用建立gRPC服务器项目,本文建立gRPC服务器项目是为了说明测试的过程。
GreeterGrpc.java HelloReply.java
HelloReplyOrBuilder.java HelloRequest.java HelloRequestOrBuilder.java
HelloWorldProto.java

4、编写在JSP页面中调用gRPC的帮助类HelloWorldClient.java
package io.grpc.examples.helloworld;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private ManagedChannel channel=null;
/** Construct client for accessing HelloWorld server by creating a channel. */
public HelloWorldClient(String server, Integer port) {
channel = ManagedChannelBuilder.forTarget(server+":"+port)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext()
.build();
// 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
// shut it down.
// Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
/** Say hello to server. */
public String greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return (e.toString());
}
logger.info("Greeting: " + response.getMessage());
return (response.getMessage());
}
/** Close the channel. */
public void close() throws Exception {
try {
// ManagedChannels use resources like threads and TCP connections. To prevent leaking these
// resources the channel should be shut down when it will no longer be used. If it may be used
// again leave it running.
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}catch(Exception e) {
e.printStackTrace();
}
}
}
5、编写界面页面 index.jsp输入用户名,提交给callgrpc.jsp,该页面通过上面的工具类HelloWorldClient.java调用gRPC服务器端的Python程序。
1)index.jsp:
<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<title>Java EE gRPC调用测试</title>
</head>
<body>
<center>
<form action="callgrpc.jsp" method="post">
跟用户打招呼 <br/>
用户名:<input type="text" name="name" value="Jean"><br/>
<input type="submit" value="打招呼">
</form>
</center>
</body>
</html>
2)callgrpc.jsp:
<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<%@ page import="io.grpc.examples.helloworld.*"%>
<%
String resp =null;
try{
String user = new String(request.getParameter("name").getBytes("ISO-8859-1"),"GBK");
if (user==null) user = "Jean";
//String server = "localhost";
String server = "jeanye.cn";
Integer port = 50051;
// Plain text without SSL & password.
HelloWorldClient client = new HelloWorldClient(server, port);
resp = client.greet(user);
client.close();
}catch(Exception e){
resp =e.toString();
}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="GBK">
<title>Java EE 调用gRPC 测试</title>
</head>
<body>
Java gRPC调用服务器端Python程序结果:
<br>
<%= resp %>
<br>
<a href="index.jsp">返回</a>
</body>
</html>
6、在PC端的Tomcat 7服务器上运行页面测试。



7、部署到虚拟主机的Tomcat 7服务器上运行测试。
体验网址。



六、为gRPC添加SSL与口令保护
gRPC的用户验证机制默认是没有用户名口令方式的,参阅资料,需要通过扩充gRPC来完成,参阅资料。该资料给出了Python上SSL+Password的实现,通过在gRPC调用的元数据中增加一些定制的header,检查客户端提交的header,来检查客户端是否授权用户,此处是检查’rpc-auth-header’的值是否相符,它的作用相当于检查口令。再增加一个’username’的header,然后Python服务器端检查时提取这两个header,再连接到用户数据库中验证就可以实现用户名口令验证了。用户数据库可以是LDAP/RDBS/Linux系统用户等,按需要实现。此处检查口令已经足够。
1、Python服务端,greeter_server_SSL_Auth.py。
服务器端具体是通过一个实现intercept_service()接口的AuthInterceptor类实现的,它扩展了grpc.ServerInterceptor类,其中定义了header的名字’rpc-auth-header’与检查的逻辑,就是是否与保存的口存令相符,这个口令是初始化AuthInterceptor类实例时赋予的。
Python服务器端的SSL支持通过调用grpc.ssl_server_credentials()来实现,需要生成服务器的证书链,自签证书要把自签根证书带上,最后的False参数表示单向SSL,不需要客户端提供数字证书,此处将使用口令验证。因为要访问服务器证书的私钥,我用root用户来运行它。参阅资料1,参阅资料2。
这是增加的部分,实现服务器端调用功能的Greeter类没有变化。
# Copyright 2015 gRPC authors.
#
# 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.
"""The Python implementation of the GRPC helloworld.Greeter server."""
from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
# The Class to serve the client with a Function defined by tha proto file.
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
print(request.name)
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)
# For authenticating with a password.
class AuthInterceptor(grpc.ServerInterceptor):
def __init__(self, key):
# 'rpc-auth-header' is the header data containing the password.
self._valid_metadata = ('rpc-auth-header', key)
def deny(_, context):
context.abort(grpc.StatusCode.UNAUTHENTICATED, 'Invalid key')
self._deny = grpc.unary_unary_rpc_method_handler(deny)
def intercept_service(self, continuation, handler_call_details):
meta = handler_call_details.invocation_metadata
print(meta)
# https://grpc.io/docs/guides/auth/#extending-grpc-to-support-other-authentication-mechanisms
# There's a bug in the document, meta[0] should be meta[1] for newer versions.
if meta and meta[1] == self._valid_metadata:
return continuation(handler_call_details)
else:
return self._deny
# The server procedure.
def serve():
port = '50051'
# Now requires authentication with a password by attaching an interceptor.
# 'access_key' is the password.
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=10),
interceptors=(AuthInterceptor('access_key'),)
)
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
# Load the server's certificate chain & private key.
# So the server should be run as root.
with open('/root/cert/server.key', 'rb') as f:
private_key = f.read()
with open('/root/cert/server.crt', 'rb') as f:
certificate_chain = f.read()
with open('/root/cert/ca.crt', 'rb') as f:
root_certificates = f.read()
# False means client cert is not required.
server_credentials = grpc.ssl_server_credentials(((private_key, certificate_chain),), root_certificates,False)
# '[::]:' means may be accessed from any IP.
server.add_secure_port('[::]:' + port, server_credentials)
server.start()
print("Server started, listening on " + port)
# May be interrupted by Ctrl+C.
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
2、Python客户端,greeter_client_SSL_Auth.py。
Python客户端通过一个扩展grpc.AuthMetadataPlugin类的GrpcAuth类来实现口令验证,它需要在建立连接时把口令’access_key’用GrpcAuth()传进去,header的名字就是服务器要求的’rpc-auth-header’。需要注意的是客户端建立SSL连接时,需要带上服务器自签证书的根证书,即CA证书,否则不能验证服务器的证书。我把根证书ca.crt拷贝到程序目录下,这样就可以用普通用户的身份运行Python客户端,因为ca.crt正是要发布给所有客户端的。GrpcAuth()与CA的证书组合成一个grpc.composite_channel_credentials(),然后可以通过grpc.secure_channel()建立SSL+Password的连接通道。
这是增加的部分。
# Copyright 2015 gRPC authors.
#
# 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.
"""The Python implementation of the GRPC helloworld.Greeter client."""
from __future__ import print_function
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
# Class to add authentication header to the meta data of every gRPC call.
class GrpcAuth(grpc.AuthMetadataPlugin):
def __init__(self, key):
self._key = key
# 'rpc-auth-header' is the authentication header defined by the server.
def __call__(self, context, callback):
callback((('rpc-auth-header', self._key),), None)
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
print("Will try to greet world ...")
# Will fail for enpty credentials.
# creds = grpc.ssl_channel_credentials()
# Need to include root certificate in credentials.
# https://stackoverflow.com/questions/72230151/how-to-open-a-secure-channel-in-python-grpc-client-without-a-client-ssl-certific
# Copy the selfsigned CA's root cert to the client directory, so that the client doesn't need to be run as root.
with open('ca.crt', 'rb') as f:
creds = grpc.ssl_channel_credentials(f.read())
# A composite channel credentials with SSL and password.
# Host name need to be the same as the server certificate.
channel = grpc.secure_channel(
'jeanye.cn:50051',
grpc.composite_channel_credentials(
creds,
grpc.metadata_call_credentials(
GrpcAuth('access_key')
# A worng key will fail then.
# GrpcAuth('wrong_access_key')
)
)
)
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='Jean'))
print("Greeter client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()


3、Java客户端。
gRPC的文档里没有直接讲Java客户端通过header验证用户的例子,甚至在grpc-java的例子中也没有直接列出来,但确实有实现的例子,只是位于源码树的深处,没有明确标示出来。如下面的代码所示,它是通过在SSL ManagedChannel之外,用ClientInterceptor类再包装为一个Channel来实现的,header就在实现ClientInterceptor接口的HeaderClientInterceptor类中添加。
1)、HelloWorldClientSSLAuth.java
SSL通道的建立用NettyChannelBuilder,与上文明文通道的ManagedChannelBuilder不同,与文档中的说明也稍有不同,因为要通过trustManager()传入自签的CA根证书,参阅资料。因为这是一个Tomcat Web APP,ca.crt打包在其中,后面在JSP页面中通过application.getRealPath(“/WEB-INF/ca.crt”)得到它在操作系统中具体的文件路径,再作为参数传给trustManager(),参阅资料。 然后是把口令”access_key”通过HeaderClientInterceptor()构造函数传入,最后关闭通道时引用未经ClientInterceptor封装的原通道。
这个结构是很简洁的。
package io.grpc.examples.helloworld;
import io.grpc.Channel;
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import java.io.File;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClientSSLAuth {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private ManagedChannel originChannel = null;
private Channel channel = null;
/** Construct client for accessing HelloWorld server by creating a channel. */
public HelloWorldClientSSLAuth(String caFile, String server, Integer port) throws Exception {
/*
* Use NettyChannelBuilder to build SSL connection to server.
* https://stackoverflow.com/questions/42700411/ssl-connection-for-grpc-java-client
* Need to include selfsigned CA certificate to build a sslContext to verify the server.
* And the server name must be same as the name in the server certificate.
*/
originChannel = NettyChannelBuilder.forAddress(server, port)
.sslContext(GrpcSslContexts.forClient().trustManager(new File(caFile)).build())
.build();
// "access_key" is the password to call gRPC.
ClientInterceptor interceptor = new HeaderClientInterceptor("access_key");
// Channel with an interceptor to add an auth header for each gRPC call.
channel = ClientInterceptors.intercept(originChannel, interceptor);
// 'channel' here is a Channel, not a ManagedChannel, so it is not this code's
// responsibility to shut it down.
// Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
/** Say hello to server. */
public String greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return (e.toString());
}
logger.info("Greeting: " + response.getMessage());
return (response.getMessage());
}
/** Close the channel. */
public void close() throws Exception {
try {
// ManagedChannels use resources like threads and TCP connections. To prevent leaking these
// resources the channel should be shut down when it will no longer be used. If
// it may be used again leave it running.
originChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、HeaderClientInterceptor.java
这个类定义了header的名字为Python服务器要求的”rpc-auth-header”,然后在interceptCall()函数中通过headers.put()函数把header的名/值对插入到gRPC调用的meta数据中,如果要增加一个’username’ header,增加一条headers.put()语句就是了。
/*
* Copyright 2015 The gRPC Authors
*
* 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 io.grpc.examples.helloworld;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import java.util.logging.Logger;
/**
* A interceptor to handle client header.
*/
public class HeaderClientInterceptor implements ClientInterceptor {
private static final Logger logger = Logger.getLogger(HeaderClientInterceptor.class.getName());
private String password = null;
// This header is defined by the Python side to verify the client with a password.
@VisibleForTesting
static final Metadata.Key<String> CUSTOM_HEADER_KEY =
Metadata.Key.of("rpc-auth-header", Metadata.ASCII_STRING_MARSHALLER);
public HeaderClientInterceptor(String password) {
this.password = password;
}
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions, Channel next) {
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
/* put custom header */
headers.put(CUSTOM_HEADER_KEY, password);
super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onHeaders(Metadata headers) {
/**
* if you don't need receive header from server,
* you can use {@link io.grpc.stub.MetadataUtils#attachHeaders}
* directly to send header
*/
logger.info("header received from server:" + headers);
super.onHeaders(headers);
}
}, headers);
}
};
}
}
3、index.jsp。
这个页面没有变化。
<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<title>Java EE gRPC调用测试</title>
</head>
<body>
<center>
<form action="callgrpc.jsp" method="post">
跟用户打招呼 <br/>
用户名:<input type="text" name="name" value="Jean"><br/>
<input type="submit" value="打招呼">
</form>
</center>
</body>
</html>
4、callgrpc.jsp。
这个页面也很直观,不用解释了。
<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<%@ page import="io.grpc.examples.helloworld.*"%>
<%
String resp =null;
try{
String user = new String(request.getParameter("name").getBytes("ISO-8859-1"),"GBK");
if (user==null) user = "Jean";
//String server = "localhost";
String server = "jeanye.cn";
Integer port = 50051;
// Plain text without SSL & password.
// HelloWorldClient client = new HelloWorldClient(server, port);
String caFile = application.getRealPath("/WEB-INF/ca.crt");
// SSL without password.
// HelloWorldClientSSL client = new HelloWorldClientSSL(caFile,server,port);
// SSL with password.
HelloWorldClientSSLAuth client = new HelloWorldClientSSLAuth(caFile,server,port);
resp = client.greet(user);
client.close();
}catch(Exception e){
resp =e.toString();
}
5、运行效果。


