gin 框架实现 GRPC 的服务端和客户端
前提准备
- go 1.19
- GO111MODULE 设置为 on
- GOPROXY 设置为 https://goproxy.io,direct
- 配置自己的GOPATH目录
初始化一个Go Module
mkdir grpc_calculator && cd grpc_calculator
go mod init grpc_calculator
安装 gRPC 和 protobuf 相关依赖
go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
安装完后请检查你配置的GOPATH/bin目录下是否存在 protoc-gen-go-grpc
可执行文件,若是没有,在bin目录直接执行
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
编写 protobuf 文件定义服务和消息
在项目根目录下创建文件 calculator.proto
并且输入
syntax = "proto3";
package calculator;
import "google/api/annotations.proto";
option go_package = "github.com/dyjh/grpc_calculator/calculator";
service Calculator {
rpc Calculate(CalculateRequest) returns (CalculateResponse) {
option (google.api.http) = {
post: "/calculate"
body: "*"
};
}
rpc Compare(CompareRequest) returns (CompareResponse) {
option (google.api.http) = {
post: "/compare"
body: "*"
};
}
}
message CalculateRequest {
float num1 = 1;
float num2 = 2;
Operation operation = 3;
}
message CalculateResponse {
float result = 1;
}
message CompareRequest {
float num1 = 1;
float num2 = 2;
}
message CompareResponse {
float max = 1;
}
enum Operation {
ADD = 0;
SUBTRACT = 1;
MULTIPLY = 2;
DIVIDE = 3;
}
在这里,我们为 Calculate 和 Compare 方法添加了 google.api.http 注释,分别指定了 post: "/calculate" 和 post: "/compare"。在尝试使用 grpc-gateway 时,HTTP 方法(例如 "post")才能正确地与 gRPC 方法关联。
当然,增加了google.api注释,我们需要在项目内手动下载相关的文件 google/api/annotations.proto
,及其依赖文件,下载后存放目录为
生成 Go 代码
在项目根目录下创建文件夹 calculator
, 然后运行以下命令生成 Go 代码:
protoc --go_out=./calculator --go_opt=paths=source_relative --go-grpc_out=./calculator --go-grpc_opt=paths=source_relative --grpc-gateway_out=./calculator --grpc-gateway_opt=paths=source_relative calculator.proto
实现 gRPC 服务端
创建一个名为 server.go 的文件,并添加以下内容:
package main
import (
"context"
"fmt"
"log"
"net"
"github.com/dyjh/grpc_calculator/calculator"
"google.golang.org/grpc"
)
type server struct {
calculator.UnimplementedCalculatorServer
}
func (*server) Calculate(ctx context.Context, req *calculator.CalculateRequest) (*calculator.CalculateResponse, error) {
num1 := req.GetNum1()
num2 := req.GetNum2()
operation := req.GetOperation()
var result float32
switch operation {
case calculator.Operation_ADD:
result = num1 + num2
case calculator.Operation_SUBTRACT:
result = num1 - num2
case calculator.Operation_MULTIPLY:
result = num1 * num2
case calculator.Operation_DIVIDE:
if num2 == 0 {
return nil, fmt.Errorf("division by zero is not allowed")
}
result = num1 / num2
}
return &calculator.CalculateResponse{Result: result}, nil
}
func (*server) Compare(ctx context.Context, req *calculator.CompareRequest) (*calculator.CompareResponse, error) {
num1 := req.GetNum1()
num2 := req.GetNum2()
max := num1
if num2 > num1 {
max = num2
}
return &calculator.CompareResponse{Max: max}, nil
}
func main() {
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
calculator.RegisterCalculatorServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
实现 gRPC 客户端(HTTP)
创建一个名为 client.go 的文件,并添加以下内容:
package main
import (
"context"
"log"
"net/http"
"strconv"
"github.com/dyjh/grpc_calculator/calculator"
"github.com/gin-gonic/gin"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
c := calculator.NewCalculatorClient(conn)
r := gin.Default()
// 路由1:计算操作
r.GET("/calculate/:operation/:num1/:num2", func(ctx *gin.Context) {
num1, err := strconv.ParseFloat(ctx.Param("num1"), 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num1"})
return
}
num2, err := strconv.ParseFloat(ctx.Param("num2"), 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num2"})
return
}
op := ctx.Param("operation")
var operation calculator.Operation
switch op {
case "add":
operation = calculator.Operation_ADD
case "subtract":
operation = calculator.Operation_SUBTRACT
case "multiply":
operation = calculator.Operation_MULTIPLY
case "divide":
operation = calculator.Operation_DIVIDE
default:
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid operation"})
return
}
calculateReq := &calculator.CalculateRequest{
Num1: float32(num1),
Num2: float32(num2),
Operation: operation,
}
calculateRes, err := c.Calculate(context.Background(), calculateReq)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, gin.H{"result": calculateRes.GetResult()})
})
// 路由2:
r.GET("/compare/:num1/:num2", func(ctx *gin.Context) {
num1, err := strconv.ParseFloat(ctx.Param("num1"), 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num1"})
return
}
num2, err := strconv.ParseFloat(ctx.Param("num2"), 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num2"})
return
}
compareReq := &calculator.CompareRequest{
Num1: float32(num1),
Num2: float32(num2),
}
compareRes, err := c.Compare(context.Background(), compareReq)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, gin.H{"max": compareRes.GetMax()})
})
r.Run(":8080")
}
现在,你可以运行 client.go
,通过 HTTP 调用相应的路由。
例如,你可以使用以下 URL 访问计算操作的路由:
http://localhost:8080/calculate/add/10/5
http://localhost:8080/calculate/subtract/10/5
http://localhost:8080/calculate/multiply/10/5
http://localhost:8080/calculate/divide/10/5
项目地址
照旧文章末尾给大家附上demo地址:Github