Java→gRPC→Python业务功能调用实例
写完这个Java→gRPC→Python业务功能调用实例,实验服务器的架构图就全部完成,加上最后一块砖封顶了。本篇在gRPC自带的HellowWorld例子上增加一个功能调用,调用前面文章中建立的墨尔本房价分析模型,服务器部分用Python实现,客户端是Java的Tomcat Web App,用浏览器访问。
一、运行效果
1、浏览器输入。
选择回归算法及输入异常值阀值
2、服务器处理请求。
服务器打印收到的请求信息以便调试
3、客户端接收服务器返回结果。
客户端接收异常房价列表并查询原始数据
这里为了在一个页面上显示返回的异常数据及原始数据,将异常值阀值设为偏差60%,返回了6条记录。训练集有7212条数据,验证集有1803条数据,回归算法中准确率较好的已超过90%,非常高了。
二、Python部分
1、proto接口原型文件helloworld.proto。
增加了一个RPC调用GetOutliers(),以及它的调用参数message MelbourneRequest{}和返回结果message MelbourneReply{},注意返回结果是不定长的列表,所以前面加stream定义返回结果为一个流,gRPC的一个优点是支持流操作,非常方便。
// 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 greetingrpc SayHello (HelloRequest) returns (HelloReply) {} // Get outliers of Melbourne house pricing for a given algo and threshold.rpc GetOutliers (MelbourneRequest) returns (stream MelbourneReply) {}}// The request message containing the user's name.message HelloRequest {string name = 1;}// The response message containing the greetingsmessage HelloReply { string message = 1;}// Added for the Melbourne Example by Jean, 2022/12/13.// The request message containing the algo's name and threshhold for outliers.message MelbourneRequest {string algo = 1;doublethreshold = 2;}// The response message containing the outliers.message MelbourneReply {int64 row = 1;double origin =2;double predict =3;double se =4;}
Protocol Buffers项目文档中有各种语言之间的数据类型转换对照表,要按对照表来定义参数的类型,Java中用double比float要方便一点。
Protocol Buffers数据类型对照表
定义好接口原型文件后要先编译它,以便在Python服务器程序中引用新增加的接口类。
$ pwd/home/jean/grpcTest$ python -m grpc_tools.protoc-I.--python_out=. --pyi_out=. --grpc_python_out=. helloworld.proto
2、Python服务器程序greeter_server_SSL_Auth.py。
增加了三个变量,各个回归算法在训练集的拟合结果train、验证集的拟合结果valid及算法性能perf,服务器启动时在主程序中直接执行与前面Shiny APP共享的Melbourne_Regress.py脚本加载预处理好的数据与各个算法最优参数的回归模型,然后初始化上述三个变量。具体脚本及参数优化请参阅前面的文章。
在服务类Greeter()中增加了提供服务的方法GetOutliers(),根据请求中的算法与阀值从验证集拟合结果valid中选出异常值,每个异常值一行,生成一个接口原型中定义的MelbourneReply对象outlier,用yield outlier以stream的形式返回给客户端。gRPC 官方文档中文版中有Python stream用法的说明。
# 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 futuresimport loggingimport grpcimport helloworld_pb2import helloworld_pb2_grpcimport timeimport pandas as pdimport numpy as nptrain = Nonevalid = Noneperf= None# 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) def GetOutliers(self, request, context): print(request.algo) print(request.threshold) test = valid[["origin",request.algo]] test.index.name = 'row' test.reset_index(inplace=True) test.rename(columns={request.algo:"predict"},inplace=True) test["origin2"] = np.exp(test["origin"]) test["predict2"] = np.exp(test["predict"]) test["se"] = np.round((test["predict2"]- test["origin2"])/test["origin2"]*100,1) outliers =test.loc)>= request.threshold] outliers = outliers[["row","origin2","predict2","se"]] outliers.rename(columns={"origin2":"origin","predict2":"predict"},inplace=True) outliers.sort_values(["se"],ascending = True, inplace=True) for index, onerow in outliers.iterrows(): outlier = helloworld_pb2.MelbourneReply(\ row = int(onerow["row"]),\ origin = onerow["origin"],\ predict = onerow["predict"],\ se = onerow["se"] ) # print(outlier) yield outlier # 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 should be meta for newer versions. if meta and meta == 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__': print("Loading Melbourne models......") t_started = time.time() exec(open("/home/jean/scripts/Melbourne_Regress.py").read()) train = PredictTrain() valid = PredictValid() perf = performance() t_finished = time.time() print("Melbourne models loaded, "+str(round(t_finished-t_started,1))+" seconds.") logging.basicConfig() serve()
3、Python客户端程序greeter_client_SSL_Auth.py。
Python客户端程序是测试用途,以验证服务器程序工作正常。先构造一个接口原型中定义的MelbourneRequest对象,然后调用GetOutliers()函数,返回的stream在Python客户端中是一个Iterator,用for语句遍历,具体可参阅gRPC 官方文档中文版。
print("Outlier client received: \n") for outlier in stub.GetOutliers(helloworld_pb2.MelbourneRequest(algo='cat', threshold=65)): print(outlier)
完整的程序。
# 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_functionimport loggingimport grpcimport helloworld_pb2import helloworld_pb2_grpc# Class to add authentication header to the meta data of every gRPC call.class GrpcAuth(rpc.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) print("Outlier client received: \n") for outlier in stub.GetOutliers(helloworld_pb2.MelbourneRequest(algo='cat', threshold=65)): print(outlier) if __name__ == '__main__': logging.basicConfig() run()
运行Python客户端程序。
服务器端打印请求的算法与阀值以便调试
Python客户端正确运行
三、Java客户端
演示性质,暂时还是在Java客户端项目中生成存根stub类,然后拷贝到Tomcat Web项目中,用Java客户端项目先测试也要方便一点。
1、proto接口原型文件helloworld.proto。
客户端项目同上篇文章配置,拷贝上面的接口原型文件到Java客户端项目中更新原来的文件。项目名grpcclient 上右键-> Run As -> Maven Build,重新生成Java的stub类。
2、HelloWorldClientSSLAuthMelbourne.java。
Java客户端操作gRPC stream的例子可以参阅grpc-java自带的RouteGuide例子及其proto文件。gRPC stream在Java客户端也是一个java.util.Iterator类,可以通过for()循环去迭代,用hasNext()判断是否结束,用next()去获取下一个返回的MelbourneReply对象,存储为一个java.util.List以便后续处理。注意blockingStub.getOutliers(),与接口原型定义中相比,java方法的首字母按Java函数的惯例都改成了小写。
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.examples.header.HeaderClientInterceptor;import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;import java.util.ArrayList;import java.util.Iterator;import java.util.List;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 HelloWorldClientSSLAuthMelbourne {private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());private final GreeterGrpc.GreeterBlockingStub blockingStub;/** Construct client for accessing HelloWorld server using the existing channel. */public HelloWorldClientSSLAuthMelbourne(Channel channel) { // '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);} /** Get outliers of Melbourne house price. */public List<MelbourneReply> getOutliers(String algo, double threshold) { logger.info("Will try to get outliers for algo " + algo +" with threshold "+ threshold + " ..."); MelbourneRequest request = MelbourneRequest.newBuilder().setAlgo(algo).setThreshold(threshold).build(); Iterator<MelbourneReply>response; List<MelbourneReply> result = new ArrayList<>(); try { response = blockingStub.getOutliers(request); for (int i = 1; response.hasNext(); i++) { MelbourneReply outlier = response.next(); result.add(outlier); System.out.println("Result #" + i + ": "+outlier.getRow()+" | "+ Math.round(outlier.getOrigin()*100.0)/100.0+" | "+Math.round(outlier.getPredict()*100.0)/100.0+" | "+outlier.getSe()); } } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return null; } logger.info("Get outliers of Melbourne house price."); return result;} /** * Greet server. If provided, the first element of {@code args} is the name to use in the * greeting. The second argument is the target server. */public static void main(String[] args) 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. */ ManagedChannel originChannel = NettyChannelBuilder.forAddress("jeanye.cn", 50051) .sslContext(GrpcSslContexts.forClient().trustManager(new File("d:/temp/ca.crt")).build()) .build(); ClientInterceptor interceptor = new HeaderClientInterceptor(); Channel channel = ClientInterceptors.intercept(originChannel, interceptor); try { HelloWorldClientSSLAuthMelbourne client = new HelloWorldClientSSLAuthMelbourne(channel); List<MelbourneReply> res = client.getOutliers("cat",60.0); } finally { // 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); }}}
运行结果:
Java客户端运行正确
四、Tomcat Web App
1、拷贝上面Java客户端项目生成的stub类更新。
GreeterGrpc.java HelloReply.java HelloReplyOrBuilder.java HelloRequest.java HelloRequestOrBuilder.java HelloWorldProto.java MelbourneReply.java MelbourneReplyOrBuilder.javaMelbourneRequest.java MelbourneRequestOrBuilder.java
2、HelloWorldClientSSLAuthMelbourne.java。
参考上面的Java客户端编写,实现gRPC调用的帮助类。
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.ArrayList;import java.util.Iterator;import java.util.List;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 HelloWorldClientSSLAuthMelbourne { 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 HelloWorldClientSSLAuthMelbourne(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()); } /** Get ooutliers of Melbourne house price. */ public List<MelbourneReply> getOutliers(String algo, double threshold) { logger.info("Will try to get outliers for algo " + algo + " with threshold " + threshold + " ..."); MelbourneRequest request = MelbourneRequest.newBuilder().setAlgo(algo).setThreshold(threshold).build(); Iterator<MelbourneReply> response; List<MelbourneReply> result = new ArrayList<>(); try { response = blockingStub.getOutliers(request); for (int i = 1; response.hasNext(); i++) { MelbourneReply outlier = response.next(); result.add(outlier); System.out.println("Result #" + i + ": " + outlier.getRow() + " | " + outlier.getOrigin() + " | " + outlier.getPredict() + " | " + outlier.getSe()); } } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return null; } logger.info("Get outliers of Melbourne house price."); return result; } /** 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(); } }}
3、algo.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="melbourne.jsp" method="post"> 墨尔本房价gRPC调用示例 <br/>算法:<select name="algo" id="algo"> <option value="svm">SVM</option> <option value="rf">RandomForest</option> <option value="gbr">GBR</option> <option value="xgb">XGB</option> <option value="lgbm">LigthGBM</option> <option value="cat" selected>CatBoost</option> <option value="blend">Blend</option> </select> <br/>异常值阀值:<input type="number" name="threshold" value=60><br/> <input type="submit" value="查看异常值"> </form> </center></body></html>
4、melbourne.jsp。
显示gRPC调用结果及匹配的原始数据,所以项目中打包了原始数据文件(预处理)Melbourne_housing_pre.csv。
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%><%@ page import="io.grpc.examples.helloworld.*,java.util.*,java.io.BufferedReader,java.io.FileReader"%> <%List<MelbourneReply> resp =null;Set<Long> set = new HashSet<Long>();try{ String algo = request.getParameter("algo"); Double threshold = new Double(request.getParameter("threshold")); if (algo==null) algo = "cat"; //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); HelloWorldClientSSLAuthMelbourne client = new HelloWorldClientSSLAuthMelbourne(caFile,server,port); resp = client.getOutliers(algo, threshold); client.close(); }catch(Exception e){ e.printStackTrace();}%> <!DOCTYPE html><html><head><meta charset="GBK"><title>Java EE 调用gRPC 测试</title></head><body><h3>Java gRPC调用服务器端Python程序结果<a href="algo.jsp">返回</a></h3><%if (resp !=null){%><table boder="1" cellspacing="0" bordercolor="#D3D3D3"><tr><td>行号</td><td>真实值</td><td>预测值</td><td>偏差%</td></tr><% for(Iterator it=resp.iterator();it.hasNext();){ MelbourneReply outlier = (MelbourneReply)it.next(); set.add(outlier.getRow());%><tr><td><%= outlier.getRow()%></td><td><%=Math.round(outlier.getOrigin()*100.0)/100.0%></td><td><%= Math.round(outlier.getPredict()*100.0)/100.0%></td><td><%= outlier.getSe()%></td></tr><% }%></table><%}%><br><h3>匹配的原始数据</h3><table border="1" cellspacing="0" bordercolor="#D3D3D3"><tr><% String path = request.getRealPath("/WEB-INF");System.out.println(path);BufferedReader reader = new BufferedReader(new FileReader(path+"/Melbourne_housing_pre.csv"));// skip headerString header[] =reader.readLine().split(",");header = "Row";for (int i=0; i<header.length;i++){%><td><%=header%></td><%}%></tr><%String line = null;long index=0; //读取每行,直到为空while((line=reader.readLine())!=null){ if (set.contains(index)){ String items[] = line.split(",");%><tr><% for (int i=0; i< items.length; i++){%><td><%=items %></td><% }%></tr><% } index++;}%></table><br><a href="algo.jsp">返回</a></body></html>
5、本机测试。
Tomcat本地测试选择算法与阀值
服务器发返回结果正确
6、部署到服务器上运行,见本篇开头的运行效果。
五、用Maven管理Tomcat项目
参考资料。用Maven管理Tomcat项目的好处,一是可以在项目中根据proto接口原型定义生成Java stub类,不用从Java客户端项目中拷贝;二是开发时不用逐个导入依赖的包,Maven会自动解决依赖关系,下载导入相应的包;三是打包成war发布到服务器时,Maven会自动把依赖包放进去。这样就方便多了。程序与上面的Tomcat Web App是一样的,只是项目的组织结构有所不同。
1、建立Maven管理的Web APP项目。
Eclipse->File->New Maven Project,后面要选择archetype,不要选simple project。
建立一个Maven项目
Catalog选All Catalogs,Filter中输入maven-archetype-webapp,项目类型选Group Id: org.apache.maven.archetypes,Artifact Id: maven-archetype-webapp,注意它的版本是1.4。如果Catalog选了Internal,也可以列出来,但它会是内置的1.0版,二者生成的pom.xml是不一样的。All Catalog要联网到各个catalog去下载archetype列表(可以按configure按钮配置),取决于网络的状况,有时候可能会出不来,有时候可能要等几十秒。
选择正确的Group Id和 Artifact Id
Internal自带的1.0版生成不同的pom.xml,本篇用1.4版
为自己的项目定义Group Id与Artifact Id,因为已经建好了grpc项目,用grpcTest演示,所以前后截图的项目名可能会有所不同。
为自己的项目定义Group Id与Artifact Id
项目名上右键->Properties->Java Build Path->Add Library,Server Runtime把Tomcat Server Runtime加入,否则JSP编译报错。JRE System Library把适当的JDK加入,由于我的笔记本上JDK11的版本比服务器上JDK11的版本新,导致服务器上部署时报java.lang.unsupportedclassversionerror,我在笔记本上降为JDK8。此时Maven Dependencies只有基本的junit等,后面再加。
设置Java Build Path
此时可以在项目名上右键->Run As->Run on Server,选择前面实验中配好的Tomcat服务器,把新建的项目发布到Tomcat上,在Eclipse中浏览首页,验证项目正确创建,它会自动生成web.xml。
启动Maven管理的Tomcat项目
2、更新pom.xml。
1)、将java版本设为与build path一样的1.8。
2)、把前面gRPC Java客户端项目中pom.xml的dependency拷贝过来。
3)、因为不了解<pluginManagement>的用法,新建了一个<plugins>,把Java客户端项目中pom.xml的generate-sources一节的plugin拷贝过来,用来生成gRPC的Java stub类源码。参阅资料。
4)、<build>里的<defaultGoal>也可以拷贝过来,generate-sources要排在clean后,其它任务的前面。
然后在项目名上右键->Maven->Update Project,更新Maven项目的Java Build Path引用。如果是本地第一次引用这些包,Maven联网下载可能需要一点时间。
完整的源码如下:
<?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>jean.test</groupId> <artifactId>grpc</artifactId> <versin>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>grpc Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <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> <finalName>grpc</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> <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> </plugins> </build></project>
更新好的包依赖关系
3、生成gRPC的Java stub类。
把前面Java客户端项目中的接口原型定义文件helloworld.proto拷贝过来,在项目名字上右键->Run As->Maven generate-sources,则会执行pom.xml中定义的generate-sources任务生成gRPC stub类。注意如果手工删除了生成的stub类,又没有更新proto原型文件,这个操作会直接跳过,什么都不做。更新一下proto原型文件,让它有个新的时间戳,重新运行即可。Maven有这个右键菜单是因为在pom.xml中定义了它。
生成gRPC的Java stub类
Eclipse中有两个可以编辑proto文件的插件,提供了语法高亮显示等功能,我安装了protobuf-dt,具体安装方法请参阅其项目主页上的说明,它有一个小问题,就是不认识proto文件中的option语句,原因是其打包的descriptor.proto文件比较旧,参考该帖子,通过WinRAR用一个比较新的替换(D:/eclipse/plugins/com.google.eclipse.protobuf_2.3.2.201609161849.jar内的)即可,我用了Anaconda3安装好的一个。
编辑proto文件的插件protobuf-dt
替换descriptor.proto
找到版本比较新的descriptor.proto
另外,Eclipse->Window->Preferences->Protocol Buffer->Compiler中,默认不勾选"Compile .proto files on save"选项,不要改它,因为我们用Run As->Maven gererate-sources来生成,勾选了每次更改保存都调Compiler,然后会报错,应该是还没有配好:
Errors occurred during the build.Errors running builder 'Xtext Project Builder' on project 'grpc'.'void com.google.common.io.Closeables.closeQuietly(java.io.Closeable)'
保存proto文件时不要编译
4、把前面Tomcat Web App项目中的程序文件拷贝到Maven Tomcat Web App项目中。包括前面图中的HelloWorldClient*.java,HeaderClient*.java,*.jsp,以及WEB-INF下的资源文件ca.crt、Melbourne_housing_pre.csv,详见前面图中的项目结构。
然后在algo.jsp上右键->Run As->Run on Server,发布到前面配好的Tomcat服务器,运行测试,这与平常是一样的。
测试Maven-Tomcat项目
测试Maven-Tomcat项目
5、打包发布到Linux服务器上。
项目名字上右键->Run As->Maven Build,因为pom.xml中定义了<defaultGoal>,直接按Run按钮就可以。
运行Maven Build打包
生成的war文件在项目的target目录下,可以在Eclipse的Console窗口看build过程的输出。
生成war文件发布到Linux服务器
在Linux服务器上测试
六、配置Python服务端开机自启动
# cd /etc/rc.d# vi rc.local# Startup Python gRPC server, added by Jean 2022/12/16.cd /home/jean/grpcTest/usr/lib64/anaconda3/bin/pythongreeter_server_SSL_Auth.py# reboot now
Done.
页:
[1]