remove transport draft, add grpc web and gateway support

This commit is contained in:
Adphi 2021-09-18 01:39:15 +02:00
parent 4085420f6f
commit 1eea54f18a
37 changed files with 2884 additions and 290 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
.idea .idea
.bin
transport

View File

@ -1,9 +1,67 @@
MODULE = go.linka.cloud/grpc
.PHONY: clean-example-proto PROTO_BASE_PATH = $(PWD)
clean-example-proto:
@rm example/*.pb.go INCLUDE_PROTO_PATH = -I$(PROTO_BASE_PATH) \
-I $(shell go list -m -f {{.Dir}} google.golang.org/protobuf) \
-I $(shell go list -m -f {{.Dir}} go.linka.cloud/protoc-gen-defaults) \
-I $(shell go list -m -f {{.Dir}} go.linka.cloud/protofilters) \
-I $(shell go list -m -f {{.Dir}} github.com/envoyproxy/protoc-gen-validate) \
-I $(shell go list -m -f {{.Dir}} github.com/alta/protopatch) \
-I $(shell go list -m -f {{.Dir}} github.com/grpc-ecosystem/grpc-gateway/v2)
PROTO_OPTS = paths=source_relative
$(shell mkdir -p .bin)
export GOBIN=$(PWD)/.bin
export PATH := $(GOBIN):$(PATH)
bin:
@go install github.com/golang/protobuf/protoc-gen-go
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
@go install go.linka.cloud/protoc-gen-defaults
@go install go.linka.cloud/protoc-gen-go-fields
@go install github.com/envoyproxy/protoc-gen-validate
@go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
@go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
@go install github.com/alta/protopatch/cmd/protoc-gen-go-patch
@go install github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
clean: clean-bin clean-proto
clean-bin:
@rm -rf .bin
clean-proto:
@find $(PROTO_BASE_PATH) -name '*.pb*.go' -type f -exec rm {} \;
.PHONY: proto
proto: tools.go gen-proto lint
.PHONY: gen-proto
gen-proto: bin
@find $(PROTO_BASE_PATH) -name '*.proto' -type f -not -path "$(PWD)/google/*" -exec \
protoc $(INCLUDE_PROTO_PATH) \
--go-patch_out=plugin=go,$(PROTO_OPTS):. \
--go-patch_out=plugin=go-grpc,$(PROTO_OPTS):. \
--go-patch_out=plugin=defaults,$(PROTO_OPTS):. \
--go-patch_out=plugin=go-fields,$(PROTO_OPTS):. \
--go-patch_out=plugin=grpc-gateway,$(PROTO_OPTS):. \
--go-patch_out=plugin=openapiv2:. \
--go-patch_out=plugin=go-vtproto,features=marshal+unmarshal+size,$(PROTO_OPTS):. \
--go-patch_out=plugin=validate,lang=go,$(PROTO_OPTS):. {} \;
.PHONY: lint
lint:
@goimports -w -local $(MODULE) $(PWD)
@gofmt -w $(PWD)
.PHONY: tests
tests: proto
@go test -v ./...
.PHONY: example-proto
example-proto:
@protoc -I. -Iexample --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. example/example.proto

View File

@ -71,4 +71,4 @@ func New(host ...string) (tls.Certificate, error) {
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes()) return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes())
} }

View File

@ -33,6 +33,9 @@ func New(opts ...Option) (Client, error) {
if c.opts.tlsConfig != nil { if c.opts.tlsConfig != nil {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(c.opts.tlsConfig))) c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(c.opts.tlsConfig)))
} }
if !c.opts.secure {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithInsecure())
}
c.addr = fmt.Sprintf("%s:///%s", c.opts.registry.String(), c.opts.name) c.addr = fmt.Sprintf("%s:///%s", c.opts.registry.String(), c.opts.name)
if c.opts.version != "" { if c.opts.version != "" {
c.addr = c.addr + ":" + strings.TrimSpace(c.opts.version) c.addr = c.addr + ":" + strings.TrimSpace(c.opts.version)

View File

@ -1,6 +1,6 @@
/* /*
Taken from the https://github.com/micro/go-micro/client/grpc Taken from the https://github.com/micro/go-micro/client/grpc
*/ */
package client package client

View File

@ -2,10 +2,13 @@ package main
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"time" "io/ioutil"
"net/http"
"strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -14,7 +17,7 @@ import (
"go.linka.cloud/grpc/service" "go.linka.cloud/grpc/service"
) )
type GreeterHandler struct{ type GreeterHandler struct {
UnimplementedGreeterServer UnimplementedGreeterServer
} }
@ -31,27 +34,29 @@ func (g *GreeterHandler) SayHelloStream(req *HelloStreamRequest, s Greeter_SayHe
if err := s.Send(&HelloReply{Message: fmt.Sprintf("Hello %s (%d)!", req.Name, i+1)}); err != nil { if err := s.Send(&HelloReply{Message: fmt.Sprintf("Hello %s (%d)!", req.Name, i+1)}); err != nil {
return err return err
} }
time.Sleep(time.Second) // time.Sleep(time.Second)
} }
return nil return nil
} }
func main() { func main() {
name := "greeter" name := "greeter"
secure := true
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{}) done := make(chan struct{})
ready := make(chan struct{}) ready := make(chan struct{})
defer cancel() defer cancel()
var svc service.Service var svc service.Service
var err error var err error
address := "0.0.0.0:9991"
svc, err = service.New( svc, err = service.New(
service.WithContext(ctx), service.WithContext(ctx),
service.WithName(name), service.WithName(name),
service.WithVersion("v0.0.1"), service.WithVersion("v0.0.1"),
service.WithAddress("0.0.0.0:9991"), service.WithAddress(address),
service.WithRegistry(mdns.NewRegistry()), service.WithRegistry(mdns.NewRegistry()),
service.WithReflection(true), service.WithReflection(true),
service.WithSecure(true), service.WithSecure(secure),
service.WithAfterStart(func() error { service.WithAfterStart(func() error {
fmt.Println("Server listening on", svc.Options().Address()) fmt.Println("Server listening on", svc.Options().Address())
close(ready) close(ready)
@ -62,6 +67,10 @@ func main() {
close(done) close(done)
return nil return nil
}), }),
service.WithGateway(RegisterGreeterHandler),
service.WithGatewayPrefix("/rest"),
service.WithGRPCWeb(true),
service.WithGRPCWebPrefix("/grpc"),
) )
if err != nil { if err != nil {
panic(err) panic(err)
@ -77,7 +86,7 @@ func main() {
client.WithName("greeter"), client.WithName("greeter"),
client.WithVersion("v0.0.1"), client.WithVersion("v0.0.1"),
client.WithRegistry(mdns.NewRegistry()), client.WithRegistry(mdns.NewRegistry()),
client.WithSecure(true), client.WithSecure(secure),
) )
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -91,7 +100,7 @@ func main() {
logrus.Infof("received message: %s", res.Message) logrus.Infof("received message: %s", res.Message)
stream, err := g.SayHelloStream(context.Background(), &HelloStreamRequest{Name: "test", Count: 10}) stream, err := g.SayHelloStream(context.Background(), &HelloStreamRequest{Name: "test", Count: 10})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
for { for {
m, err := stream.Recv() m, err := stream.Recv()
@ -99,10 +108,38 @@ func main() {
break break
} }
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("received stream message: %s", m.Message) logrus.Infof("received stream message: %s", m.Message)
} }
scheme := "http://"
if secure {
scheme = "https://"
}
httpc := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
req := `{"name":"test"}`
do := func(url, contentType string) {
resp, err := httpc.Post(url, contentType, strings.NewReader(req))
if err != nil {
logrus.Fatal(err)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Fatal(err)
}
logrus.Info(string(b))
}
do(scheme+address+"/rest/api/v1/greeter/hello", "application/json")
do(scheme+address+"/grpc/helloworld.Greeter/SayHello", "application/grpc-web+json")
cancel() cancel()
<-done <-done
} }

View File

@ -0,0 +1,24 @@
// Code generated by protoc-gen-defaults. DO NOT EDIT.
package main
import (
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var (
_ *timestamppb.Timestamp
_ *durationpb.Duration
_ *wrapperspb.BoolValue
)
func (x *HelloRequest) Default() {
}
func (x *HelloReply) Default() {
}
func (x *HelloStreamRequest) Default() {
}

View File

@ -0,0 +1,21 @@
package main
var HelloRequestFields = struct {
Name string
}{
Name: "name",
}
var HelloReplyFields = struct {
Message string
}{
Message: "message",
}
var HelloStreamRequestFields = struct {
Name string
Count string
}{
Name: "name",
Count: "count",
}

View File

@ -1,17 +1,18 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.23.0 // protoc-gen-go v1.27.1
// protoc v3.13.0 // protoc v3.17.3
// source: example/example.proto // source: example/example.proto
package main package main
import ( import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
) )
const ( const (
@ -21,10 +22,6 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type HelloRequest struct { type HelloRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -179,27 +176,32 @@ var File_example_example_proto protoreflect.FileDescriptor
var file_example_example_proto_rawDesc = []byte{ var file_example_example_proto_rawDesc = []byte{
0x0a, 0x15, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x0a, 0x15, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f,
0x72, 0x6c, 0x64, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x72, 0x6c, 0x64, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3e, 0x0a,
0x12, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xb7, 0x01,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x5e, 0x0a, 0x08, 0x53, 0x61, 0x79,
0x3e, 0x0a, 0x12, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c,
0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22,
0x97, 0x01, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72,
0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3a, 0x01, 0x2a, 0x12, 0x4c, 0x0a, 0x0e, 0x53, 0x61, 0x79,
0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x65,
0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74,
0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0e, 0x53, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65,
0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x6c, 0x69,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6b, 0x61, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65,
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x42, 0x0e, 0x5a, 0x0c, 0x65, 0x78, 0x61, 0x74, 0x6f, 0x33,
0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
} }
var ( var (

167
example/example.pb.gw.go Normal file
View File

@ -0,0 +1,167 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: example/example.proto
/*
Package main is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package main
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Greeter_SayHello_0(ctx context.Context, marshaler runtime.Marshaler, client GreeterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq HelloRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SayHello(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Greeter_SayHello_0(ctx context.Context, marshaler runtime.Marshaler, server GreeterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq HelloRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SayHello(ctx, &protoReq)
return msg, metadata, err
}
// RegisterGreeterHandlerServer registers the http handlers for service Greeter to "mux".
// UnaryRPC :call GreeterServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterGreeterHandlerFromEndpoint instead.
func RegisterGreeterHandlerServer(ctx context.Context, mux *runtime.ServeMux, server GreeterServer) error {
mux.Handle("POST", pattern_Greeter_SayHello_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/helloworld.Greeter/SayHello", runtime.WithHTTPPathPattern("/api/v1/greeter/hello"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Greeter_SayHello_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Greeter_SayHello_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterGreeterHandlerFromEndpoint is same as RegisterGreeterHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterGreeterHandlerFromEndpoint(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.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterGreeterHandler(ctx, mux, conn)
}
// RegisterGreeterHandler registers the http handlers for service Greeter to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterGreeterHandler(ctx context.Context, mux *runtime.ServeMux, cc grpc.ClientConnInterface) error {
return RegisterGreeterHandlerClient(ctx, mux, NewGreeterClient(cc))
}
// RegisterGreeterHandlerClient registers the http handlers for service Greeter
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "GreeterClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "GreeterClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "GreeterClient" to call the correct interceptors.
func RegisterGreeterHandlerClient(ctx context.Context, mux *runtime.ServeMux, client GreeterClient) error {
mux.Handle("POST", pattern_Greeter_SayHello_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/helloworld.Greeter/SayHello", runtime.WithHTTPPathPattern("/api/v1/greeter/hello"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Greeter_SayHello_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Greeter_SayHello_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Greeter_SayHello_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "greeter", "hello"}, ""))
)
var (
forward_Greeter_SayHello_0 = runtime.ForwardResponseMessage
)

View File

@ -0,0 +1,313 @@
// Code generated by protoc-gen-validate. DO NOT EDIT.
// source: example/example.proto
package main
import (
"bytes"
"errors"
"fmt"
"net"
"net/mail"
"net/url"
"regexp"
"strings"
"time"
"unicode/utf8"
"google.golang.org/protobuf/types/known/anypb"
)
// ensure the imports are used
var (
_ = bytes.MinRead
_ = errors.New("")
_ = fmt.Print
_ = utf8.UTFMax
_ = (*regexp.Regexp)(nil)
_ = (*strings.Reader)(nil)
_ = net.IPv4len
_ = time.Duration(0)
_ = (*url.URL)(nil)
_ = (*mail.Address)(nil)
_ = anypb.Any{}
)
// Validate checks the field values on HelloRequest with the rules defined in
// the proto definition for this message. If any rules are violated, an error
// is returned. When asked to return all errors, validation continues after
// first violation, and the result is a list of violation errors wrapped in
// HelloRequestMultiError, or nil if none found. Otherwise, only the first
// error is returned, if any.
func (m *HelloRequest) Validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Name
if len(errors) > 0 {
return HelloRequestMultiError(errors)
}
return nil
}
// HelloRequestMultiError is an error wrapping multiple validation errors
// returned by HelloRequest.Validate(true) if the designated constraints
// aren't met.
type HelloRequestMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m HelloRequestMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m HelloRequestMultiError) AllErrors() []error { return m }
// HelloRequestValidationError is the validation error returned by
// HelloRequest.Validate if the designated constraints aren't met.
type HelloRequestValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e HelloRequestValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e HelloRequestValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e HelloRequestValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e HelloRequestValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e HelloRequestValidationError) ErrorName() string { return "HelloRequestValidationError" }
// Error satisfies the builtin error interface
func (e HelloRequestValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sHelloRequest.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = HelloRequestValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = HelloRequestValidationError{}
// Validate checks the field values on HelloReply with the rules defined in the
// proto definition for this message. If any rules are violated, an error is
// returned. When asked to return all errors, validation continues after first
// violation, and the result is a list of violation errors wrapped in
// HelloReplyMultiError, or nil if none found. Otherwise, only the first error
// is returned, if any.
func (m *HelloReply) Validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Message
if len(errors) > 0 {
return HelloReplyMultiError(errors)
}
return nil
}
// HelloReplyMultiError is an error wrapping multiple validation errors
// returned by HelloReply.Validate(true) if the designated constraints aren't met.
type HelloReplyMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m HelloReplyMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m HelloReplyMultiError) AllErrors() []error { return m }
// HelloReplyValidationError is the validation error returned by
// HelloReply.Validate if the designated constraints aren't met.
type HelloReplyValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e HelloReplyValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e HelloReplyValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e HelloReplyValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e HelloReplyValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e HelloReplyValidationError) ErrorName() string { return "HelloReplyValidationError" }
// Error satisfies the builtin error interface
func (e HelloReplyValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sHelloReply.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = HelloReplyValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = HelloReplyValidationError{}
// Validate checks the field values on HelloStreamRequest with the rules
// defined in the proto definition for this message. If any rules are
// violated, an error is returned. When asked to return all errors, validation
// continues after first violation, and the result is a list of violation
// errors wrapped in HelloStreamRequestMultiError, or nil if none found.
// Otherwise, only the first error is returned, if any.
func (m *HelloStreamRequest) Validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Name
// no validation rules for Count
if len(errors) > 0 {
return HelloStreamRequestMultiError(errors)
}
return nil
}
// HelloStreamRequestMultiError is an error wrapping multiple validation errors
// returned by HelloStreamRequest.Validate(true) if the designated constraints
// aren't met.
type HelloStreamRequestMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m HelloStreamRequestMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m HelloStreamRequestMultiError) AllErrors() []error { return m }
// HelloStreamRequestValidationError is the validation error returned by
// HelloStreamRequest.Validate if the designated constraints aren't met.
type HelloStreamRequestValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e HelloStreamRequestValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e HelloStreamRequestValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e HelloStreamRequestValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e HelloStreamRequestValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e HelloStreamRequestValidationError) ErrorName() string {
return "HelloStreamRequestValidationError"
}
// Error satisfies the builtin error interface
func (e HelloStreamRequestValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sHelloStreamRequest.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = HelloStreamRequestValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = HelloStreamRequestValidationError{}

View File

@ -2,10 +2,17 @@ syntax = "proto3";
package helloworld; package helloworld;
option go_package = "example;main"; option go_package = "go.linka.cloud/grpc/example;main";
import "google/api/annotations.proto";
service Greeter { service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/api/v1/greeter/hello"
body: "*"
};
}
rpc SayHelloStream (HelloStreamRequest) returns (stream HelloReply) {} rpc SayHelloStream (HelloStreamRequest) returns (stream HelloReply) {}
} }

View File

@ -0,0 +1,97 @@
{
"swagger": "2.0",
"info": {
"title": "example/example.proto",
"version": "version not set"
},
"tags": [
{
"name": "Greeter"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/api/v1/greeter/hello": {
"post": {
"operationId": "Greeter_SayHello",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/helloworldHelloReply"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helloworldHelloRequest"
}
}
],
"tags": [
"Greeter"
]
}
}
},
"definitions": {
"helloworldHelloReply": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"helloworldHelloRequest": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
}
}
}

View File

@ -4,6 +4,7 @@ package main
import ( import (
context "context" context "context"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes" codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status" status "google.golang.org/grpc/status"
@ -11,6 +12,7 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion7
// GreeterClient is the client API for Greeter service. // GreeterClient is the client API for Greeter service.
@ -39,7 +41,7 @@ func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...
} }
func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error) { func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_Greeter_serviceDesc.Streams[0], "/helloworld.Greeter/SayHelloStream", opts...) stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], "/helloworld.Greeter/SayHelloStream", opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -99,7 +101,7 @@ type UnsafeGreeterServer interface {
} }
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv) s.RegisterService(&Greeter_ServiceDesc, srv)
} }
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
@ -141,7 +143,10 @@ func (x *greeterSayHelloStreamServer) Send(m *HelloReply) error {
return x.ServerStream.SendMsg(m) return x.ServerStream.SendMsg(m)
} }
var _Greeter_serviceDesc = grpc.ServiceDesc{ // Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Greeter_ServiceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.Greeter", ServiceName: "helloworld.Greeter",
HandlerType: (*GreeterServer)(nil), HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{ Methods: []grpc.MethodDesc{

View File

@ -0,0 +1,566 @@
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// protoc-gen-go-vtproto version: v0.2.0
// source: example/example.proto
package main
import (
fmt "fmt"
io "io"
bits "math/bits"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *HelloRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HelloRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *HelloRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarint(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *HelloReply) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HelloReply) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *HelloReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Message) > 0 {
i -= len(m.Message)
copy(dAtA[i:], m.Message)
i = encodeVarint(dAtA, i, uint64(len(m.Message)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *HelloStreamRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HelloStreamRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *HelloStreamRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.Count != 0 {
i = encodeVarint(dAtA, i, uint64(m.Count))
i--
dAtA[i] = 0x10
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarint(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarint(dAtA []byte, offset int, v uint64) int {
offset -= sov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *HelloRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
func (m *HelloReply) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Message)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
func (m *HelloStreamRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if m.Count != 0 {
n += 1 + sov(uint64(m.Count))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
func sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func soz(x uint64) (n int) {
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HelloRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HelloRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HelloReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HelloReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Message = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HelloStreamRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HelloStreamRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType)
}
m.Count = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Count |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skip(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLength
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLength
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
)

24
go.mod
View File

@ -3,21 +3,33 @@ module go.linka.cloud/grpc
go 1.13 go 1.13
require ( require (
github.com/alta/protopatch v0.3.4
github.com/envoyproxy/protoc-gen-validate v0.6.0
github.com/fullstorydev/grpchan v1.0.2-0.20201120232431-d0ab778aeebd github.com/fullstorydev/grpchan v1.0.2-0.20201120232431-d0ab778aeebd
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
github.com/iancoleman/strcase v0.2.0 // indirect
github.com/improbable-eng/grpc-web v0.14.1
github.com/jinzhu/gorm v1.9.12 github.com/jinzhu/gorm v1.9.12
github.com/lyft/protoc-gen-star v0.6.0 // indirect
github.com/miekg/dns v1.1.35 github.com/miekg/dns v1.1.35
github.com/planetscale/vtprotobuf v0.2.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.6.2 github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
go.linka.cloud/protoc-gen-defaults v0.1.0
go.linka.cloud/protoc-gen-go-fields v0.1.1
go.linka.cloud/protofilters v0.2.2
go.uber.org/multierr v1.7.0 go.uber.org/multierr v1.7.0
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/net v0.0.0-20210525063256-abc453219eb5
golang.org/x/sys v0.0.0-20201126233918-771906719818 // indirect google.golang.org/genproto v0.0.0-20210916144049-3192f974c780
golang.org/x/text v0.3.4 // indirect google.golang.org/grpc v1.40.0
google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
google.golang.org/grpc v1.33.2 google.golang.org/protobuf v1.27.1
google.golang.org/protobuf v1.26.0
) )
replace github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/linka-cloud/grpc-gateway/v2 v2.5.1-0.20210917084803-33b6d54c9e11

552
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
// Copyright 2015 Google LLC
//
// 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 go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
option java_multiple_files = true;
option java_outer_classname = "AnnotationsProto";
option java_package = "com.google.api";
option objc_class_prefix = "GAPI";
extend google.protobuf.MethodOptions {
// See `HttpRule`.
HttpRule http = 72295728;
}

View File

@ -0,0 +1,90 @@
// Copyright 2018 Google LLC
//
// 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/protobuf/descriptor.proto";
option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
option java_multiple_files = true;
option java_outer_classname = "FieldBehaviorProto";
option java_package = "com.google.api";
option objc_class_prefix = "GAPI";
extend google.protobuf.FieldOptions {
// A designation of a specific field behavior (required, output only, etc.)
// in protobuf messages.
//
// Examples:
//
// string name = 1 [(google.api.field_behavior) = REQUIRED];
// State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
// google.protobuf.Duration ttl = 1
// [(google.api.field_behavior) = INPUT_ONLY];
// google.protobuf.Timestamp expire_time = 1
// [(google.api.field_behavior) = OUTPUT_ONLY,
// (google.api.field_behavior) = IMMUTABLE];
repeated google.api.FieldBehavior field_behavior = 1052;
}
// An indicator of the behavior of a given field (for example, that a field
// is required in requests, or given as output but ignored as input).
// This **does not** change the behavior in protocol buffers itself; it only
// denotes the behavior and may affect how API tooling handles the field.
//
// Note: This enum **may** receive new values in the future.
enum FieldBehavior {
// Conventional default for enums. Do not use this.
FIELD_BEHAVIOR_UNSPECIFIED = 0;
// Specifically denotes a field as optional.
// While all fields in protocol buffers are optional, this may be specified
// for emphasis if appropriate.
OPTIONAL = 1;
// Denotes a field as required.
// This indicates that the field **must** be provided as part of the request,
// and failure to do so will cause an error (usually `INVALID_ARGUMENT`).
REQUIRED = 2;
// Denotes a field as output only.
// This indicates that the field is provided in responses, but including the
// field in a request does nothing (the server *must* ignore it and
// *must not* throw an error as a result of the field's presence).
OUTPUT_ONLY = 3;
// Denotes a field as input only.
// This indicates that the field is provided in requests, and the
// corresponding field is not included in output.
INPUT_ONLY = 4;
// Denotes a field as immutable.
// This indicates that the field may be set once in a request to create a
// resource, but may not be changed thereafter.
IMMUTABLE = 5;
// Denotes that a (repeated) field is an unordered list.
// This indicates that the service may provide the elements of the list
// in any arbitrary order, rather than the order the user originally
// provided. Additionally, the list's order may or may not be stable.
UNORDERED_LIST = 6;
// Denotes that this field returns a non-empty default value if not set.
// This indicates that if the user provides the empty value in a request,
// a non-empty value will be returned. The user will not be aware of what
// non-empty value to expect.
NON_EMPTY_DEFAULT = 7;
}

375
google/api/http.proto Normal file
View File

@ -0,0 +1,375 @@
// Copyright 2015 Google LLC
//
// 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 go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
option java_multiple_files = true;
option java_outer_classname = "HttpProto";
option java_package = "com.google.api";
option objc_class_prefix = "GAPI";
// Defines the HTTP configuration for an API 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 configuration rules that apply to individual API methods.
//
// **NOTE:** All service configuration rules follow "last one wins" order.
repeated HttpRule rules = 1;
// When set to true, URL path parameters will be fully URI-decoded except in
// cases of single segment matches in reserved expansion, where "%2F" will be
// left encoded.
//
// The default behavior is to not decode RFC 6570 reserved characters in multi
// segment matches.
bool fully_decode_reserved_expansion = 2;
}
// # gRPC Transcoding
//
// gRPC Transcoding is a feature for mapping between a gRPC method and one or
// more HTTP REST endpoints. It allows developers to build a single API service
// that supports both gRPC APIs and REST APIs. Many systems, including [Google
// APIs](https://github.com/googleapis/googleapis),
// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC
// Gateway](https://github.com/grpc-ecosystem/grpc-gateway),
// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature
// and use it for large scale production services.
//
// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies
// how different portions of the gRPC request message are mapped to the URL
// path, URL query parameters, and HTTP request body. It also controls how the
// gRPC response message is mapped to the HTTP response body. `HttpRule` is
// typically specified as an `google.api.http` annotation on the gRPC method.
//
// Each mapping specifies a URL path template and an HTTP method. The path
// template may refer to one or more fields in the gRPC request message, as long
// as each field is a non-repeated field with a primitive (non-message) type.
// The path template controls how fields of the request message are mapped to
// the URL path.
//
// Example:
//
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get: "/v1/{name=messages/*}"
// };
// }
// }
// message GetMessageRequest {
// string name = 1; // Mapped to URL path.
// }
// message Message {
// string text = 1; // The resource content.
// }
//
// This enables an HTTP REST to gRPC mapping as below:
//
// HTTP | gRPC
// -----|-----
// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")`
//
// Any fields in the request message which are not bound by the path template
// automatically become HTTP query parameters if there is no HTTP request body.
// For example:
//
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get:"/v1/messages/{message_id}"
// };
// }
// }
// message GetMessageRequest {
// message SubMessage {
// string subfield = 1;
// }
// string message_id = 1; // Mapped to URL path.
// int64 revision = 2; // Mapped to URL query parameter `revision`.
// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`.
// }
//
// This enables a HTTP JSON to RPC mapping as below:
//
// HTTP | gRPC
// -----|-----
// `GET /v1/messages/123456?revision=2&sub.subfield=foo` |
// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield:
// "foo"))`
//
// Note that fields which are mapped to URL query parameters must have a
// primitive type or a repeated primitive type or a non-repeated message type.
// In the case of a repeated type, the parameter can be repeated in the URL
// as `...?param=A&param=B`. In the case of a message type, each field of the
// message is mapped to a separate parameter, such as
// `...?foo.a=A&foo.b=B&foo.c=C`.
//
// For HTTP methods that allow a request body, the `body` field
// specifies the mapping. Consider a REST update method on the
// message resource collection:
//
// service Messaging {
// rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
// option (google.api.http) = {
// patch: "/v1/messages/{message_id}"
// body: "message"
// };
// }
// }
// message UpdateMessageRequest {
// string message_id = 1; // mapped to the URL
// Message message = 2; // mapped to the body
// }
//
// The following HTTP JSON to RPC mapping is enabled, where the
// representation of the JSON in the request body is determined by
// protos JSON encoding:
//
// HTTP | gRPC
// -----|-----
// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id:
// "123456" message { text: "Hi!" })`
//
// The special name `*` can be used in the body mapping to define that
// every field not bound by the path template should be mapped to the
// request body. This enables the following alternative definition of
// the update method:
//
// service Messaging {
// rpc UpdateMessage(Message) returns (Message) {
// option (google.api.http) = {
// patch: "/v1/messages/{message_id}"
// body: "*"
// };
// }
// }
// message Message {
// string message_id = 1;
// string text = 2;
// }
//
//
// The following HTTP JSON to RPC mapping is enabled:
//
// HTTP | gRPC
// -----|-----
// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id:
// "123456" text: "Hi!")`
//
// Note that when using `*` in the body mapping, it is not possible to
// have HTTP parameters, as all fields not bound by the path end in
// the body. This makes this option more rarely used in practice when
// defining REST APIs. The common usage of `*` is in custom methods
// which don't use the URL at all for transferring data.
//
// It is possible to define multiple HTTP methods for one RPC by using
// the `additional_bindings` option. Example:
//
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get: "/v1/messages/{message_id}"
// additional_bindings {
// get: "/v1/users/{user_id}/messages/{message_id}"
// }
// };
// }
// }
// message GetMessageRequest {
// string message_id = 1;
// string user_id = 2;
// }
//
// This enables the following two alternative HTTP JSON to RPC mappings:
//
// HTTP | gRPC
// -----|-----
// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")`
// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id:
// "123456")`
//
// ## Rules for HTTP mapping
//
// 1. Leaf request fields (recursive expansion nested messages in the request
// message) are classified into three categories:
// - Fields referred by the path template. They are passed via the URL path.
// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP
// request body.
// - All other fields are passed via the URL query parameters, and the
// parameter name is the field path in the request message. A repeated
// field can be represented as multiple query parameters under the same
// name.
// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields
// are passed via URL path and HTTP request body.
// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all
// fields are passed via URL path and URL query parameters.
//
// ### Path template syntax
//
// Template = "/" Segments [ Verb ] ;
// Segments = Segment { "/" Segment } ;
// Segment = "*" | "**" | LITERAL | Variable ;
// Variable = "{" FieldPath [ "=" Segments ] "}" ;
// FieldPath = IDENT { "." IDENT } ;
// Verb = ":" LITERAL ;
//
// The syntax `*` matches a single URL path segment. The syntax `**` matches
// zero or more URL path segments, which must be the last part of the URL path
// except the `Verb`.
//
// The syntax `Variable` matches part of the URL path as specified by its
// template. A variable template must not contain other variables. If a variable
// matches a single path segment, its template may be omitted, e.g. `{var}`
// is equivalent to `{var=*}`.
//
// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL`
// contains any reserved character, such characters should be percent-encoded
// before the matching.
//
// If a variable contains exactly one path segment, such as `"{var}"` or
// `"{var=*}"`, when such a variable is expanded into a URL path on the client
// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The
// server side does the reverse decoding. Such variables show up in the
// [Discovery
// Document](https://developers.google.com/discovery/v1/reference/apis) as
// `{var}`.
//
// If a variable contains multiple path segments, such as `"{var=foo/*}"`
// or `"{var=**}"`, when such a variable is expanded into a URL path on the
// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded.
// The server side does the reverse decoding, except "%2F" and "%2f" are left
// unchanged. Such variables show up in the
// [Discovery
// Document](https://developers.google.com/discovery/v1/reference/apis) as
// `{+var}`.
//
// ## Using gRPC API Service Configuration
//
// gRPC API Service Configuration (service config) is a configuration language
// for configuring a gRPC service to become a user-facing product. The
// service config is simply the YAML representation of the `google.api.Service`
// proto message.
//
// As an alternative to annotating your proto file, you can configure gRPC
// transcoding in your service config YAML files. You do this by specifying a
// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same
// effect as the proto annotation. This can be particularly useful if you
// have a proto that is reused in multiple services. Note that any transcoding
// specified in the service config will override any matching transcoding
// configuration in the proto.
//
// Example:
//
// http:
// rules:
// # Selects a gRPC method and applies HttpRule to it.
// - selector: example.v1.Messaging.GetMessage
// get: /v1/messages/{message_id}/{sub.subfield}
//
// ## Special notes
//
// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the
// proto to JSON conversion must follow the [proto3
// specification](https://developers.google.com/protocol-buffers/docs/proto3#json).
//
// While the single segment variable follows the semantics of
// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String
// Expansion, the multi segment variable **does not** follow RFC 6570 Section
// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion
// does not expand special characters like `?` and `#`, which would lead
// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding
// for multi segment variables.
//
// The path variables **must not** refer to any repeated or mapped field,
// because client libraries are not capable of handling such variable expansion.
//
// The path variables **must not** capture the leading "/" character. The reason
// is that the most common use case "{var}" does not capture the leading "/"
// character. For consistency, all path variables must share the same behavior.
//
// Repeated message fields must not be mapped to URL query parameters, because
// no client library can support such complicated mapping.
//
// If an API needs to use a JSON array for request or response body, it can map
// the request or response body to a repeated field. However, some gRPC
// Transcoding implementations may not support this feature.
message HttpRule {
// Selects a method 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 {
// Maps to HTTP GET. Used for listing and getting information about
// resources.
string get = 2;
// Maps to HTTP PUT. Used for replacing a resource.
string put = 3;
// Maps to HTTP POST. Used for creating a resource or performing an action.
string post = 4;
// Maps to HTTP DELETE. Used for deleting a resource.
string delete = 5;
// Maps to HTTP PATCH. Used for updating a resource.
string patch = 6;
// The custom pattern is used for specifying an HTTP method that is not
// included in the `pattern` field, such as HEAD, or "*" to leave the
// HTTP method unspecified for this rule. The wild-card rule is useful
// for services that provide content to Web (HTML) clients.
CustomHttpPattern custom = 8;
}
// The name of the request field whose value is mapped to the HTTP request
// body, or `*` for mapping all request fields not captured by the path
// pattern to the HTTP body, or omitted for not having any HTTP request body.
//
// NOTE: the referred field must be present at the top-level of the request
// message type.
string body = 7;
// Optional. The name of the response field whose value is mapped to the HTTP
// response body. When omitted, the entire response message will be used
// as the HTTP response body.
//
// NOTE: The referred field must be present at the top-level of the response
// message type.
string response_body = 12;
// 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;
}

77
google/api/httpbody.proto Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2015 Google LLC
//
// 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/protobuf/any.proto";
option cc_enable_arenas = true;
option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody";
option java_multiple_files = true;
option java_outer_classname = "HttpBodyProto";
option java_package = "com.google.api";
option objc_class_prefix = "GAPI";
// Message that represents an arbitrary HTTP body. It should only be used for
// payload formats that can't be represented as JSON, such as raw binary or
// an HTML page.
//
//
// This message can be used both in streaming and non-streaming API methods in
// the request as well as the response.
//
// It can be used as a top-level request field, which is convenient if one
// wants to extract parameters from either the URL or HTTP template into the
// request fields and also want access to the raw HTTP body.
//
// Example:
//
// message GetResourceRequest {
// // A unique request id.
// string request_id = 1;
//
// // The raw HTTP body is bound to this field.
// google.api.HttpBody http_body = 2;
// }
//
// service ResourceService {
// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody);
// rpc UpdateResource(google.api.HttpBody) returns
// (google.protobuf.Empty);
// }
//
// Example with streaming methods:
//
// service CaldavService {
// rpc GetCalendar(stream google.api.HttpBody)
// returns (stream google.api.HttpBody);
// rpc UpdateCalendar(stream google.api.HttpBody)
// returns (stream google.api.HttpBody);
// }
//
// Use of this type only changes how the request and response bodies are
// handled, all other features will continue to work unchanged.
message HttpBody {
// The HTTP Content-Type header value specifying the content type of the body.
string content_type = 1;
// The HTTP request/response body as raw binary.
bytes data = 2;
// Application specific response metadata. Must be set in the first response
// for streaming APIs.
repeated google.protobuf.Any extensions = 3;
}

View File

@ -42,4 +42,3 @@ func From(ctx context.Context) Logger {
defaultLogger = logr defaultLogger = logr
return defaultLogger return defaultLogger
} }

View File

@ -54,7 +54,7 @@ func (l *logger) WithField(key string, value interface{}) Logger {
} }
func (l *logger) WithFields(kv ...string) Logger { func (l *logger) WithFields(kv ...string) Logger {
for i := 0; i < len(kv); i+=2 { for i := 0; i < len(kv); i += 2 {
l.FieldLogger = l.FieldLogger.WithField(kv[i], kv[i+1]) l.FieldLogger = l.FieldLogger.WithField(kv[i], kv[i+1])
} }
return l return l

View File

@ -27,9 +27,9 @@ var (
) )
type mdnsTxt struct { type mdnsTxt struct {
Service string Service string
Version string Version string
Metadata map[string]string Metadata map[string]string
} }
type mdnsEntry struct { type mdnsEntry struct {
@ -225,9 +225,9 @@ func (m *mdnsRegistry) Register(service *registry.Service, opts ...registry.Regi
} }
txt, err := encode(&mdnsTxt{ txt, err := encode(&mdnsTxt{
Service: service.Name, Service: service.Name,
Version: service.Version, Version: service.Version,
Metadata: node.Metadata, Metadata: node.Metadata,
}) })
if err != nil { if err != nil {
@ -351,8 +351,8 @@ func (m *mdnsRegistry) GetService(service string, opts ...registry.GetOption) ([
s, ok := serviceMap[txt.Version] s, ok := serviceMap[txt.Version]
if !ok { if !ok {
s = &registry.Service{ s = &registry.Service{
Name: txt.Service, Name: txt.Service,
Version: txt.Version, Version: txt.Version,
} }
} }
addr := "" addr := ""
@ -572,8 +572,8 @@ func (m *mdnsWatcher) Next() (*registry.Result, error) {
} }
service := &registry.Service{ service := &registry.Service{
Name: txt.Service, Name: txt.Service,
Version: txt.Version, Version: txt.Version,
} }
// skip anything without the domain we care about // skip anything without the domain we care about

View File

@ -7,12 +7,11 @@ import (
resolver2 "go.linka.cloud/grpc/resolver" resolver2 "go.linka.cloud/grpc/resolver"
) )
func New() registry.Registry { func New() registry.Registry {
return &noop{} return &noop{}
} }
type noop struct {} type noop struct{}
func (n noop) ResolverBuilder() resolver.Builder { func (n noop) ResolverBuilder() resolver.Builder {
return resolver2.New(n) return resolver2.New(n)
@ -50,11 +49,10 @@ func (n noop) String() string {
return "passthroug" return "passthroug"
} }
type noopWatcher struct {} type noopWatcher struct{}
func (n noopWatcher) Next() (*registry.Result, error) { func (n noopWatcher) Next() (*registry.Result, error) {
return &registry.Result{}, nil return &registry.Result{}, nil
} }
func (n noopWatcher) Stop() {} func (n noopWatcher) Stop() {}

View File

@ -29,10 +29,10 @@ type Registry interface {
} }
type Service struct { type Service struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
Metadata map[string]string `json:"metadata"` Metadata map[string]string `json:"metadata"`
Nodes []*Node `json:"nodes"` Nodes []*Node `json:"nodes"`
} }
type Node struct { type Node struct {

View File

@ -17,7 +17,7 @@ type builder struct {
} }
func (r builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { func (r builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
rslvr := &resolvr{reg: r.reg, target: target, cc: cc} rslvr := &resolvr{reg: r.reg, target: target, cc: cc}
go rslvr.run() go rslvr.run()
return rslvr, nil return rslvr, nil
} }

View File

@ -12,9 +12,9 @@ var cmd = &cobra.Command{
const ( const (
serverAddress = "server_address" serverAddress = "server_address"
secure = "secure" secure = "secure"
reflect = "reflect" reflect = "reflect"
caCert = "ca_cert" caCert = "ca_cert"
serverCert = "server_cert" serverCert = "server_cert"

29
service/gateway.go Normal file
View File

@ -0,0 +1,29 @@
package service
import (
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)
var defaultGatewayOptions = []runtime.ServeMuxOption{
runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) {
return s, true
}),
}
func (s *service) gateway(opts ...runtime.ServeMuxOption) error {
if !s.opts.Gateway() {
return nil
}
mux := runtime.NewServeMux(append(defaultGatewayOptions, opts...)...)
if err := s.opts.gateway(s.opts.ctx, mux, s.inproc); err != nil {
return err
}
if s.opts.gatewayPrefix != "" {
s.mux.Handle(s.opts.gatewayPrefix+"/", http.StripPrefix(s.opts.gatewayPrefix, mux))
} else {
s.mux.Handle("/", mux)
}
return nil
}

View File

@ -7,13 +7,17 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"go.uber.org/multierr" "go.uber.org/multierr"
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/certs" "go.linka.cloud/grpc/certs"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc/transport"
"go.linka.cloud/grpc/utils/addr" "go.linka.cloud/grpc/utils/addr"
) )
@ -46,29 +50,51 @@ GLOBAL OPTIONS:
--db_path DB_PATH --db_path DB_PATH
*/ */
type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, cc grpc.ClientConnInterface) error
type Options interface { type Options interface {
Context() context.Context Context() context.Context
Name() string Name() string
Version() string Version() string
Address() string Address() string
Reflection() bool Reflection() bool
CACert() string CACert() string
Cert() string Cert() string
Key() string Key() string
TLSConfig() *tls.Config TLSConfig() *tls.Config
Secure() bool Secure() bool
Registry() registry.Registry Registry() registry.Registry
DB() *gorm.DB DB() *gorm.DB
BeforeStart() []func() error BeforeStart() []func() error
AfterStart() []func() error AfterStart() []func() error
BeforeStop() []func() error BeforeStop() []func() error
AfterStop() []func() error AfterStop() []func() error
ServerOpts() []grpc.ServerOption ServerOpts() []grpc.ServerOption
ServerInterceptors() []grpc.UnaryServerInterceptor ServerInterceptors() []grpc.UnaryServerInterceptor
StreamServerInterceptors() []grpc.StreamServerInterceptor StreamServerInterceptors() []grpc.StreamServerInterceptor
ClientInterceptors() []grpc.UnaryClientInterceptor ClientInterceptors() []grpc.UnaryClientInterceptor
StreamClientInterceptors() []grpc.StreamClientInterceptor StreamClientInterceptors() []grpc.StreamClientInterceptor
Defaults()
// TODO(adphi): CORS for http handler
GRPCWeb() bool
GRPCWebPrefix() string
GRPCWebOpts() []grpcweb.Option
Gateway() bool
GatewayPrefix() string
GatewayOpts() []runtime.ServeMuxOption
// TODO(adphi): metrics
Default()
} }
func NewOptions() *options { func NewOptions() *options {
@ -78,13 +104,16 @@ func NewOptions() *options {
} }
} }
func (o *options) Defaults() { func (o *options) Default() {
if o.ctx == nil { if o.ctx == nil {
o.ctx = context.Background() o.ctx = context.Background()
} }
if o.address == "" { if o.address == "" {
o.address = "0.0.0.0:0" o.address = "0.0.0.0:0"
} }
if o.transport == nil {
o.transport = &grpc.Server{}
}
} }
type Option func(*options) type Option func(*options)
@ -229,20 +258,60 @@ func WithSubscriberInterceptor(w ...interface{}) Option {
} }
} }
type options struct { func WithGRPCWeb(b bool) Option {
ctx context.Context return func(o *options) {
name string o.grpcWeb = b
version string }
address string }
secure bool
reflection bool
caCert string
cert string
key string
tlsConfig *tls.Config
db *gorm.DB
registry registry.Registry func WithGRPCWebPrefix(prefix string) Option {
return func(o *options) {
o.grpcWebPrefix = strings.TrimSuffix(prefix, "/")
}
}
func WithGRPCWebOpts(opts ...grpcweb.Option) Option {
return func(o *options) {
o.grpcWebOpts = opts
}
}
func WithGateway(fn RegisterGatewayFunc) Option {
return func(o *options) {
o.gateway = fn
}
}
func WithGatewayPrefix(prefix string) Option {
return func(o *options) {
o.gatewayPrefix = strings.TrimSuffix(prefix, "/")
}
}
func WithGatewayOpts(opts ...runtime.ServeMuxOption) Option {
return func(o *options) {
o.gatewayOpts = opts
}
}
type options struct {
ctx context.Context
name string
version string
address string
reflection bool
secure bool
caCert string
cert string
key string
tlsConfig *tls.Config
db *gorm.DB
transport transport.Transport
registry registry.Registry
beforeStart []func() error beforeStart []func() error
afterStart []func() error afterStart []func() error
@ -257,7 +326,15 @@ type options struct {
clientInterceptors []grpc.UnaryClientInterceptor clientInterceptors []grpc.UnaryClientInterceptor
streamClientInterceptors []grpc.StreamClientInterceptor streamClientInterceptors []grpc.StreamClientInterceptor
error error grpcWeb bool
grpcWebOpts []grpcweb.Option
grpcWebPrefix string
gateway RegisterGatewayFunc
gatewayOpts []runtime.ServeMuxOption
error error
gatewayPrefix string
} }
func (o *options) Name() string { func (o *options) Name() string {
@ -344,6 +421,30 @@ func (o *options) StreamClientInterceptors() []grpc.StreamClientInterceptor {
return o.streamClientInterceptors return o.streamClientInterceptors
} }
func (o *options) GRPCWeb() bool {
return o.grpcWeb
}
func (o *options) GRPCWebPrefix() string {
return o.grpcWebPrefix
}
func (o *options) GRPCWebOpts() []grpcweb.Option {
return o.grpcWebOpts
}
func (o *options) Gateway() bool {
return o.gateway != nil
}
func (o *options) GatewayPrefix() string {
return o.gatewayPrefix
}
func (o *options) GatewayOpts() []runtime.ServeMuxOption {
return o.gatewayOpts
}
func (o *options) parseTLSConfig() error { func (o *options) parseTLSConfig() error {
if o.tlsConfig != nil { if o.tlsConfig != nil {
return nil return nil

86
service/register.go Normal file
View File

@ -0,0 +1,86 @@
package service
import (
"net"
"strings"
"time"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc/utils/addr"
"go.linka.cloud/grpc/utils/backoff"
net2 "go.linka.cloud/grpc/utils/net"
)
func (s *service) register() error {
const (
defaultRegisterInterval = time.Second * 30
defaultRegisterTTL = time.Second * 90
)
regFunc := func(service *registry.Service) error {
var regErr error
for i := 0; i < 3; i++ {
// set the ttl
rOpts := []registry.RegisterOption{registry.RegisterTTL(defaultRegisterTTL)}
// attempt to register
if err := s.opts.Registry().Register(service, rOpts...); err != nil {
// set the error
regErr = err
// backoff then retry
time.Sleep(backoff.Do(i + 1))
continue
}
// success so nil error
regErr = nil
break
}
return regErr
}
var err error
var advt, host, port string
// // check the advertise address first
// // if it exists then use it, otherwise
// // use the address
// if len(config.Advertise) > 0 {
// advt = config.Advertise
// } else {
advt = s.opts.address
// }
if cnt := strings.Count(advt, ":"); cnt >= 1 {
// ipv6 address in format [host]:port or ipv4 host:port
host, port, err = net.SplitHostPort(advt)
if err != nil {
return err
}
} else {
host = s.opts.address
}
addr, err := addr.Extract(host)
if err != nil {
return err
}
// register service
node := &registry.Node{
Id: s.opts.name + "-" + s.id,
Address: net2.HostPort(addr, port),
}
s.regSvc = &registry.Service{
Name: s.opts.name,
Version: s.opts.version,
Nodes: []*registry.Node{node},
}
// register the service
if err := regFunc(s.regSvc); err != nil {
return err
}
return nil
}

View File

@ -2,8 +2,10 @@ package service
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
@ -11,21 +13,19 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/fullstorydev/grpchan/inprocgrpc"
"github.com/google/uuid" "github.com/google/uuid"
grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/multierr" "go.uber.org/multierr"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc/registry/noop" "go.linka.cloud/grpc/registry/noop"
"go.linka.cloud/grpc/utils/addr"
"go.linka.cloud/grpc/utils/backoff"
net2 "go.linka.cloud/grpc/utils/net"
) )
type Service interface { type Service interface {
@ -45,14 +45,18 @@ func New(opts ...Option) (Service, error) {
} }
type service struct { type service struct {
cmd *cobra.Command cmd *cobra.Command
opts *options opts *options
cancel context.CancelFunc cancel context.CancelFunc
server *grpc.Server server *grpc.Server
list net.Listener
mu sync.Mutex mu sync.Mutex
running bool running bool
mux *http.ServeMux
// inproc Channel is used to serve grpc gateway
inproc *inprocgrpc.Channel
id string id string
regSvc *registry.Service regSvc *registry.Service
closed chan struct{} closed chan struct{}
@ -63,9 +67,11 @@ func newService(opts ...Option) (*service, error) {
return nil, err return nil, err
} }
s := &service{ s := &service{
opts: parseFlags(NewOptions()), opts: parseFlags(NewOptions()),
cmd: cmd, cmd: cmd,
id: uuid.New().String(), id: uuid.New().String(),
mux: http.NewServeMux(),
inproc: &inprocgrpc.Channel{},
} }
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -87,11 +93,7 @@ func newService(opts ...Option) (*service, error) {
if s.opts.registry == nil { if s.opts.registry == nil {
s.opts.registry = noop.New() s.opts.registry = noop.New()
} }
var err error
s.list, err = net.Listen("tcp", s.opts.address)
if err != nil {
return nil, err
}
if err := s.opts.parseTLSConfig(); err != nil { if err := s.opts.parseTLSConfig(); err != nil {
return nil, err return nil, err
} }
@ -102,18 +104,24 @@ func newService(opts ...Option) (*service, error) {
} }
return s.run() return s.run()
} }
ui := grpcmiddleware.ChainUnaryServer(s.opts.serverInterceptors...)
s.inproc = s.inproc.WithServerUnaryInterceptor(ui)
si := grpcmiddleware.ChainStreamServer(/*TODO(adphi): add to options*/)
s.inproc = s.inproc.WithServerStreamInterceptor(si)
gopts := []grpc.ServerOption{ gopts := []grpc.ServerOption{
grpc.UnaryInterceptor( grpc.StreamInterceptor(si),
grpcmiddleware.ChainUnaryServer(s.opts.serverInterceptors...), grpc.UnaryInterceptor(ui),
),
}
if s.opts.tlsConfig != nil {
gopts = append(gopts, grpc.Creds(credentials.NewTLS(s.opts.tlsConfig)))
} }
s.server = grpc.NewServer(append(gopts, s.opts.serverOpts...)...) s.server = grpc.NewServer(append(gopts, s.opts.serverOpts...)...)
if s.opts.reflection { if s.opts.reflection {
reflection.Register(s.server) reflection.Register(s.server)
} }
if err := s.gateway(s.opts.gatewayOpts...); err != nil {
return nil, err
}
// we do not configure grpc web here as the grpc handlers are not yet registered
return s, nil return s, nil
} }
@ -133,99 +141,69 @@ func (s *service) Cmd() *cobra.Command {
return s.cmd return s.cmd
} }
func (s *service) register() error {
const (
defaultRegisterInterval = time.Second * 30
defaultRegisterTTL = time.Second * 90
)
regFunc := func(service *registry.Service) error {
var regErr error
for i := 0; i < 3; i++ {
// set the ttl
rOpts := []registry.RegisterOption{registry.RegisterTTL(defaultRegisterTTL)}
// attempt to register
if err := s.opts.Registry().Register(service, rOpts...); err != nil {
// set the error
regErr = err
// backoff then retry
time.Sleep(backoff.Do(i + 1))
continue
}
// success so nil error
regErr = nil
break
}
return regErr
}
var err error
var advt, host, port string
//// check the advertise address first
//// if it exists then use it, otherwise
//// use the address
//if len(config.Advertise) > 0 {
// advt = config.Advertise
//} else {
advt = s.opts.address
//}
if cnt := strings.Count(advt, ":"); cnt >= 1 {
// ipv6 address in format [host]:port or ipv4 host:port
host, port, err = net.SplitHostPort(advt)
if err != nil {
return err
}
} else {
host = s.opts.address
}
addr, err := addr.Extract(host)
if err != nil {
return err
}
// register service
node := &registry.Node{
Id: s.opts.name + "-" + s.id,
Address: net2.HostPort(addr, port),
}
s.regSvc = &registry.Service{
Name: s.opts.name,
Version: s.opts.version,
Nodes: []*registry.Node{node},
}
// register the service
if err := regFunc(s.regSvc); err != nil {
return err
}
return nil
}
func (s *service) run() error { func (s *service) run() error {
s.mu.Lock() s.mu.Lock()
s.closed = make(chan struct{}) s.closed = make(chan struct{})
// configure grpc web now that we are ready to go
if err := s.grpcWeb(s.opts.grpcWebOpts...); err != nil {
return err
}
lis, err := net.Listen("tcp", s.opts.address)
if err != nil {
return err
}
if s.opts.tlsConfig != nil {
lis = tls.NewListener(lis, s.opts.tlsConfig)
}
s.opts.address = lis.Addr().String()
mux := cmux.New(lis)
mux.SetReadTimeout(5 * time.Second)
gLis := mux.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
hList := mux.Match(cmux.Any())
for i := range s.opts.beforeStart { for i := range s.opts.beforeStart {
if err := s.opts.beforeStart[i](); err != nil { if err := s.opts.beforeStart[i](); err != nil {
s.mu.Unlock() s.mu.Unlock()
return err return err
} }
} }
s.opts.address = s.list.Addr().String()
if err := s.register(); err != nil { if err := s.register(); err != nil {
return err return err
} }
s.running = true s.running = true
errs := make(chan error) errs := make(chan error, 3)
hServer := &http.Server{
Handler: s.mux,
}
if s.opts.Gateway() || s.opts.grpcWeb {
go func() {
errs <- hServer.Serve(hList)
hServer.Shutdown(s.opts.ctx)
}()
}
go func() { go func() {
errs <- s.server.Serve(s.list) errs <- s.server.Serve(gLis)
}()
go func() {
if err := mux.Serve(); err != nil {
// TODO(adphi): find more elegant solution
if ignoreMuxError(err) {
errs <- nil
return
}
errs <- err
return
}
errs <- nil
}() }()
for i := range s.opts.afterStart { for i := range s.opts.afterStart {
if err := s.opts.afterStart[i](); err != nil { if err := s.opts.afterStart[i](); err != nil {
@ -242,7 +220,7 @@ func (s *service) run() error {
logrus.Warnf("received %v", sig) logrus.Warnf("received %v", sig)
return s.Close() return s.Close()
case err := <-errs: case err := <-errs:
if err != nil{ if err != nil {
logrus.Error(err) logrus.Error(err)
return err return err
} }
@ -297,6 +275,7 @@ func (s *service) Stop() error {
func (s *service) RegisterService(desc *grpc.ServiceDesc, impl interface{}) { func (s *service) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
s.server.RegisterService(desc, impl) s.server.RegisterService(desc, impl)
s.inproc.RegisterService(desc, impl)
} }
func (s *service) Close() error { func (s *service) Close() error {
@ -313,3 +292,11 @@ func (s *service) notify() <-chan os.Signal {
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT) signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT)
return sigs return sigs
} }
func ignoreMuxError(err error) bool {
if err == nil {
return true
}
return strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "mux: server closed")
}

35
service/web.go Normal file
View File

@ -0,0 +1,35 @@
package service
import (
"net/http"
"time"
"github.com/improbable-eng/grpc-web/go/grpcweb"
)
var defaultWebOptions = []grpcweb.Option{
grpcweb.WithWebsockets(true),
grpcweb.WithWebsocketOriginFunc(func(req *http.Request) bool {
return true
}),
grpcweb.WithCorsForRegisteredEndpointsOnly(false),
grpcweb.WithOriginFunc(func(origin string) bool {
return true
}),
grpcweb.WithWebsocketPingInterval(time.Second),
}
func (s *service) grpcWeb(opts ...grpcweb.Option) error {
if !s.opts.grpcWeb {
return nil
}
h := grpcweb.WrapServer(s.server, opts...)
for _, v := range grpcweb.ListGRPCResources(s.server) {
if s.opts.grpcWebPrefix != "" {
s.mux.Handle(s.opts.grpcWebPrefix+v, http.StripPrefix(s.opts.grpcWebPrefix, h))
} else {
s.mux.Handle(v, h)
}
}
return nil
}

31
tools.go Normal file
View File

@ -0,0 +1,31 @@
//go:build tools
// +build tools
// Copyright 2021 Linka Cloud All rights reserved.
//
// 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.
package main
import (
_ "github.com/alta/protopatch/cmd/protoc-gen-go-patch"
_ "github.com/envoyproxy/protoc-gen-validate"
_ "github.com/golang/protobuf/protoc-gen-go"
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
_ "github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto"
_ "go.linka.cloud/protoc-gen-defaults"
_ "go.linka.cloud/protoc-gen-go-fields"
_ "go.linka.cloud/protofilters"
_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
)

View File

@ -1,10 +0,0 @@
package grpc
import (
"go.linka.cloud/grpc/transport"
"google.golang.org/grpc"
)
var (
_ transport.Transport = &grpc.Server{}
)

View File

@ -1,52 +0,0 @@
package inproc
import (
"errors"
"net"
"sync"
"github.com/fullstorydev/grpchan/inprocgrpc"
"go.linka.cloud/grpc/transport"
)
var (
_ transport.Transport = &InProc{}
)
func New() transport.Transport {
return &InProc{stop: make(chan struct{})}
}
type InProc struct {
inprocgrpc.Channel
stop chan struct{}
running bool
mu sync.RWMutex
}
func (i *InProc) Serve(lis net.Listener) error {
i.mu.RLock()
running := i.running
i.mu.RUnlock()
if running {
return errors.New("already running")
}
i.mu.Lock()
i.running = true
i.mu.Unlock()
<-i.stop
return nil
}
func (i *InProc) Stop() {
i.mu.RLock()
running := i.running
i.mu.RUnlock()
if running {
i.stop <- struct{}{}
}
}
func (i *InProc) GracefulStop() {
i.Stop()
}

View File

@ -1,15 +0,0 @@
package transport
import (
"net"
"google.golang.org/grpc"
)
type Transport interface {
grpc.ServiceRegistrar
RegisterService(sd *grpc.ServiceDesc, ss interface{})
Serve(lis net.Listener) error
Stop()
GracefulStop()
}