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

ch4-06: 增加rest内容

This commit is contained in:
chai2010 2018-07-15 07:41:27 +08:00
parent 00a6238fb1
commit 903e87b09b
7 changed files with 680 additions and 3 deletions

View File

@ -148,11 +148,122 @@ func (this *Message) Validate() error {
通过生成的验证函数并结合GRPC的截取器我们可以很容易为每个方法的输入参数和返回值进行验证。
<!--
## REST接口
TODO
GRPC服务一般用于集群内部通信如果需要对外暴露服务一般会提供等价的REST接口。通过REST接口比较方便前端JavaScript和后端交互。开源社区中的grac-gateway项目就实现了将GRPC服务转为REST服务的能力。
grpc-gateway的工作原理如下图
![](../images/ch4-06-grpc-gateway.png)
通过在Protobuf文件中添加路由相关的元信息通过自定义的代码插件生成路由相关的处理代码最终将Rest请求转给更后端的Grpc服务处理。
路由扩展元信息也是通过Protobuf的元数据扩展用法提供
```protobuf
syntax = "proto3";
package main;
import "google/api/annotations.proto";
message StringMessage {
string value = 1;
}
service RestService {
rpc Get(StringMessage) returns (StringMessage) {
option (google.api.http) = {
get: "/get/{value}"
};
}
rpc Post(StringMessage) returns (StringMessage) {
option (google.api.http) = {
post: "/post"
body: "*"
};
}
}
```
我们首先为GRPC定义了Get和Post方法然后通过元扩展语法在对应的方法后添加路由信息。其中“/get/{value}”路径对应的是Get方法`{value}`部分对应参数中的value成员结果通过json格式返回。Post方法对应“/post”路径body中包含json格式的请求信息。
然后通过以下命令安装protoc-gen-grpc-gateway插件
```
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
```
再通过插件生成grpc-gateway必须的路由处理代码
```
$ protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=. \
hello.proto
```
插件会为RestService服务生成对应的RegisterRestServiceHandlerFromEndpoint函数
```go
func RegisterRestServiceHandlerFromEndpoint(
ctx context.Context, mux *runtime.ServeMux, endpoint string,
opts []grpc.DialOption,
) (err error) {
...
}
```
RegisterRestServiceHandlerFromEndpoint函数用于将定义了Rest接口的请求转发到真正的GRPC服务。注册路由处理函数之后就可以启动Web服务了
```go
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
err := RegisterRestServiceHandlerFromEndpoint(
ctx, mux, "localhost:5000",
grpc.WithInsecure(),
)
if err != nil {
log.Fatal(err)
}
http.ListenAndServe(":8080", mux)
}
```
首先通过runtime.NewServeMux()函数创建路由处理器然后通过RegisterRestServiceHandlerFromEndpoint函数将RestService服务相关的REST接口导到后面的GRPC服务。grpc-gateway提供runtime.ServeMux类似同时也实现了http.Handler接口因此可以标准库中的相关函数配置使用。
档GRPC和REST服务全部启动之后就可以用curl请求REST服务了
```
$ curl localhost:8080/get/gopher
{"value":"Get: gopher"}
$ curl localhost:8080/post -X POST --data '{"value":"grpc"}'
{"value":"Post: grpc"}
```
在对外公布REST接口时我们一般还会提供一个Swagger格式的文件用于描述这个接口规范。
```
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ protoc \
-I=. -I=../../../github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=. \
hello.proto
```
然后会生成一个hello.swagger.json文件。这样的话就可以通过swagger-ui这个项目在网页中提供REST接口的文档和测试等功能。
<!--
## Nginx代理

View File

@ -0,0 +1,21 @@
run:
@go build -o a.out && ./a.out
-@rm ./a.out
gen:
protoc \
-I=. -I=../../../github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
helloworld.proto
protoc \
-I=. -I=../../../github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=. \
helloworld.proto
protoc \
-I=. -I=../../../github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=. \
helloworld.proto
clean:
-rm *.pb.go

View File

@ -0,0 +1,190 @@
// 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 _ "google.golang.org/genproto/googleapis/api/annotations"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// 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 StringMessage struct {
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *StringMessage) Reset() { *m = StringMessage{} }
func (m *StringMessage) String() string { return proto.CompactTextString(m) }
func (*StringMessage) ProtoMessage() {}
func (*StringMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_helloworld_b65656966bdaa93a, []int{0}
}
func (m *StringMessage) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StringMessage.Unmarshal(m, b)
}
func (m *StringMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_StringMessage.Marshal(b, m, deterministic)
}
func (dst *StringMessage) XXX_Merge(src proto.Message) {
xxx_messageInfo_StringMessage.Merge(dst, src)
}
func (m *StringMessage) XXX_Size() int {
return xxx_messageInfo_StringMessage.Size(m)
}
func (m *StringMessage) XXX_DiscardUnknown() {
xxx_messageInfo_StringMessage.DiscardUnknown(m)
}
var xxx_messageInfo_StringMessage proto.InternalMessageInfo
func (m *StringMessage) GetValue() string {
if m != nil {
return m.Value
}
return ""
}
func init() {
proto.RegisterType((*StringMessage)(nil), "main.StringMessage")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// RestServiceClient is the client API for RestService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RestServiceClient interface {
Get(ctx context.Context, in *StringMessage, opts ...grpc.CallOption) (*StringMessage, error)
Post(ctx context.Context, in *StringMessage, opts ...grpc.CallOption) (*StringMessage, error)
}
type restServiceClient struct {
cc *grpc.ClientConn
}
func NewRestServiceClient(cc *grpc.ClientConn) RestServiceClient {
return &restServiceClient{cc}
}
func (c *restServiceClient) Get(ctx context.Context, in *StringMessage, opts ...grpc.CallOption) (*StringMessage, error) {
out := new(StringMessage)
err := c.cc.Invoke(ctx, "/main.RestService/Get", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *restServiceClient) Post(ctx context.Context, in *StringMessage, opts ...grpc.CallOption) (*StringMessage, error) {
out := new(StringMessage)
err := c.cc.Invoke(ctx, "/main.RestService/Post", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RestServiceServer is the server API for RestService service.
type RestServiceServer interface {
Get(context.Context, *StringMessage) (*StringMessage, error)
Post(context.Context, *StringMessage) (*StringMessage, error)
}
func RegisterRestServiceServer(s *grpc.Server, srv RestServiceServer) {
s.RegisterService(&_RestService_serviceDesc, srv)
}
func _RestService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StringMessage)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RestServiceServer).Get(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/main.RestService/Get",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RestServiceServer).Get(ctx, req.(*StringMessage))
}
return interceptor(ctx, in, info, handler)
}
func _RestService_Post_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StringMessage)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RestServiceServer).Post(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/main.RestService/Post",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RestServiceServer).Post(ctx, req.(*StringMessage))
}
return interceptor(ctx, in, info, handler)
}
var _RestService_serviceDesc = grpc.ServiceDesc{
ServiceName: "main.RestService",
HandlerType: (*RestServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Get",
Handler: _RestService_Get_Handler,
},
{
MethodName: "Post",
Handler: _RestService_Post_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "helloworld.proto",
}
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_helloworld_b65656966bdaa93a) }
var fileDescriptor_helloworld_b65656966bdaa93a = []byte{
// 192 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, 0x92, 0x49, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x4f, 0x2c, 0xc8, 0xd4, 0x4f,
0xcc, 0xcb, 0xcb, 0x2f, 0x49, 0x2c, 0xc9, 0xcc, 0xcf, 0x2b, 0x86, 0xa8, 0x51, 0x52, 0xe5, 0xe2,
0x0d, 0x2e, 0x29, 0xca, 0xcc, 0x4b, 0xf7, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x15, 0x12, 0xe1,
0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x8c,
0x66, 0x30, 0x72, 0x71, 0x07, 0xa5, 0x16, 0x97, 0x04, 0xa7, 0x16, 0x95, 0x65, 0x26, 0xa7, 0x0a,
0xb9, 0x72, 0x31, 0xbb, 0xa7, 0x96, 0x08, 0x09, 0xeb, 0x81, 0xac, 0xd0, 0x43, 0x31, 0x41, 0x0a,
0x9b, 0xa0, 0x92, 0x48, 0xd3, 0xe5, 0x27, 0x93, 0x99, 0xf8, 0x84, 0x78, 0xf4, 0xd3, 0x53, 0x4b,
0xf4, 0xab, 0xc1, 0xa6, 0xd6, 0x0a, 0x39, 0x71, 0xb1, 0x04, 0xe4, 0x17, 0x93, 0x62, 0x8e, 0x00,
0xd8, 0x1c, 0x2e, 0x25, 0x56, 0xfd, 0x82, 0xfc, 0xe2, 0x12, 0x2b, 0x46, 0xad, 0x24, 0x36, 0xb0,
0x47, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x75, 0x88, 0x64, 0x8b, 0x00, 0x01, 0x00, 0x00,
}

View File

@ -0,0 +1,180 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: helloworld.proto
/*
Package main is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package main
import (
"io"
"net/http"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
)
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
func request_RestService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client RestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StringMessage
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["value"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "value")
}
protoReq.Value, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "value", err)
}
msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func request_RestService_Post_0(ctx context.Context, marshaler runtime.Marshaler, client RestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StringMessage
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.Post(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
// RegisterRestServiceHandlerFromEndpoint is same as RegisterRestServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterRestServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterRestServiceHandler(ctx, mux, conn)
}
// RegisterRestServiceHandler registers the http handlers for service RestService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterRestServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterRestServiceHandlerClient(ctx, mux, NewRestServiceClient(conn))
}
// RegisterRestServiceHandler registers the http handlers for service RestService to "mux".
// The handlers forward requests to the grpc endpoint over the given implementation of "RestServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "RestServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "RestServiceClient" to call the correct interceptors.
func RegisterRestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client RestServiceClient) error {
mux.Handle("GET", pattern_RestService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if cn, ok := w.(http.CloseNotifier); ok {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_RestService_Get_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_RestService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_RestService_Post_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if cn, ok := w.(http.CloseNotifier); ok {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_RestService_Post_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_RestService_Post_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_RestService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"get", "value"}, ""))
pattern_RestService_Post_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"post"}, ""))
)
var (
forward_RestService_Get_0 = runtime.ForwardResponseMessage
forward_RestService_Post_0 = runtime.ForwardResponseMessage
)

View File

@ -0,0 +1,23 @@
syntax = "proto3";
package main;
import "google/api/annotations.proto";
message StringMessage {
string value = 1;
}
service RestService {
rpc Get(StringMessage) returns (StringMessage) {
option (google.api.http) = {
get: "/get/{value}"
};
}
rpc Post(StringMessage) returns (StringMessage) {
option (google.api.http) = {
post: "/post"
body: "*"
};
}
}

View File

@ -0,0 +1,79 @@
{
"swagger": "2.0",
"info": {
"title": "helloworld.proto",
"version": "version not set"
},
"schemes": [
"http",
"https"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/get/{value}": {
"get": {
"operationId": "Get",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/mainStringMessage"
}
}
},
"parameters": [
{
"name": "value",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"RestService"
]
}
},
"/post": {
"post": {
"operationId": "Post",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/mainStringMessage"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/mainStringMessage"
}
}
],
"tags": [
"RestService"
]
}
}
},
"definitions": {
"mainStringMessage": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
}
}
}

View File

@ -0,0 +1,73 @@
package main
import (
"flag"
"log"
"net"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
var (
port = ":5000"
echoEndpoint = flag.String("echo_endpoint", "localhost"+port, "endpoint of YourService")
)
type myGrpcServer struct{}
func (s *myGrpcServer) Get(ctx context.Context, in *StringMessage) (*StringMessage, error) {
return &StringMessage{Value: "Get: " + in.Value}, nil
}
func (s *myGrpcServer) Post(ctx context.Context, in *StringMessage) (*StringMessage, error) {
return &StringMessage{Value: "Post: " + in.Value}, nil
}
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := RegisterRestServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
// $ curl localhost:8080/get/gopher
// {"value":"Get: gopher"}
// $ curl localhost:8080/post -X POST --data '{"value":"grpc"}'
// {"value":"Post: grpc"}
func main() {
flag.Parse()
defer glog.Flush()
go startGrpcServer()
if err := run(); err != nil {
glog.Fatal(err)
}
}
func startGrpcServer() {
server := grpc.NewServer()
RegisterRestServiceServer(server, new(myGrpcServer))
lis, err := net.Listen("tcp", port)
if err != nil {
log.Panicf("could not list on %s: %s", port, err)
}
if err := server.Serve(lis); err != nil {
log.Panicf("grpc serve error: %s", err)
}
}