加载中...
avatar

Grpc系列学习(二)

Grpc系列学习(二)

一切都是RPC

写在前面:因为1024CSDN写博客会有徽章,本人多少带点收集控,故正好整理出第二篇关于Grpc的学习文章.

这次照例宣传一下我的个人博客:

demo:

制作证书

在服务端支持RpcRestful Api,需要用到TLS,因此我们要先制作证书

进入certs目录,生成TLS所需的公钥密钥文件

1
2
3
openssl genrsa -out server.key 2048

openssl ecparam -genkey -name secp384r1 -out server.key
  • openssl genrsa:生成RSA私钥,命令的最后一个参数,将指定生成密钥的位数,如果没有指定,默认512
  • openssl ecparam:生成ECC私钥,命令为椭圆曲线密钥参数生成及操作,本文中ECC曲线选择的是secp384r1

自签名公钥

1
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

openssl req:生成自签名证书,-new指生成证书请求、-sha256指使用sha256加密、-key指定私钥文件、-x509指输出证书、-days 3650为有效期,此后则输入证书拥有者信息

填写信息

1
2
3
4
5
6
7
Country Name (2 letter code) [XX]:
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:grpc server name
Email Address []:

Proto

编写

1.google.api

我们看到proto目录中有google/api目录,它用到了google官方提供的两个api描述文件,主要是针对grpc-gatewayhttp转换提供支持,定义了Protocol Buffer所扩展的HTTP Option

annotations.proto文件:

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
// Copyright (c) 2015, Google Inc.
//
// 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";

package google.api;

import "google/api/http.proto";
import "google/protobuf/descriptor.proto";

option java_multiple_files = true;
option java_outer_classname = "AnnotationsProto";
option java_package = "com.google.api";

extend google.protobuf.MethodOptions {
// See `HttpRule`.
HttpRule http = 72295728;
}

http.proto文件:

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// Copyright 2016 Google Inc.
//
// 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";

package google.api;

option cc_enable_arenas = true;
option java_multiple_files = true;
option java_outer_classname = "HttpProto";
option java_package = "com.google.api";


// Defines the HTTP configuration for a service. It contains a list of
// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
// to one or more HTTP REST API methods.
message Http {
// A list of HTTP rules for configuring the HTTP REST API methods.
repeated HttpRule rules = 1;
}

// Use CustomHttpPattern to specify any HTTP method that is not included in the
// `pattern` field, such as HEAD, or "*" to leave the HTTP method unspecified for
// a given URL path rule. The wild-card rule is useful for services that provide
// content to Web (HTML) clients.
message HttpRule {
// Selects methods to which this rule applies.
//
// Refer to [selector][google.api.DocumentationRule.selector] for syntax details.
string selector = 1;

// Determines the URL pattern is matched by this rules. This pattern can be
// used with any of the {get|put|post|delete|patch} methods. A custom method
// can be defined using the 'custom' field.
oneof pattern {
// Used for listing and getting information about resources.
string get = 2;

// Used for updating a resource.
string put = 3;

// Used for creating a resource.
string post = 4;

// Used for deleting a resource.
string delete = 5;

// Used for updating a resource.
string patch = 6;

// Custom pattern is used for defining custom verbs.
CustomHttpPattern custom = 8;
}

// The name of the request field whose value is mapped to the HTTP body, or
// `*` for mapping all fields not captured by the path pattern to the HTTP
// body. NOTE: the referred field must not be a repeated field.
string body = 7;

// Additional HTTP bindings for the selector. Nested bindings must
// not contain an `additional_bindings` field themselves (that is,
// the nesting may only be one level deep).
repeated HttpRule additional_bindings = 11;
}

// A custom pattern is used for defining custom HTTP verb.
message CustomHttpPattern {
// The name of this custom HTTP verb.
string kind = 1;

// The path matched by this custom verb.
string path = 2;
}
  1. hello.proto

这一小节将编写Demo.proto文件,我们在proto目录下新建hello.proto文件,写入文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
syntax = "proto3";

package proto;

import "google/api/annotations.proto";

service HelloWorld {
rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse) {
option (google.api.http) = {
post: "/hello_world"
body: "*"
};
}
}

message HelloWorldRequest {
string referer = 1;
}

message HelloWorldResponse {
string message = 1;
}

hello.proto文件中,引用了google/api/annotations.proto,达到支持HTTP Option的效果

  • 定义了一个serviceRPC服务HelloWorld,在其内部定义了一个HTTP OptionPOST方法,HTTP响应路径为/hello_world
  • 定义message类型HelloWorldRequestHelloWorldResponse,用于响应请求和返回结果

编译

在编写完.proto文件后,我们需要对其进行编译,就能够在server中使用

进入proto目录,执行以下命令

1
2
3
4
5
6
7
8
# 编译google.api
protoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto

#编译hello_http.proto为hello_http.pb.proto
protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=grpc-hello-world/proto/google/api:. ./hello.proto

#编译hello_http.proto为hello_http.pb.gw.proto
protoc --grpc-gateway_out=logtostderr=true:. ./hello.proto

执行完毕后将生成hello.pb.gohello.gw.pb.go,分别针对grpcgrpc-gateway的功能支持

命令行模块 cmd

介绍

这一小节我们编写命令行模块,为什么要独立出来呢,是为了将cmdserver两者解耦,避免混淆在一起。

我们采用 Cobra 来完成这项功能,Cobra既是创建强大的现代CLI应用程序的库,也是生成应用程序和命令文件的程序。提供了以下功能:

  • 简易的子命令行模式
  • 完全兼容posix的命令行模式(包括短和长版本)
  • 嵌套的子命令
  • 全局、本地和级联flags
  • 使用Cobra很容易的生成应用程序和命令,使用cobra create appnamecobra add cmdname
  • 智能提示
  • 自动生成commands和flags的帮助信息
  • 自动生成详细的help信息-h--help等等
  • 自动生成的bash自动完成功能
  • 为应用程序自动生成手册
  • 命令别名
  • 定义您自己的帮助、用法等的灵活性。
  • 可选与viper紧密集成的apps

—选自某百科

编写server

在编写cmd时需要先用server进行测试关联,因此这一步我们先写server.go用于测试

server模块下 新建server.go文件,写入测试内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package server

import (
"log"
)

var (
ServerPort string
CertName string
CertPemPath string
CertKeyPath string
)

func Serve() (err error){
log.Println(ServerPort)

log.Println(CertName)

log.Println(CertPemPath)

log.Println(CertKeyPath)

return nil
}

编写cmd

cmd模块下 新建root.go文件,写入内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "grpc",
Short: "Run the gRPC hello-world server",
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}

emmm….这个官网的Hello World还是有点多的,今天就不写了~~

新建server.go文件,写入内容:

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
package cmd

import (
"log"

"github.com/spf13/cobra"

"grpc-hello-world/server"
)

var serverCmd = &cobra.Command{
Use: "server",
Short: "Run the gRPC hello-world server",
Run: func(cmd *cobra.Command, args []string) {
defer func() {
if err := recover(); err != nil {
log.Println("Recover error : %v", err)
}
}()

server.Serve()
},
}

func init() {
serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")
serverCmd.Flags().StringVarP(&server.CertPemPath, "cert-pem", "", "./certs/server.pem", "cert pem path")
serverCmd.Flags().StringVarP(&server.CertKeyPath, "cert-key", "", "./certs/server.key", "cert key path")
serverCmd.Flags().StringVarP(&server.CertName, "cert-name", "", "grpc server name", "server's hostname")
rootCmd.AddCommand(serverCmd)
}

我们在grpc-hello-world/目录下,新建文件main.go,写入内容:

1
2
3
4
5
6
7
8
9
package main

import (
"grpc-hello-world/cmd"
)

func main() {
cmd.Execute()
}

讲解

要使用Cobra,按照Cobra标准要创建main.go和一个rootCmd文件,另外我们有子命令server

1、rootCmdrootCmd表示在没有任何子命令的情况下的基本命令

2、&cobra.Command

  • UseCommand的用法,Use是一个行用法消息
  • ShortShorthelp命令输出中显示的简短描述
  • Run:运行:典型的实际工作功能。大多数命令只会实现这一点;另外还有PreRunPreRunEPostRunPostRunE等等不同时期的运行命令,但比较少用,具体使用时再查看亦可

3、rootCmd.AddCommandAddCommand向这父命令(rootCmd)添加一个或多个命令

4、serverCmd.Flags().StringVarP()

一般来说,我们需要在init()函数中定义flags和处理配置,以serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")为例,我们定义了一个flag,值存储在&server.ServerPort中,长命令为--port,短命令为-p,,默认值为50052,命令的描述为server port。这一种调用方式成为Local Flags

我们延伸一下,如果觉得每一个子命令都要设一遍觉得很麻烦,我们可以采用Persistent Flags

1
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

作用:

flag是可以持久的,这意味着该flag将被分配给它所分配的命令以及该命令下的每个命令。对于全局标记,将标记作为根上的持久标志。

另外还有Local Flag on Parent CommandsBind Flags with ConfigRequired flags等等,使用到再 传送 了解即可

测试

回到grpc-hello-world/目录下执行go run main.go server,查看输出是否为(此时应为默认值):

咕咕

@copyright ————baijianruoliorz@Github——————————–

文章作者: liqiqiorz
文章链接: https://yangxiangrui.site/posts/8534/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Blog-YXR
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论