概述
当我寻找一种比REST API更好的API通信方法时,我发现了一种叫做gRPC的东西。它看起来不错,因此在制作一个简单的应用程序时,请记下其使用方式。
首先得出结论,使用Python创建服务器可能有点困难。 ..是。
这是因为我期望的消息完成不起作用。
什么是gRPC
gRPC是RPC的实现之一。
什么是RPC?它是克服REST API弱点的通信标准之一。
它被公认为消除了REST痛苦的备选方法之一。
与REST API的最大区别是必须牢固确定参数和返回值的数据类型。
让我感到高兴的是,当编辑或使用API??参数和返回值时,完成工作(应该)有效。
如果您决定API的结构,则会自动生成数据类型。
因此,在查看规范时,我不必担心该项目是字符类型还是数字类型这一事实,并且我认为它更像是实现=规范。
另外,非常希望能够处理与服务器的双向通信(推送类型)。
您无需再进行投票!
正如RPC(远程过程调用)的名称所暗示的那样,感觉就像可以调用函数而不是REST API一样使用它。
试试看
做什么
让我们做个计时器。
与前端Vue和服务器Python配置进行GRPC通信。
它是可以在没有单独的服务器的情况下创建的内容,但是无论如何,我都会强制使用gRPC。
那是示例程序。 h5>
规格 h5>
-
服务器具有计时器功能。
-
计时器启动命令通过gRPC从前台发送。
-
服务器每秒通过gRPC将剩余时间发送给前台。
-
前台在屏幕上显示从服务器发送的剩余时间。
- 服务器具有计时器功能。
- 计时器启动命令通过gRPC从前台发送。
- 服务器每秒通过gRPC将剩余时间发送给前台。
- 前台在屏幕上显示从服务器发送的剩余时间。
我正在将自己制作的内容上传到GitHub。
尝试过的环境(前提)
- Windows 10
- Python 3.6
- Node.js 12.5.0
- npm 6.9.0
- 纱线1.9.4
- 适用于Windows 2.0.0.3的Docker(特使)
(题外话)我第一次使用Docker,但是我很方便死了。
环境建设
我将放入必要的工具。
安装Vue h5>
正面由Vue制成,因此请安装Vue工具。
1 | npm install -g @vue/cli |
我在这里指的是。
协议缓冲区编译器准备
gRPC使用一种称为协议缓冲区的接口定义语言来描述API规范。
大致来说,协议缓冲区是一种语言,它允许您通过仅提取SQL的CREATE TABLE语法来定义函数接口。
安装特定于语言的编译器,以将用协议缓冲区编写的数据定义和函数定义编译为每种语言。
这次使用javascript和Python。
安装JavaScript编译器(gRPC-web) h5>
手动插入。 h6>
下载编译器本身和javascript插件,并将路径传递到其中包含的可执行文件(
要检查它是否已安装,
1 2 | protoc --version libprotoc 3.8.0 |
如果变为
,则暂时可以。
尽管它是用于javascript的,但您也可以吐出Typescript定义文件。万岁。
此处的过程基于此处的官方教程。
安装适用于Python的编译器 h5>
1
pip install grpcio-tools
1 | pip install grpcio-tools |
要检查它是否已安装,
1 2 | python -m grpc_tools.protoc --version libprotoc 3.8.0 |
如果它变成
,就可以了。
这一点令人怀疑。
为什么为Web和python安装了不同的编译器? ..
就个人而言,我希望python的插件是类似于protocol-gen-python.exe的插件,并且编译器本身必须相同。
显然,即使使用pip进行安装,protoc编译器的主体(dll版本)也需要单独下载,因此我想知道客户端和服务器之间的版本是否不同。
API实施
我们将实现协议缓冲区。
timer.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | syntax = "proto3"; package timer_with_grpc; service Timer { rpc StartTimer(StartRequest) returns (stream TimerState); // タイマー開始 rpc StopTimer(Empty) returns (TimerState); // タイマー停止 } message StartRequest { int32 time = 1; } message TimerState { bool isRunning = 1; int32 leftTime = 2; } // 引数空ができないようなので、空用の定義をしておく message Empty { } |
简单。
基本定义是重写示例,这似乎很有可能。
API接口在
此处定义了两个API,
似乎有一个不需要参数的API,但是
1 | rpc StopTimer() returns (TimerState); |
当我尝试
时,发生了错误,因此似乎有一些参数。
这次我定义了一个名为
我不知道这是个好方法。
您还可以通过在返回中添加
编译协议缓冲区
客户端代码生成(Javascript Typescript定义文件) h5>
1
protoc -I=. timer.proto --js_out=import_style=commonjs:. --grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:.
1 | protoc -I=. timer.proto --js_out=import_style=commonjs:. --grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:. |
输出了四个名为
的文件。
我很高兴它甚至可以根据选项正确输出Typescript定义文件。 (添加dts)
我无法正确阅读它,但可以确认
服务器代码生成(Python) h5>
1
python -m grpc_tools.protoc -I. timer.proto --python_out=. --grpc_python_out=.
1 | python -m grpc_tools.protoc -I. timer.proto --python_out=. --grpc_python_out=. |
输出两个文件
由于
服务器端API实施
TimerApi.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import timer_pb2 import timer_pb2_grpc from concurrent import futures import time import grpc # APIのロジック class TimerServicer(timer_pb2_grpc.TimerServicer): leftTime = 0 isRunning = False def StartTimer(self, request, context): self.leftTime = request.time self.isRunning = True while self.leftTime > 0: if self.isRunning: # 途中でStopTimerされてないかチェック yield self.makeTimerState() time.sleep(1) self.leftTime -= 1 else: return self.isRunning = False yield self.makeTimerState() def StopTimer(self, request, context): self.isRunning = False return self.makeTimerState() def makeTimerState(self): return timer_pb2.TimerState( isRunning=self.isRunning, leftTime=self.leftTime ) # サーバーの実行 def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) timer_pb2_grpc.add_TimerServicer_to_server(TimerServicer(), server) server.add_insecure_port('0.0.0.0:8082') server.start() print("Server Start!!") try: while True: time.sleep(1) except KeyboardInterrupt: server.stop(0) if __name__ == '__main__': serve() |
这是添加逻辑的部分。
StartTimer倒计时作为参数接收的秒数。
用StopTimer停止。
客户端实施
在Vue中创建项目 h5>
首先,在Vue中创建一个名为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ vue create client Vue CLI v3.8.4 ┌───────────────────────────┐ │ Update available: 3.9.2 │ └───────────────────────────┘ ? Please pick a preset: Manually select features ? Check the features needed for your project: TS, CSS Pre-processors ? Use class-style component syntax? Yes ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? No ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass) ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No |
除了使用Typescript和scss而不是Babel之外,我默认情况下使用它。
尝试编译。 h5>
现在您已经为客户准备了一个项目,让我们对其进行编译。
编译命令进入
1 | yarn serve |
是
那时,我编译了协议缓冲区
当我尝试对其进行编译时(在client / src下为4个文件),
我收到以下错误。
1 2 3 4 5 6 7 | 1:23 Cannot find module 'google-protobuf'. > 1 | import * as jspb from "google-protobuf" | ^ <<中略>> 10:26 Cannot find module 'grpc-web'. > 10 | import * as grpcWeb from 'grpc-web'; | ^ |
我很生气,因为我没有
1 | yarn add google-protobuf @types/google-protobuf grpc-web |
当我再次编译它时,它过去了。
实施 h5>
应用程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | <template> <div id="app"> <p> - TIMER - </p> <br> <p> {{isTimerRunning ? "実行中" : "停止中"}} </p> <p class="time"> {{leftTime}} </p> <button @click="startTimer">start</button> <button @click="stopTimer">stop</button> </div> </template> <script lang="ts"> import {Component, Vue} from 'vue-property-decorator'; import {TimerClient} from "./timer_grpc_web_pb"; import {Empty, StartRequest, TimerState} from "./timer_pb"; import {ClientReadableStream} from "grpc-web"; @Component({}) export default class App extends Vue { private timerClient: TimerClient; private isTimerRunning: boolean = false; private leftTime: number = 0; constructor() { super(); this.timerClient = new TimerClient('http://' + window.location.hostname + ':8081', null, null); } private startTimer(): void { const request = new StartRequest(); request.setTime(10); // 10秒をセット const stream: ClientReadableStream<TimerState> = this.timerClient.startTimer(request, {}); stream.on('data', (response: TimerState) => { this.isTimerRunning = response.getIsrunning(); this.leftTime = response.getLefttime(); }); } private stopTimer(): void { this.timerClient.stopTimer(new Empty(), {}, (err, response: TimerState) => { this.isTimerRunning = response.getIsrunning(); this.leftTime = response.getLefttime(); }); } } </script> <style lang="scss"> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } .time{ margin:0 0 40px 0; font-size:148px; color:#999; } </style> |
在
Typescript端,使用构造函数实例化API客户端,并使用开始按钮或停止按钮调用相应的API。
计时器启动后,每次时间更改都会从服务器端推送数据,因此秒数将根据该值进行更新。
准备代理服务器
这是官方样品。
老实说我不明白。
我使用了一个名为envoy的代理服务器(这似乎是负载均衡器?)。
当前,似乎无法将gRPC请求直接发送到服务器。 (为什么端口有问题?)
在官方教程中,
并使用以下命令
1 | docker build -t envoy_for_timer -f ./envoy.Dockerfile . |
运行
启动服务器应用程序
1 | python TimerApi.py |
反向代理激活
1 | docker run -d -p 8081:8081 envoy_for_timer |
前台应用程序启动
1 | yarn serve |
启动前端应用程序后,
1 2 3 | App running at: - Local: http://localhost:8080/ - Network: http://192.168.1.22:8080/ |
如果显示
,则应该可以使用上述地址进行访问。
印象数
- 对于那些只接触过REST API的人来说,来自服务器的推入式API接触起来很有趣。
- 我认为,如果您继续与流连接,则可能要在再次执行时取消前一个。
- Python版本的信息较少。我选择Python的目的是使用Django作为与数据库的连接,但是我想知道它是否应该用Go编写。
- 对于Typescript,完成了message变量名的工作。 Python运行不佳。我想知道Python是否没用。 ..
仅此而已。