1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-29 08:12:21 +00:00

ch4: 完善

This commit is contained in:
chai2010 2018-07-14 23:03:45 +08:00
parent f98d1084f1
commit cfe946b219
13 changed files with 458 additions and 6 deletions

View File

@ -314,7 +314,24 @@ func filter(
}
```
不够GRPC框架中只能为每个服务设置一个截取器因此所有对截取工作只能在一个函数中完成。不过开源的grpc-ecosystem项目中的go-grpc-middleware包已经基于GRPC对截取器实现了链式截取器的支持感兴趣的同学可以参考。
不够GRPC框架中只能为每个服务设置一个截取器因此所有对截取工作只能在一个函数中完成。开源的grpc-ecosystem项目中的go-grpc-middleware包已经基于GRPC对截取器实现了链式截取器的支持。
以下是go-grpc-middleware包中链式截取器的简单用法
```go
import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
filter1, filter2, ...
)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
filter1, filter2, ...
)),
)
```
感兴趣的同学可以参考go-grpc-middleware包的代码。
## 和Web服务共存

View File

@ -1,9 +1,157 @@
# 4.6. GRPC扩展
<!--
valiate
gateway
nginx
-->
目前开源社区已经围绕Protobuf和GRPC开发出众多扩展形成了庞大的生态。本节我们将简单介绍验证器、REST接口和Nginx代理等几个比较流行的扩展。
## 验证器
到目前位置我们接触的全部是第三版的Protobuf语法。第二版的Protobuf有个默认值特性可以为字符串或数值类型的成员定义默认值。
我们采用第二版的Protobuf语法创建文件
```protobuf
syntax = "proto2";
package main;
message Message {
optional string name = 1 [default = "gopher"];
optional int32 age = 2 [default = 10];
}
```
默认值语法内置其实是通过Protobuf的扩展选项特性实现。在第三版的Protobuf中不再支持默认值特性但是我们可以通过扩展选项自己定制默认值。
下面是用proto3语法的扩展特性重新改写上述的proto文件
```protobuf
syntax = "proto3";
package main;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
string default_string = 50000;
int32 default_int = 50001;
}
message Message {
string name = 1 [(default_string) = "gopher"];
int32 age = 2[(default_int) = 10];
}
```
其中成员后面的方括号内部的就是扩展语法。重新生成Go语言代码里面会包含扩展选项相关的元信息
```go
var E_DefaultString = &proto.ExtensionDesc{
ExtendedType: (*descriptor.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50000,
Name: "main.default_string",
Tag: "bytes,50000,opt,name=default_string,json=defaultString",
Filename: "helloworld.proto",
}
var E_DefaultInt = &proto.ExtensionDesc{
ExtendedType: (*descriptor.FieldOptions)(nil),
ExtensionType: (*int32)(nil),
Field: 50001,
Name: "main.default_int",
Tag: "varint,50001,opt,name=default_int,json=defaultInt",
Filename: "helloworld.proto",
}
```
我们可以在运行时通过类似反射的技术解析出Message每个成员定义的扩展选项然后从每个扩展的相关联的信息中解析出我们定义的默认值。
在开源社区中github.com/mwitkow/go-proto-validators 基于Protobuf的扩展特性实现了功能较为强大的验证器功能。要使用该验证器首先需要下载其提供的代码生成插件
```
$ go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
```
然后基于go-proto-validators验证器重新定义上述的Message成员的验证规则
```protobuf
syntax = "proto3";
package main;
import "github.com/mwitkow/go-proto-validators/validator.proto";
message Message {
string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
int32 age = 2 [(validator.field) = {int_gt: 0, int_lt: 100}];
}
```
在方括弧表示的成员扩展中validator.field表示扩展是validator包中定义的名为field扩展选项。validator.field的类型是FieldValidator结构体在导入的validator.proto文件中定义。
validator.proto文件的内容如下
```protobuf
syntax = "proto2";
package validator;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
optional FieldValidator field = 65020;
}
message FieldValidator {
// Uses a Golang RE2-syntax regex to match the field contents.
optional string regex = 1;
// Field value of integer strictly greater than this value.
optional int64 int_gt = 2;
// Field value of integer strictly smaller than this value.
optional int64 int_lt = 3;
// ... more ...
}
```
从FieldValidator定义的注释中我们可以知道验证器扩展的一些语法其中regex表示用于字符串验证的正则表达式int_gt和int_lt表示数值的范围。
采用以下的命令生成验证函数代码:
```
protoc \
--proto_path=${GOPATH}/src \
--proto_path=${GOPATH}/src/github.com/google/protobuf/src \
--proto_path=. \
--govalidators_out=. \
hello.proto
```
以上的命令会调用protoc-gen-govalidators程序生成一个名为hello.validator.pb.go的代码
```go
var _regex_Message_ImportantString = regexp.MustCompile("^[a-z]{2,5}$")
func (this *Message) Validate() error {
if !_regex_Message_ImportantString.MatchString(this.ImportantString) {
return go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString))
}
if !(this.Age > 0) {
return go_proto_validators.FieldError("Age", fmt.Errorf(`value '%v' must be greater than '0'`, this.Age))
}
if !(this.Age < 100) {
return go_proto_validators.FieldError("Age", fmt.Errorf(`value '%v' must be less than '100'`, this.Age))
}
return nil
}
```
也就是为Message结构体增加了一个Validate方法用于验证该成员是否满足Protobuf中定义的条件约束。
通过生成的验证函数并结合GRPC的截取器我们可以很容易为每个方法的输入参数和返回值进行验证。
## REST接口
TODO
## Nginx代理
TODO

View File

@ -0,0 +1,10 @@
run:
@go build -o a.out && ./a.out
-@rm ./a.out
gen:
protoc -I . --go_out=plugins=grpc:. helloworld.proto
clean:
-rm *.pb.go

View File

@ -0,0 +1,110 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: helloworld.proto
package main
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Message struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Message) Reset() { *m = Message{} }
func (m *Message) String() string { return proto.CompactTextString(m) }
func (*Message) ProtoMessage() {}
func (*Message) Descriptor() ([]byte, []int) {
return fileDescriptor_helloworld_0196a2ef510324e3, []int{0}
}
func (m *Message) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Message.Unmarshal(m, b)
}
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
}
func (dst *Message) XXX_Merge(src proto.Message) {
xxx_messageInfo_Message.Merge(dst, src)
}
func (m *Message) XXX_Size() int {
return xxx_messageInfo_Message.Size(m)
}
func (m *Message) XXX_DiscardUnknown() {
xxx_messageInfo_Message.DiscardUnknown(m)
}
var xxx_messageInfo_Message proto.InternalMessageInfo
func (m *Message) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *Message) GetAge() int32 {
if m != nil {
return m.Age
}
return 0
}
var E_DefaultString = &proto.ExtensionDesc{
ExtendedType: (*descriptor.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50000,
Name: "main.default_string",
Tag: "bytes,50000,opt,name=default_string,json=defaultString",
Filename: "helloworld.proto",
}
var E_DefaultInt = &proto.ExtensionDesc{
ExtendedType: (*descriptor.FieldOptions)(nil),
ExtensionType: (*int32)(nil),
Field: 50001,
Name: "main.default_int",
Tag: "varint,50001,opt,name=default_int,json=defaultInt",
Filename: "helloworld.proto",
}
func init() {
proto.RegisterType((*Message)(nil), "main.Message")
proto.RegisterExtension(E_DefaultString)
proto.RegisterExtension(E_DefaultInt)
}
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_helloworld_0196a2ef510324e3) }
var fileDescriptor_helloworld_0196a2ef510324e3 = []byte{
// 217 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d,
0xcc, 0xcc, 0x93, 0x52, 0x48, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x25, 0x95, 0xa6,
0xe9, 0xa7, 0xa4, 0x16, 0x27, 0x17, 0x65, 0x16, 0x94, 0xe4, 0x17, 0x41, 0xd4, 0x29, 0x39, 0x72,
0xb1, 0xfb, 0xa6, 0x16, 0x17, 0x27, 0xa6, 0xa7, 0x0a, 0xc9, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6,
0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x3a, 0x71, 0x35, 0x6d, 0x95, 0x60, 0x4b, 0xcf, 0x2f, 0xc8,
0x48, 0x2d, 0x0a, 0x02, 0x8b, 0x0b, 0x89, 0x71, 0x31, 0x27, 0xa6, 0xa7, 0x4a, 0x30, 0x29, 0x30,
0x6a, 0xb0, 0x3a, 0xb1, 0x74, 0x6c, 0x95, 0xe0, 0x0a, 0x02, 0x09, 0x58, 0xb9, 0x71, 0xf1, 0xa5,
0xa4, 0xa6, 0x25, 0x96, 0xe6, 0x94, 0xc4, 0x17, 0x97, 0x14, 0x65, 0xe6, 0xa5, 0x0b, 0xc9, 0xea,
0x41, 0xec, 0xd5, 0x83, 0xd9, 0xab, 0xe7, 0x96, 0x99, 0x9a, 0x93, 0xe2, 0x5f, 0x50, 0x92, 0x99,
0x9f, 0x57, 0x2c, 0x71, 0xa1, 0x8d, 0x19, 0x64, 0x45, 0x10, 0x2f, 0x54, 0x5b, 0x30, 0x58, 0x97,
0x95, 0x03, 0x17, 0x37, 0xcc, 0x9c, 0xcc, 0xbc, 0x12, 0x42, 0x86, 0x5c, 0x04, 0x1b, 0xc2, 0x1a,
0xc4, 0x05, 0xd5, 0xe3, 0x99, 0x57, 0x92, 0xc4, 0x06, 0x56, 0x6a, 0x0c, 0x08, 0x00, 0x00, 0xff,
0xff, 0xce, 0x8c, 0x0b, 0x48, 0x0f, 0x01, 0x00, 0x00,
}

View File

@ -0,0 +1,15 @@
syntax = "proto3";
package main;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
string default_string = 50000;
int32 default_int = 50001;
}
message Message {
string name = 1 [(default_string) = "gopher"];
int32 age = 2[(default_int) = 10];
}

View File

@ -0,0 +1,14 @@
run:
@go build -o a.out && ./a.out
-@rm ./a.out
aa:
cd ../../.. && pwd
gen:
protoc -I=. -I=../../.. --go_out=plugins=grpc:. helloworld.proto
protoc -I=. -I=../../.. --govalidators_out=. helloworld.proto
clean:
-rm *.pb.go

View File

@ -0,0 +1,88 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: helloworld.proto
package main
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/mwitkow/go-proto-validators"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Message struct {
ImportantString string `protobuf:"bytes,1,opt,name=important_string,json=importantString,proto3" json:"important_string,omitempty"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Message) Reset() { *m = Message{} }
func (m *Message) String() string { return proto.CompactTextString(m) }
func (*Message) ProtoMessage() {}
func (*Message) Descriptor() ([]byte, []int) {
return fileDescriptor_helloworld_6a5a39a6dbe7620e, []int{0}
}
func (m *Message) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Message.Unmarshal(m, b)
}
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
}
func (dst *Message) XXX_Merge(src proto.Message) {
xxx_messageInfo_Message.Merge(dst, src)
}
func (m *Message) XXX_Size() int {
return xxx_messageInfo_Message.Size(m)
}
func (m *Message) XXX_DiscardUnknown() {
xxx_messageInfo_Message.DiscardUnknown(m)
}
var xxx_messageInfo_Message proto.InternalMessageInfo
func (m *Message) GetImportantString() string {
if m != nil {
return m.ImportantString
}
return ""
}
func (m *Message) GetAge() int32 {
if m != nil {
return m.Age
}
return 0
}
func init() {
proto.RegisterType((*Message)(nil), "main.Message")
}
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_helloworld_6a5a39a6dbe7620e) }
var fileDescriptor_helloworld_6a5a39a6dbe7620e = []byte{
// 185 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d,
0xcc, 0xcc, 0x93, 0x32, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf,
0x2d, 0xcf, 0x2c, 0xc9, 0xce, 0x2f, 0xd7, 0x4f, 0xcf, 0xd7, 0x05, 0x2b, 0xd1, 0x2d, 0x4b, 0xcc,
0xc9, 0x4c, 0x49, 0x2c, 0xc9, 0x2f, 0x2a, 0xd6, 0x87, 0x33, 0x21, 0xba, 0x95, 0x52, 0xb8, 0xd8,
0x7d, 0x53, 0x8b, 0x8b, 0x13, 0xd3, 0x53, 0x85, 0x6c, 0xb9, 0x04, 0x32, 0x73, 0x0b, 0xf2, 0x8b,
0x4a, 0x12, 0xf3, 0x4a, 0xe2, 0x8b, 0x4b, 0x8a, 0x32, 0xf3, 0xd2, 0x25, 0x18, 0x15, 0x18, 0x35,
0x38, 0x9d, 0x84, 0x1e, 0xdd, 0x97, 0xe7, 0xe3, 0xe2, 0x89, 0x8b, 0x4e, 0xd4, 0xad, 0x8a, 0xad,
0x36, 0xd2, 0x31, 0xad, 0x55, 0x09, 0xe2, 0x87, 0xab, 0x0d, 0x06, 0x2b, 0x15, 0x92, 0xe2, 0x62,
0x4e, 0x4c, 0x4f, 0x95, 0x60, 0x52, 0x60, 0xd4, 0x60, 0x75, 0xe2, 0x78, 0x74, 0x5f, 0x9e, 0x45,
0x80, 0x41, 0x22, 0x25, 0x08, 0x24, 0x98, 0xc4, 0x06, 0xb6, 0xcc, 0x18, 0x10, 0x00, 0x00, 0xff,
0xff, 0xa7, 0x15, 0x2e, 0x91, 0xbe, 0x00, 0x00, 0x00,
}

View File

@ -0,0 +1,10 @@
syntax = "proto3";
package main;
import "github.com/mwitkow/go-proto-validators/validator.proto";
message Message {
string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
int32 age = 2 [(validator.field) = {int_gt: 0, int_lt: 100}];
}

View File

@ -0,0 +1,40 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: helloworld.proto
/*
Package main is a generated protocol buffer package.
It is generated from these files:
helloworld.proto
It has these top-level messages:
Message
*/
package main
import regexp "regexp"
import fmt "fmt"
import go_proto_validators "github.com/mwitkow/go-proto-validators"
import proto "github.com/golang/protobuf/proto"
import math "math"
import _ "github.com/mwitkow/go-proto-validators"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
var _regex_Message_ImportantString = regexp.MustCompile("^[a-z]{2,5}$")
func (this *Message) Validate() error {
if !_regex_Message_ImportantString.MatchString(this.ImportantString) {
return go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString))
}
if !(this.Age > 0) {
return go_proto_validators.FieldError("Age", fmt.Errorf(`value '%v' must be greater than '0'`, this.Age))
}
if !(this.Age < 100) {
return go_proto_validators.FieldError("Age", fmt.Errorf(`value '%v' must be less than '100'`, this.Age))
}
return nil
}