Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4aa0192eba | ||
|
1765be049b | ||
|
8d5d812e32 | ||
|
3f910038a3 | ||
|
a8042adac1 | ||
|
10a3636a9f | ||
|
4e5fbbf7eb | ||
|
59035ab801 | ||
|
d3525ebab3 | ||
|
2674294cbe | ||
|
b20dd16f92 | ||
|
5088c9d916 | ||
|
f62fcaad76 | ||
|
322eaae529 | ||
|
6a33b7576b | ||
|
6e669d4611 | ||
|
95fc625e99 | ||
|
338e0fdf18 | ||
|
5899134b66 | ||
|
da18ea4ab5 | ||
|
459f4c8387 | ||
|
9c57f32f58 | ||
|
ad92e6821e | ||
|
d7f0db04ec | ||
|
e4311c3a10 | ||
|
ee8b6b3114 | ||
|
08da7c1283 | ||
|
6587ae07be | ||
|
1c1dae0642 | ||
|
a0cb105cf6 | ||
|
606b1ff7cf | ||
|
73a8b14145 | ||
|
c0a628d65b | ||
|
e9c2df775a | ||
|
d3a6297b17 | ||
|
7266c62d09 | ||
|
6459cdfc21 | ||
|
ed54384bf4 | ||
|
51560009d2 | ||
|
cf2f8a9a55 | ||
|
97cf2cd7c3 | ||
|
d9fe8f802b | ||
|
b754c33549 | ||
|
59eaa89bac | ||
|
f65694670e | ||
|
1a571b8c82 | ||
|
308673b393 | ||
|
3a454d870a | ||
|
baaa386e27 | ||
|
a619321b64 | ||
|
363fb551af | ||
|
7a87ae0efa | ||
ab692ff590 | |||
|
2b18b11ab1 | ||
|
af096951fc | ||
|
97967cbe14 | ||
|
a6e09c9249 | ||
|
000e25a4b2 | ||
|
7a1cef46b0 | ||
|
a5412dd4a0 | ||
|
f81f66c98b | ||
|
43ed8f58f0 | ||
|
7727b359c8 | ||
|
8e4e710e15 | ||
|
4d4686d9be | ||
|
6d06ee8078 | ||
|
aec1ca6635 | ||
|
235a653f78 | ||
|
d030c78d1c | ||
|
90a9df9b8c | ||
|
070bd40b4c | ||
|
46de3ae9a9 | ||
|
b6833e478d | ||
|
73b0a0ed0e | ||
|
7c4515d748 | ||
|
748c20c979 | ||
|
3573ac818f | ||
|
ed4bce3285 | ||
|
95b8147fa1 | ||
|
f5ac238231 | ||
|
bfdec9e2e3 | ||
|
a2fbf19341 | ||
|
9e23855c37 | ||
|
d65393a799 | ||
|
ef67dc7ceb | ||
|
c317daf6b8 | ||
|
1152c7cb03 | ||
|
2cc18d6edc | ||
|
a58bc8e75c | ||
|
64459c54a1 | ||
|
c60b5a45bb | ||
|
94772a8cc7 | ||
|
52613190b0 | ||
|
a29ce20e31 | ||
|
d59b693fa6 | ||
|
deee4dcd6a | ||
|
ab7d036697 | ||
|
de87dfb6a0 | ||
|
0f70281e28 | ||
|
9d5cde42a3 | ||
|
83f46d9c9d | ||
|
19de02646e | ||
695c546385 |
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
_build
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# vim temp files
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
@@ -6,11 +6,11 @@ import (
|
||||
|
||||
goapi "github.com/micro/go-micro/api"
|
||||
"github.com/micro/go-micro/api/handler"
|
||||
api "github.com/micro/go-micro/api/proto"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/selector"
|
||||
"github.com/micro/go-micro/util/ctx"
|
||||
api "github.com/micro/go-micro/api/proto"
|
||||
)
|
||||
|
||||
type apiHandler struct {
|
||||
|
@@ -8,9 +8,9 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
api "github.com/micro/go-micro/api/proto"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/selector"
|
||||
api "github.com/micro/go-micro/api/proto"
|
||||
)
|
||||
|
||||
func requestToProto(r *http.Request) (*api.Request, error) {
|
||||
|
@@ -31,7 +31,7 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/google/uuid"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
@@ -66,7 +66,7 @@ func New(eventType string, mimeType string, payload interface{}) *Event {
|
||||
EventType: eventType,
|
||||
CloudEventsVersion: CloudEventsVersion,
|
||||
Source: "https://micro.mu",
|
||||
EventID: uuid.NewUUID().String(),
|
||||
EventID: uuid.New().String(),
|
||||
EventTime: &now,
|
||||
ContentType: mimeType,
|
||||
Data: payload,
|
||||
|
@@ -10,10 +10,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/api/handler"
|
||||
proto "github.com/micro/go-micro/api/proto"
|
||||
"github.com/micro/go-micro/util/ctx"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
type event struct {
|
||||
@@ -73,7 +73,7 @@ func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ev := &proto.Event{
|
||||
Name: action,
|
||||
// TODO: dedupe event
|
||||
Id: fmt.Sprintf("%s-%s-%s", topic, action, uuid.NewUUID().String()),
|
||||
Id: fmt.Sprintf("%s-%s-%s", topic, action, uuid.New().String()),
|
||||
Header: make(map[string]*proto.Pair),
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
|
51
broker/common_test.go
Normal file
51
broker/common_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package broker
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost",
|
||||
Port: 6666,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost",
|
||||
Port: 8888,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -14,7 +14,7 @@ import (
|
||||
func newTestRegistry() *memory.Registry {
|
||||
r := memory.NewRegistry()
|
||||
m := r.(*memory.Registry)
|
||||
m.Setup()
|
||||
m.Services = testData
|
||||
return m
|
||||
}
|
||||
|
||||
|
@@ -38,7 +38,7 @@ var addrTestCases = []struct {
|
||||
"default",
|
||||
"check if default Address is set correctly",
|
||||
map[string]string{
|
||||
"nats://localhost:4222": "",
|
||||
"nats://127.0.0.1:4222": "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
51
client/common_test.go
Normal file
51
client/common_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost",
|
||||
Port: 6666,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost",
|
||||
Port: 8888,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -2,12 +2,15 @@ package grpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/json-iterator/go"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"github.com/micro/go-micro/codec/jsonrpc"
|
||||
"github.com/micro/go-micro/codec/protorpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
)
|
||||
|
||||
@@ -52,7 +55,28 @@ func (w wrapCodec) String() string {
|
||||
return w.Codec.Name()
|
||||
}
|
||||
|
||||
func (w wrapCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
b, ok := v.(*bytes.Frame)
|
||||
if ok {
|
||||
return b.Data, nil
|
||||
}
|
||||
return w.Codec.Marshal(v)
|
||||
}
|
||||
|
||||
func (w wrapCodec) Unmarshal(data []byte, v interface{}) error {
|
||||
b, ok := v.(*bytes.Frame)
|
||||
if ok {
|
||||
b.Data = data
|
||||
return nil
|
||||
}
|
||||
return w.Codec.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
b, ok := v.(*bytes.Frame)
|
||||
if ok {
|
||||
return b.Data, nil
|
||||
}
|
||||
return proto.Marshal(v.(proto.Message))
|
||||
}
|
||||
|
||||
@@ -96,3 +120,59 @@ func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
|
||||
func (jsonCodec) Name() string {
|
||||
return "json"
|
||||
}
|
||||
|
||||
type grpcCodec struct {
|
||||
// headers
|
||||
id string
|
||||
target string
|
||||
method string
|
||||
endpoint string
|
||||
|
||||
s grpc.ClientStream
|
||||
c encoding.Codec
|
||||
}
|
||||
|
||||
func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
|
||||
md, err := g.s.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m == nil {
|
||||
m = new(codec.Message)
|
||||
}
|
||||
if m.Header == nil {
|
||||
m.Header = make(map[string]string)
|
||||
}
|
||||
for k, v := range md {
|
||||
m.Header[k] = strings.Join(v, ",")
|
||||
}
|
||||
m.Id = g.id
|
||||
m.Target = g.target
|
||||
m.Method = g.method
|
||||
m.Endpoint = g.endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *grpcCodec) ReadBody(v interface{}) error {
|
||||
if f, ok := v.(*bytes.Frame); ok {
|
||||
return g.s.RecvMsg(f)
|
||||
}
|
||||
return g.s.RecvMsg(v)
|
||||
}
|
||||
|
||||
func (g *grpcCodec) Write(m *codec.Message, v interface{}) error {
|
||||
// if we don't have a body
|
||||
if v != nil {
|
||||
return g.s.SendMsg(v)
|
||||
}
|
||||
// write the body using the framing codec
|
||||
return g.s.SendMsg(&bytes.Frame{m.Body})
|
||||
}
|
||||
|
||||
func (g *grpcCodec) Close() error {
|
||||
return g.s.CloseSend()
|
||||
}
|
||||
|
||||
func (g *grpcCodec) String() string {
|
||||
return g.c.Name()
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -31,8 +32,9 @@ type grpcClient struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
encoding.RegisterCodec(jsonCodec{})
|
||||
encoding.RegisterCodec(bytesCodec{})
|
||||
encoding.RegisterCodec(wrapCodec{jsonCodec{}})
|
||||
encoding.RegisterCodec(wrapCodec{jsonCodec{}})
|
||||
encoding.RegisterCodec(wrapCodec{bytesCodec{}})
|
||||
}
|
||||
|
||||
// secure returns the dial option for whether its a secure or insecure connection
|
||||
@@ -48,6 +50,18 @@ func (g *grpcClient) secure() grpc.DialOption {
|
||||
}
|
||||
|
||||
func (g *grpcClient) next(request client.Request, opts client.CallOptions) (selector.Next, error) {
|
||||
service := request.Service()
|
||||
|
||||
// get proxy
|
||||
if prx := os.Getenv("MICRO_PROXY"); len(prx) > 0 {
|
||||
service = prx
|
||||
}
|
||||
|
||||
// get proxy address
|
||||
if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 {
|
||||
opts.Address = prx
|
||||
}
|
||||
|
||||
// return remote address
|
||||
if len(opts.Address) > 0 {
|
||||
return func() (*registry.Node, error) {
|
||||
@@ -58,7 +72,7 @@ func (g *grpcClient) next(request client.Request, opts client.CallOptions) (sele
|
||||
}
|
||||
|
||||
// get next nodes from the selector
|
||||
next, err := g.opts.Selector.Select(request.Service(), opts.SelectOptions...)
|
||||
next, err := g.opts.Selector.Select(service, opts.SelectOptions...)
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return nil, errors.NotFound("go.micro.client", err.Error())
|
||||
} else if err != nil {
|
||||
@@ -99,7 +113,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
|
||||
|
||||
var grr error
|
||||
|
||||
cc, err := g.pool.getConn(address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)),
|
||||
cc, err := g.pool.getConn(address, grpc.WithDefaultCallOptions(grpc.ForceCodec(cf)),
|
||||
grpc.WithTimeout(opts.DialTimeout), g.secure(),
|
||||
grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
|
||||
@@ -116,7 +130,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
err := cc.Invoke(ctx, methodToGRPC(req.Endpoint(), req.Body()), req.Body(), rsp, grpc.CallContentSubtype(cf.String()))
|
||||
err := cc.Invoke(ctx, methodToGRPC(req.Service(), req.Endpoint()), req.Body(), rsp, grpc.ForceCodec(cf))
|
||||
ch <- microError(err)
|
||||
}()
|
||||
|
||||
@@ -164,7 +178,10 @@ func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client
|
||||
dialCtx, cancel = context.WithCancel(ctx)
|
||||
}
|
||||
defer cancel()
|
||||
cc, err := grpc.DialContext(dialCtx, address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)), g.secure())
|
||||
|
||||
wc := wrapCodec{cf}
|
||||
|
||||
cc, err := grpc.DialContext(dialCtx, address, grpc.WithDefaultCallOptions(grpc.ForceCodec(wc)), g.secure())
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
|
||||
}
|
||||
@@ -175,16 +192,34 @@ func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client
|
||||
ServerStreams: true,
|
||||
}
|
||||
|
||||
st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Endpoint(), req.Body()), grpc.CallContentSubtype(cf.String()))
|
||||
st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Service(), req.Endpoint()))
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error creating stream: %v", err))
|
||||
}
|
||||
|
||||
codec := &grpcCodec{
|
||||
s: st,
|
||||
c: wc,
|
||||
}
|
||||
|
||||
// set request codec
|
||||
if r, ok := req.(*grpcRequest); ok {
|
||||
r.codec = codec
|
||||
}
|
||||
|
||||
rsp := &response{
|
||||
conn: cc,
|
||||
stream: st,
|
||||
codec: cf,
|
||||
gcodec: codec,
|
||||
}
|
||||
|
||||
return &grpcStream{
|
||||
context: ctx,
|
||||
request: req,
|
||||
stream: st,
|
||||
conn: cc,
|
||||
context: ctx,
|
||||
request: req,
|
||||
response: rsp,
|
||||
stream: st,
|
||||
conn: cc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -210,7 +245,7 @@ func (g *grpcClient) maxSendMsgSizeValue() int {
|
||||
return v.(int)
|
||||
}
|
||||
|
||||
func (g *grpcClient) newGRPCCodec(contentType string) (grpc.Codec, error) {
|
||||
func (g *grpcClient) newGRPCCodec(contentType string) (encoding.Codec, error) {
|
||||
codecs := make(map[string]encoding.Codec)
|
||||
if g.opts.Context != nil {
|
||||
if v := g.opts.Context.Value(codecsKey{}); v != nil {
|
||||
@@ -260,7 +295,7 @@ func (g *grpcClient) Options() client.Options {
|
||||
}
|
||||
|
||||
func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message {
|
||||
return newGRPCPublication(topic, msg, "application/octet-stream")
|
||||
return newGRPCPublication(topic, msg, g.opts.ContentType, opts...)
|
||||
}
|
||||
|
||||
func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
|
||||
|
@@ -45,7 +45,7 @@ func TestGRPCClient(t *testing.T) {
|
||||
|
||||
// register service
|
||||
r.Register(®istry.Service{
|
||||
Name: "test",
|
||||
Name: "helloworld",
|
||||
Version: "test",
|
||||
Nodes: []*registry.Node{
|
||||
®istry.Node{
|
||||
@@ -73,7 +73,7 @@ func TestGRPCClient(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, method := range testMethods {
|
||||
req := c.NewRequest("test", method, &pb.HelloRequest{
|
||||
req := c.NewRequest("helloworld", method, &pb.HelloRequest{
|
||||
Name: "John",
|
||||
})
|
||||
|
||||
|
@@ -2,7 +2,6 @@ package grpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
@@ -15,32 +14,28 @@ type grpcRequest struct {
|
||||
contentType string
|
||||
request interface{}
|
||||
opts client.RequestOptions
|
||||
codec codec.Codec
|
||||
}
|
||||
|
||||
func methodToGRPC(method string, request interface{}) string {
|
||||
// service Struct.Method /service.Struct/Method
|
||||
func methodToGRPC(service, method string) string {
|
||||
// no method or already grpc method
|
||||
if len(method) == 0 || method[0] == '/' {
|
||||
return method
|
||||
}
|
||||
// can't operate on nil request
|
||||
t := reflect.TypeOf(request)
|
||||
if t == nil {
|
||||
return method
|
||||
}
|
||||
// dereference
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// get package name
|
||||
pParts := strings.Split(t.PkgPath(), "/")
|
||||
pkg := pParts[len(pParts)-1]
|
||||
|
||||
// assume method is Foo.Bar
|
||||
mParts := strings.Split(method, ".")
|
||||
if len(mParts) != 2 {
|
||||
return method
|
||||
}
|
||||
|
||||
if len(service) == 0 {
|
||||
return fmt.Sprintf("/%s/%s", mParts[0], mParts[1])
|
||||
}
|
||||
|
||||
// return /pkg.Foo/Bar
|
||||
return fmt.Sprintf("/%s.%s/%s", pkg, mParts[0], mParts[1])
|
||||
return fmt.Sprintf("/%s.%s/%s", service, mParts[0], mParts[1])
|
||||
}
|
||||
|
||||
func newGRPCRequest(service, method string, request interface{}, contentType string, reqOpts ...client.RequestOption) client.Request {
|
||||
@@ -80,7 +75,7 @@ func (g *grpcRequest) Endpoint() string {
|
||||
}
|
||||
|
||||
func (g *grpcRequest) Codec() codec.Writer {
|
||||
return nil
|
||||
return g.codec
|
||||
}
|
||||
|
||||
func (g *grpcRequest) Body() interface{} {
|
||||
|
@@ -2,45 +2,38 @@ package grpc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
func TestMethodToGRPC(t *testing.T) {
|
||||
testData := []struct {
|
||||
service string
|
||||
method string
|
||||
expect string
|
||||
request interface{}
|
||||
}{
|
||||
{
|
||||
"helloworld",
|
||||
"Greeter.SayHello",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
new(pb.HelloRequest),
|
||||
},
|
||||
{
|
||||
"helloworld",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
new(pb.HelloRequest),
|
||||
},
|
||||
{
|
||||
"",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
},
|
||||
{
|
||||
"",
|
||||
"Greeter.SayHello",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
pb.HelloRequest{},
|
||||
},
|
||||
{
|
||||
"/helloworld.Greeter/SayHello",
|
||||
"/helloworld.Greeter/SayHello",
|
||||
pb.HelloRequest{},
|
||||
},
|
||||
{
|
||||
"Greeter.SayHello",
|
||||
"Greeter.SayHello",
|
||||
nil,
|
||||
"/Greeter/SayHello",
|
||||
},
|
||||
}
|
||||
|
||||
for _, d := range testData {
|
||||
method := methodToGRPC(d.method, d.request)
|
||||
method := methodToGRPC(d.service, d.method)
|
||||
if method != d.expect {
|
||||
t.Fatalf("expected %s got %s", d.expect, method)
|
||||
}
|
||||
|
44
client/grpc/response.go
Normal file
44
client/grpc/response.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
conn *grpc.ClientConn
|
||||
stream grpc.ClientStream
|
||||
codec encoding.Codec
|
||||
gcodec codec.Codec
|
||||
}
|
||||
|
||||
// Read the response
|
||||
func (r *response) Codec() codec.Reader {
|
||||
return r.gcodec
|
||||
}
|
||||
|
||||
// read the header
|
||||
func (r *response) Header() map[string]string {
|
||||
md, err := r.stream.Header()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
hdr := make(map[string]string)
|
||||
for k, v := range md {
|
||||
hdr[k] = strings.Join(v, ",")
|
||||
}
|
||||
return hdr
|
||||
}
|
||||
|
||||
// Read the undecoded response
|
||||
func (r *response) Read() ([]byte, error) {
|
||||
f := &bytes.Frame{}
|
||||
if err := r.gcodec.ReadBody(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Data, nil
|
||||
}
|
@@ -12,11 +12,12 @@ import (
|
||||
// Implements the streamer interface
|
||||
type grpcStream struct {
|
||||
sync.RWMutex
|
||||
err error
|
||||
conn *grpc.ClientConn
|
||||
request client.Request
|
||||
stream grpc.ClientStream
|
||||
context context.Context
|
||||
err error
|
||||
conn *grpc.ClientConn
|
||||
stream grpc.ClientStream
|
||||
request client.Request
|
||||
response client.Response
|
||||
context context.Context
|
||||
}
|
||||
|
||||
func (g *grpcStream) Context() context.Context {
|
||||
@@ -28,7 +29,7 @@ func (g *grpcStream) Request() client.Request {
|
||||
}
|
||||
|
||||
func (g *grpcStream) Response() client.Response {
|
||||
return nil
|
||||
return g.response
|
||||
}
|
||||
|
||||
func (g *grpcStream) Send(msg interface{}) error {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Package rpc provides an rpc client
|
||||
package rpc
|
||||
// Package mucp provides an mucp client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/client"
|
203
client/proto/client.micro.go
Normal file
203
client/proto/client.micro.go
Normal file
@@ -0,0 +1,203 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: micro/go-micro/client/proto/client.proto
|
||||
|
||||
package go_micro_client
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
import (
|
||||
context "context"
|
||||
client "github.com/micro/go-micro/client"
|
||||
server "github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// 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.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Client API for Micro service
|
||||
|
||||
type MicroService interface {
|
||||
// Call allows a single request to be made
|
||||
Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error)
|
||||
// Stream is a bidirectional stream
|
||||
Stream(ctx context.Context, opts ...client.CallOption) (Micro_StreamService, error)
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Message, error)
|
||||
}
|
||||
|
||||
type microService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewMicroService(name string, c client.Client) MicroService {
|
||||
if c == nil {
|
||||
c = client.NewClient()
|
||||
}
|
||||
if len(name) == 0 {
|
||||
name = "go.micro.client"
|
||||
}
|
||||
return µService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *microService) Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) {
|
||||
req := c.c.NewRequest(c.name, "Micro.Call", in)
|
||||
out := new(Response)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *microService) Stream(ctx context.Context, opts ...client.CallOption) (Micro_StreamService, error) {
|
||||
req := c.c.NewRequest(c.name, "Micro.Stream", &Request{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return µServiceStream{stream}, nil
|
||||
}
|
||||
|
||||
type Micro_StreamService interface {
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Request) error
|
||||
Recv() (*Response, error)
|
||||
}
|
||||
|
||||
type microServiceStream struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *microServiceStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *microServiceStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *microServiceStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *microServiceStream) Send(m *Request) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *microServiceStream) Recv() (*Response, error) {
|
||||
m := new(Response)
|
||||
err := x.stream.Recv(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *microService) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Message, error) {
|
||||
req := c.c.NewRequest(c.name, "Micro.Publish", in)
|
||||
out := new(Message)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Micro service
|
||||
|
||||
type MicroHandler interface {
|
||||
// Call allows a single request to be made
|
||||
Call(context.Context, *Request, *Response) error
|
||||
// Stream is a bidirectional stream
|
||||
Stream(context.Context, Micro_StreamStream) error
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(context.Context, *Message, *Message) error
|
||||
}
|
||||
|
||||
func RegisterMicroHandler(s server.Server, hdlr MicroHandler, opts ...server.HandlerOption) error {
|
||||
type micro interface {
|
||||
Call(ctx context.Context, in *Request, out *Response) error
|
||||
Stream(ctx context.Context, stream server.Stream) error
|
||||
Publish(ctx context.Context, in *Message, out *Message) error
|
||||
}
|
||||
type Micro struct {
|
||||
micro
|
||||
}
|
||||
h := µHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Micro{h}, opts...))
|
||||
}
|
||||
|
||||
type microHandler struct {
|
||||
MicroHandler
|
||||
}
|
||||
|
||||
func (h *microHandler) Call(ctx context.Context, in *Request, out *Response) error {
|
||||
return h.MicroHandler.Call(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *microHandler) Stream(ctx context.Context, stream server.Stream) error {
|
||||
return h.MicroHandler.Stream(ctx, µStreamStream{stream})
|
||||
}
|
||||
|
||||
type Micro_StreamStream interface {
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Response) error
|
||||
Recv() (*Request, error)
|
||||
}
|
||||
|
||||
type microStreamStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *microStreamStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *microStreamStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *microStreamStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *microStreamStream) Send(m *Response) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *microStreamStream) Recv() (*Request, error) {
|
||||
m := new(Request)
|
||||
if err := x.stream.Recv(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (h *microHandler) Publish(ctx context.Context, in *Message, out *Message) error {
|
||||
return h.MicroHandler.Publish(ctx, in, out)
|
||||
}
|
388
client/proto/client.pb.go
Normal file
388
client/proto/client.pb.go
Normal file
@@ -0,0 +1,388 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: micro/go-micro/client/proto/client.proto
|
||||
|
||||
package go_micro_client
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// 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.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Request struct {
|
||||
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
|
||||
Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||
ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7d733ae29171347b, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetService() string {
|
||||
if m != nil {
|
||||
return m.Service
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetEndpoint() string {
|
||||
if m != nil {
|
||||
return m.Endpoint
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetContentType() string {
|
||||
if m != nil {
|
||||
return m.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7d733ae29171347b, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
|
||||
ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Message) Reset() { *m = Message{} }
|
||||
func (m *Message) String() string { return proto.CompactTextString(m) }
|
||||
func (*Message) ProtoMessage() {}
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7d733ae29171347b, []int{2}
|
||||
}
|
||||
|
||||
func (m *Message) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Message.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Message) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Message.Merge(m, src)
|
||||
}
|
||||
func (m *Message) XXX_Size() int {
|
||||
return xxx_messageInfo_Message.Size(m)
|
||||
}
|
||||
func (m *Message) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Message.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Message proto.InternalMessageInfo
|
||||
|
||||
func (m *Message) GetTopic() string {
|
||||
if m != nil {
|
||||
return m.Topic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Message) GetContentType() string {
|
||||
if m != nil {
|
||||
return m.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Message) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "go.micro.client.Request")
|
||||
proto.RegisterType((*Response)(nil), "go.micro.client.Response")
|
||||
proto.RegisterType((*Message)(nil), "go.micro.client.Message")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("micro/go-micro/client/proto/client.proto", fileDescriptor_7d733ae29171347b)
|
||||
}
|
||||
|
||||
var fileDescriptor_7d733ae29171347b = []byte{
|
||||
// 270 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x91, 0x3f, 0x4f, 0xc3, 0x30,
|
||||
0x10, 0xc5, 0xeb, 0xfe, 0x4b, 0x39, 0x2a, 0x21, 0x9d, 0x18, 0x4c, 0x06, 0x54, 0x32, 0x65, 0xc1,
|
||||
0x45, 0x30, 0x23, 0x86, 0xce, 0x95, 0x50, 0x40, 0xac, 0x28, 0x71, 0x4f, 0xc1, 0x52, 0x6a, 0x9b,
|
||||
0xd8, 0xad, 0x94, 0xef, 0xc8, 0x87, 0x42, 0x38, 0x29, 0x45, 0xd0, 0x2e, 0x6c, 0xf7, 0xee, 0x67,
|
||||
0xbd, 0x3b, 0xbf, 0x83, 0x74, 0xad, 0x64, 0x6d, 0xe6, 0xa5, 0xb9, 0x6e, 0x0b, 0x59, 0x29, 0xd2,
|
||||
0x7e, 0x6e, 0x6b, 0xe3, 0x77, 0x42, 0x04, 0x81, 0x67, 0xa5, 0x11, 0xe1, 0x8d, 0x68, 0xdb, 0xc9,
|
||||
0x16, 0xa2, 0x8c, 0xde, 0x37, 0xe4, 0x3c, 0x72, 0x88, 0x1c, 0xd5, 0x5b, 0x25, 0x89, 0xb3, 0x19,
|
||||
0x4b, 0x4f, 0xb2, 0x9d, 0xc4, 0x18, 0x26, 0xa4, 0x57, 0xd6, 0x28, 0xed, 0x79, 0x3f, 0xa0, 0x6f,
|
||||
0x8d, 0x57, 0x30, 0x95, 0x46, 0x7b, 0xd2, 0xfe, 0xd5, 0x37, 0x96, 0xf8, 0x20, 0xf0, 0xd3, 0xae,
|
||||
0xf7, 0xdc, 0x58, 0x42, 0x84, 0x61, 0x61, 0x56, 0x0d, 0x1f, 0xce, 0x58, 0x3a, 0xcd, 0x42, 0x9d,
|
||||
0x5c, 0xc2, 0x24, 0x23, 0x67, 0x8d, 0x76, 0x7b, 0xce, 0x7e, 0xf0, 0x17, 0x88, 0x96, 0xe4, 0x5c,
|
||||
0x5e, 0x12, 0x9e, 0xc3, 0xc8, 0x1b, 0xab, 0x64, 0xb7, 0x55, 0x2b, 0xfe, 0xcc, 0xed, 0x1f, 0x9f,
|
||||
0x3b, 0xd8, 0xfb, 0xde, 0x7e, 0x30, 0x18, 0x2d, 0xbf, 0x02, 0xc0, 0x7b, 0x18, 0x2e, 0xf2, 0xaa,
|
||||
0x42, 0x2e, 0x7e, 0x65, 0x22, 0xba, 0x40, 0xe2, 0x8b, 0x03, 0xa4, 0x5d, 0x39, 0xe9, 0xe1, 0x02,
|
||||
0xc6, 0x4f, 0xbe, 0xa6, 0x7c, 0xfd, 0x4f, 0x83, 0x94, 0xdd, 0x30, 0x7c, 0x80, 0xe8, 0x71, 0x53,
|
||||
0x54, 0xca, 0xbd, 0x1d, 0x70, 0xe9, 0xfe, 0x1f, 0x1f, 0x25, 0x49, 0xaf, 0x18, 0x87, 0xb3, 0xde,
|
||||
0x7d, 0x06, 0x00, 0x00, 0xff, 0xff, 0xd3, 0x63, 0x94, 0x1a, 0x02, 0x02, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// MicroClient is the client API for Micro service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type MicroClient interface {
|
||||
// Call allows a single request to be made
|
||||
Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
// Stream is a bidirectional stream
|
||||
Stream(ctx context.Context, opts ...grpc.CallOption) (Micro_StreamClient, error)
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error)
|
||||
}
|
||||
|
||||
type microClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewMicroClient(cc *grpc.ClientConn) MicroClient {
|
||||
return µClient{cc}
|
||||
}
|
||||
|
||||
func (c *microClient) Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/go.micro.client.Micro/Call", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *microClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Micro_StreamClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_Micro_serviceDesc.Streams[0], "/go.micro.client.Micro/Stream", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := µStreamClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Micro_StreamClient interface {
|
||||
Send(*Request) error
|
||||
Recv() (*Response, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type microStreamClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *microStreamClient) Send(m *Request) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *microStreamClient) Recv() (*Response, error) {
|
||||
m := new(Response)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *microClient) Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) {
|
||||
out := new(Message)
|
||||
err := c.cc.Invoke(ctx, "/go.micro.client.Micro/Publish", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MicroServer is the server API for Micro service.
|
||||
type MicroServer interface {
|
||||
// Call allows a single request to be made
|
||||
Call(context.Context, *Request) (*Response, error)
|
||||
// Stream is a bidirectional stream
|
||||
Stream(Micro_StreamServer) error
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(context.Context, *Message) (*Message, error)
|
||||
}
|
||||
|
||||
func RegisterMicroServer(s *grpc.Server, srv MicroServer) {
|
||||
s.RegisterService(&_Micro_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Micro_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MicroServer).Call(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/go.micro.client.Micro/Call",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MicroServer).Call(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Micro_Stream_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(MicroServer).Stream(µStreamServer{stream})
|
||||
}
|
||||
|
||||
type Micro_StreamServer interface {
|
||||
Send(*Response) error
|
||||
Recv() (*Request, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type microStreamServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *microStreamServer) Send(m *Response) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *microStreamServer) Recv() (*Request, error) {
|
||||
m := new(Request)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _Micro_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Message)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MicroServer).Publish(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/go.micro.client.Micro/Publish",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MicroServer).Publish(ctx, req.(*Message))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Micro_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "go.micro.client.Micro",
|
||||
HandlerType: (*MicroServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Call",
|
||||
Handler: _Micro_Call_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Publish",
|
||||
Handler: _Micro_Publish_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Stream",
|
||||
Handler: _Micro_Stream_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "micro/go-micro/client/proto/client.proto",
|
||||
}
|
30
client/proto/client.proto
Normal file
30
client/proto/client.proto
Normal file
@@ -0,0 +1,30 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package go.micro.client;
|
||||
|
||||
// Micro is the micro client interface
|
||||
service Micro {
|
||||
// Call allows a single request to be made
|
||||
rpc Call(Request) returns (Response) {};
|
||||
// Stream is a bidirectional stream
|
||||
rpc Stream(stream Request) returns (stream Response) {};
|
||||
// Publish publishes a message and returns an empty Message
|
||||
rpc Publish(Message) returns (Message) {};
|
||||
}
|
||||
|
||||
message Request {
|
||||
string service = 1;
|
||||
string endpoint = 2;
|
||||
string content_type = 3;
|
||||
bytes body = 4;
|
||||
}
|
||||
|
||||
message Response {
|
||||
bytes body = 1;
|
||||
}
|
||||
|
||||
message Message {
|
||||
string topic = 1;
|
||||
string content_type = 2;
|
||||
bytes body = 3;
|
||||
}
|
@@ -301,6 +301,10 @@ func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, erro
|
||||
return ®istry.Node{
|
||||
Address: address,
|
||||
Port: port,
|
||||
// Set the protocol
|
||||
Metadata: map[string]string{
|
||||
"protocol": "mucp",
|
||||
},
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
@@ -563,5 +567,5 @@ func (r *rpcClient) NewRequest(service, method string, request interface{}, reqO
|
||||
}
|
||||
|
||||
func (r *rpcClient) String() string {
|
||||
return "rpc"
|
||||
return "mucp"
|
||||
}
|
||||
|
@@ -13,8 +13,9 @@ import (
|
||||
|
||||
func newTestRegistry() registry.Registry {
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
return r
|
||||
reg := r.(*memory.Registry)
|
||||
reg.Services = testData
|
||||
return reg
|
||||
}
|
||||
|
||||
func TestCallAddress(t *testing.T) {
|
||||
|
12
cmd/cmd.go
12
cmd/cmd.go
@@ -11,7 +11,11 @@ import (
|
||||
|
||||
"github.com/micro/cli"
|
||||
"github.com/micro/go-micro/client"
|
||||
cgrpc "github.com/micro/go-micro/client/grpc"
|
||||
cmucp "github.com/micro/go-micro/client/mucp"
|
||||
"github.com/micro/go-micro/server"
|
||||
sgrpc "github.com/micro/go-micro/server/grpc"
|
||||
smucp "github.com/micro/go-micro/server/mucp"
|
||||
"github.com/micro/go-micro/util/log"
|
||||
|
||||
// brokers
|
||||
@@ -176,7 +180,9 @@ var (
|
||||
}
|
||||
|
||||
DefaultClients = map[string]func(...client.Option) client.Client{
|
||||
"rpc": client.NewClient,
|
||||
"rpc": client.NewClient,
|
||||
"mucp": cmucp.NewClient,
|
||||
"grpc": cgrpc.NewClient,
|
||||
}
|
||||
|
||||
DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
|
||||
@@ -194,7 +200,9 @@ var (
|
||||
}
|
||||
|
||||
DefaultServers = map[string]func(...server.Option) server.Server{
|
||||
"rpc": server.NewServer,
|
||||
"rpc": server.NewServer,
|
||||
"mucp": smucp.NewServer,
|
||||
"grpc": sgrpc.NewServer,
|
||||
}
|
||||
|
||||
DefaultTransports = map[string]func(...transport.Option) transport.Transport{
|
||||
|
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/micro/go-micro/codec"
|
||||
)
|
||||
|
||||
@@ -22,6 +24,9 @@ func (c *Codec) ReadBody(b interface{}) error {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
if pb, ok := b.(proto.Message); ok {
|
||||
return jsonpb.UnmarshalNext(c.Decoder, pb)
|
||||
}
|
||||
return c.Decoder.Decode(b)
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,9 @@ package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
type Marshaler struct{}
|
||||
@@ -11,6 +14,9 @@ func (j Marshaler) Marshal(v interface{}) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (j Marshaler) Unmarshal(d []byte, v interface{}) error {
|
||||
if pb, ok := v.(proto.Message); ok {
|
||||
return jsonpb.UnmarshalString(string(d), pb)
|
||||
}
|
||||
return json.Unmarshal(d, v)
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,9 @@ func (c *Codec) ReadBody(b interface{}) error {
|
||||
}
|
||||
|
||||
switch b.(type) {
|
||||
case *string:
|
||||
v := b.(*string)
|
||||
*v = string(buf)
|
||||
case *[]byte:
|
||||
v := b.(*[]byte)
|
||||
*v = buf
|
||||
@@ -51,6 +54,12 @@ func (c *Codec) Write(m *codec.Message, b interface{}) error {
|
||||
case *[]byte:
|
||||
ve := b.(*[]byte)
|
||||
v = *ve
|
||||
case *string:
|
||||
ve := b.(*string)
|
||||
v = []byte(*ve)
|
||||
case string:
|
||||
ve := b.(string)
|
||||
v = []byte(ve)
|
||||
case []byte:
|
||||
v = b.([]byte)
|
||||
default:
|
||||
@@ -65,7 +74,7 @@ func (c *Codec) Close() error {
|
||||
}
|
||||
|
||||
func (c *Codec) String() string {
|
||||
return "bytes"
|
||||
return "text"
|
||||
}
|
||||
|
||||
func NewCodec(c io.ReadWriteCloser) codec.Codec {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package memory
|
||||
package micro
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
var (
|
||||
// mock data
|
||||
Data = map[string][]*registry.Service{
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
34
config/options/default.go
Normal file
34
config/options/default.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package options
|
||||
|
||||
type defaultOptions struct {
|
||||
opts *Values
|
||||
}
|
||||
|
||||
type stringKey struct{}
|
||||
|
||||
func (d *defaultOptions) Init(opts ...Option) error {
|
||||
if d.opts == nil {
|
||||
d.opts = new(Values)
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := d.opts.Option(o); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *defaultOptions) Values() *Values {
|
||||
return d.opts
|
||||
}
|
||||
|
||||
func (d *defaultOptions) String() string {
|
||||
if d.opts == nil {
|
||||
d.opts = new(Values)
|
||||
}
|
||||
n, ok := d.opts.Get(stringKey{})
|
||||
if ok {
|
||||
return n.(string)
|
||||
}
|
||||
return "Values"
|
||||
}
|
73
config/options/options.go
Normal file
73
config/options/options.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Package options provides a way to initialise options
|
||||
package options
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Options is used for initialisation
|
||||
type Options interface {
|
||||
// Initialise options
|
||||
Init(...Option) error
|
||||
// Options returns the current options
|
||||
Values() *Values
|
||||
// The name for who these options exist
|
||||
String() string
|
||||
}
|
||||
|
||||
// Values holds the set of option values and protects them
|
||||
type Values struct {
|
||||
sync.RWMutex
|
||||
values map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// Option gives access to options
|
||||
type Option func(o *Values) error
|
||||
|
||||
// Get a value from options
|
||||
func (o *Values) Get(k interface{}) (interface{}, bool) {
|
||||
o.RLock()
|
||||
defer o.RUnlock()
|
||||
v, ok := o.values[k]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Set a value in the options
|
||||
func (o *Values) Set(k, v interface{}) error {
|
||||
o.Lock()
|
||||
defer o.Unlock()
|
||||
if o.values == nil {
|
||||
o.values = map[interface{}]interface{}{}
|
||||
}
|
||||
o.values[k] = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetOption executes an option
|
||||
func (o *Values) Option(op Option) error {
|
||||
return op(o)
|
||||
}
|
||||
|
||||
// WithValue allows you to set any value within the options
|
||||
func WithValue(k, v interface{}) Option {
|
||||
return func(o *Values) error {
|
||||
return o.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// WithOption gives you the ability to create an option that accesses values
|
||||
func WithOption(o Option) Option {
|
||||
return o
|
||||
}
|
||||
|
||||
// String sets the string
|
||||
func WithString(s string) Option {
|
||||
return WithValue(stringKey{}, s)
|
||||
}
|
||||
|
||||
// NewOptions returns a new initialiser
|
||||
func NewOptions(opts ...Option) Options {
|
||||
o := new(defaultOptions)
|
||||
o.Init(opts...)
|
||||
return o
|
||||
}
|
@@ -4,7 +4,7 @@ The consul source reads config from consul key/values
|
||||
|
||||
## Consul Format
|
||||
|
||||
The consul source expects keys under the default prefix `/micro/config`
|
||||
The consul source expects keys under the default prefix `micro/config`
|
||||
|
||||
Values are expected to be json
|
||||
|
||||
@@ -29,8 +29,8 @@ Specify source with data
|
||||
consulSource := consul.NewSource(
|
||||
// optionally specify consul address; default to localhost:8500
|
||||
consul.WithAddress("10.0.0.10:8500"),
|
||||
// optionally specify prefix; defaults to /micro/config
|
||||
consul.WithPrefix("/my/prefix"),
|
||||
// optionally specify prefix; defaults to micro/config
|
||||
consul.WithPrefix("my/prefix"),
|
||||
// optionally strip the provided prefix from the keys, defaults to false
|
||||
consul.StripPrefix(true),
|
||||
)
|
||||
|
@@ -21,7 +21,7 @@ type consul struct {
|
||||
var (
|
||||
// DefaultPrefix is the prefix that consul keys will be assumed to have if you
|
||||
// haven't specified one
|
||||
DefaultPrefix = "/micro/config/"
|
||||
DefaultPrefix = "micro/config/"
|
||||
)
|
||||
|
||||
func (c *consul) Read() (*source.ChangeSet, error) {
|
||||
|
@@ -1,51 +0,0 @@
|
||||
# Etcd Source
|
||||
|
||||
The etcd source reads config from etcd key/values
|
||||
|
||||
This source supports etcd version 3 and beyond.
|
||||
|
||||
## Etcd Format
|
||||
|
||||
The etcd source expects keys under the default prefix `/micro/config` (prefix can be changed)
|
||||
|
||||
Values are expected to be JSON
|
||||
|
||||
```
|
||||
// set database
|
||||
etcdctl put /micro/config/database '{"address": "10.0.0.1", "port": 3306}'
|
||||
// set cache
|
||||
etcdctl put /micro/config/cache '{"address": "10.0.0.2", "port": 6379}'
|
||||
```
|
||||
|
||||
Keys are split on `/` so access becomes
|
||||
|
||||
```
|
||||
conf.Get("micro", "config", "database")
|
||||
```
|
||||
|
||||
## New Source
|
||||
|
||||
Specify source with data
|
||||
|
||||
```go
|
||||
etcdSource := etcd.NewSource(
|
||||
// optionally specify etcd address; default to localhost:8500
|
||||
etcd.WithAddress("10.0.0.10:8500"),
|
||||
// optionally specify prefix; defaults to /micro/config
|
||||
etcd.WithPrefix("/my/prefix"),
|
||||
// optionally strip the provided prefix from the keys, defaults to false
|
||||
etcd.StripPrefix(true),
|
||||
)
|
||||
```
|
||||
|
||||
## Load Source
|
||||
|
||||
Load the source into config
|
||||
|
||||
```go
|
||||
// Create new config
|
||||
conf := config.NewConfig()
|
||||
|
||||
// Load file source
|
||||
conf.Load(etcdSource)
|
||||
```
|
@@ -1,134 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/source"
|
||||
cetcd "go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
// Currently a single etcd reader
|
||||
type etcd struct {
|
||||
prefix string
|
||||
stripPrefix string
|
||||
opts source.Options
|
||||
client *cetcd.Client
|
||||
cerr error
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultPrefix = "/micro/config/"
|
||||
)
|
||||
|
||||
func (c *etcd) Read() (*source.ChangeSet, error) {
|
||||
if c.cerr != nil {
|
||||
return nil, c.cerr
|
||||
}
|
||||
|
||||
rsp, err := c.client.Get(context.Background(), c.prefix, cetcd.WithPrefix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rsp == nil || len(rsp.Kvs) == 0 {
|
||||
return nil, fmt.Errorf("source not found: %s", c.prefix)
|
||||
}
|
||||
|
||||
var kvs []*mvccpb.KeyValue
|
||||
for _, v := range rsp.Kvs {
|
||||
kvs = append(kvs, (*mvccpb.KeyValue)(v))
|
||||
}
|
||||
|
||||
data := makeMap(c.opts.Encoder, kvs, c.stripPrefix)
|
||||
|
||||
b, err := c.opts.Encoder.Encode(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading source: %v", err)
|
||||
}
|
||||
|
||||
cs := &source.ChangeSet{
|
||||
Timestamp: time.Now(),
|
||||
Source: c.String(),
|
||||
Data: b,
|
||||
Format: c.opts.Encoder.String(),
|
||||
}
|
||||
cs.Checksum = cs.Sum()
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *etcd) String() string {
|
||||
return "etcd"
|
||||
}
|
||||
|
||||
func (c *etcd) Watch() (source.Watcher, error) {
|
||||
if c.cerr != nil {
|
||||
return nil, c.cerr
|
||||
}
|
||||
cs, err := c.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWatcher(c.prefix, c.stripPrefix, c.client.Watcher, cs, c.opts)
|
||||
}
|
||||
|
||||
func NewSource(opts ...source.Option) source.Source {
|
||||
options := source.NewOptions(opts...)
|
||||
|
||||
var endpoints []string
|
||||
|
||||
// check if there are any addrs
|
||||
addrs, ok := options.Context.Value(addressKey{}).([]string)
|
||||
if ok {
|
||||
for _, a := range addrs {
|
||||
addr, port, err := net.SplitHostPort(a)
|
||||
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
||||
port = "2379"
|
||||
addr = a
|
||||
endpoints = append(endpoints, fmt.Sprintf("%s:%s", addr, port))
|
||||
} else if err == nil {
|
||||
endpoints = append(endpoints, fmt.Sprintf("%s:%s", addr, port))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) == 0 {
|
||||
endpoints = []string{"localhost:2379"}
|
||||
}
|
||||
|
||||
config := cetcd.Config{
|
||||
Endpoints: endpoints,
|
||||
}
|
||||
|
||||
u, ok := options.Context.Value(authKey{}).(*authCreds)
|
||||
if ok {
|
||||
config.Username = u.Username
|
||||
config.Password = u.Password
|
||||
}
|
||||
|
||||
// use default config
|
||||
client, err := cetcd.New(config)
|
||||
|
||||
prefix := DefaultPrefix
|
||||
sp := ""
|
||||
f, ok := options.Context.Value(prefixKey{}).(string)
|
||||
if ok {
|
||||
prefix = f
|
||||
}
|
||||
|
||||
if b, ok := options.Context.Value(stripPrefixKey{}).(bool); ok && b {
|
||||
sp = prefix
|
||||
}
|
||||
|
||||
return &etcd{
|
||||
prefix: prefix,
|
||||
stripPrefix: sp,
|
||||
opts: options,
|
||||
client: client,
|
||||
cerr: err,
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/config/source"
|
||||
)
|
||||
|
||||
type addressKey struct{}
|
||||
type prefixKey struct{}
|
||||
type stripPrefixKey struct{}
|
||||
type authKey struct{}
|
||||
|
||||
type authCreds struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// WithAddress sets the consul address
|
||||
func WithAddress(a ...string) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, addressKey{}, a)
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix sets the key prefix to use
|
||||
func WithPrefix(p string) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, prefixKey{}, p)
|
||||
}
|
||||
}
|
||||
|
||||
// StripPrefix indicates whether to remove the prefix from config entries, or leave it in place.
|
||||
func StripPrefix(strip bool) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
|
||||
o.Context = context.WithValue(o.Context, stripPrefixKey{}, strip)
|
||||
}
|
||||
}
|
||||
|
||||
// Auth allows you to specify username/password
|
||||
func Auth(username, password string) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, authKey{}, &authCreds{Username: username, Password: password})
|
||||
}
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/config/encoder"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
func makeEvMap(e encoder.Encoder, data map[string]interface{}, kv []*clientv3.Event, stripPrefix string) map[string]interface{} {
|
||||
if data == nil {
|
||||
data = make(map[string]interface{})
|
||||
}
|
||||
|
||||
for _, v := range kv {
|
||||
switch mvccpb.Event_EventType(v.Type) {
|
||||
case mvccpb.DELETE:
|
||||
data = update(e, data, (*mvccpb.KeyValue)(v.Kv), "delete", stripPrefix)
|
||||
default:
|
||||
data = update(e, data, (*mvccpb.KeyValue)(v.Kv), "insert", stripPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func makeMap(e encoder.Encoder, kv []*mvccpb.KeyValue, stripPrefix string) map[string]interface{} {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
for _, v := range kv {
|
||||
data = update(e, data, v, "put", stripPrefix)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func update(e encoder.Encoder, data map[string]interface{}, v *mvccpb.KeyValue, action, stripPrefix string) map[string]interface{} {
|
||||
// remove prefix if non empty, and ensure leading / is removed as well
|
||||
vkey := strings.TrimPrefix(strings.TrimPrefix(string(v.Key), stripPrefix), "/")
|
||||
// split on prefix
|
||||
haveSplit := strings.Contains(vkey, "/")
|
||||
keys := strings.Split(vkey, "/")
|
||||
|
||||
var vals interface{}
|
||||
e.Decode(v.Value, &vals)
|
||||
|
||||
if !haveSplit && len(keys) == 1 {
|
||||
switch action {
|
||||
case "delete":
|
||||
data = make(map[string]interface{})
|
||||
default:
|
||||
v, ok := vals.(map[string]interface{})
|
||||
if ok {
|
||||
data = v
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// set data for first iteration
|
||||
kvals := data
|
||||
// iterate the keys and make maps
|
||||
for i, k := range keys {
|
||||
kval, ok := kvals[k].(map[string]interface{})
|
||||
if !ok {
|
||||
// create next map
|
||||
kval = make(map[string]interface{})
|
||||
// set it
|
||||
kvals[k] = kval
|
||||
}
|
||||
|
||||
// last key: write vals
|
||||
if l := len(keys) - 1; i == l {
|
||||
switch action {
|
||||
case "delete":
|
||||
delete(kvals, k)
|
||||
default:
|
||||
kvals[k] = vals
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// set kvals for next iterator
|
||||
kvals = kval
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/source"
|
||||
cetcd "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
opts source.Options
|
||||
name string
|
||||
stripPrefix string
|
||||
|
||||
sync.RWMutex
|
||||
cs *source.ChangeSet
|
||||
|
||||
ch chan *source.ChangeSet
|
||||
exit chan bool
|
||||
}
|
||||
|
||||
func newWatcher(key, strip string, wc cetcd.Watcher, cs *source.ChangeSet, opts source.Options) (source.Watcher, error) {
|
||||
w := &watcher{
|
||||
opts: opts,
|
||||
name: "etcd",
|
||||
stripPrefix: strip,
|
||||
cs: cs,
|
||||
ch: make(chan *source.ChangeSet),
|
||||
exit: make(chan bool),
|
||||
}
|
||||
|
||||
ch := wc.Watch(context.Background(), key, cetcd.WithPrefix())
|
||||
|
||||
go w.run(wc, ch)
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *watcher) handle(evs []*cetcd.Event) {
|
||||
w.RLock()
|
||||
data := w.cs.Data
|
||||
w.RUnlock()
|
||||
|
||||
var vals map[string]interface{}
|
||||
|
||||
// unpackage existing changeset
|
||||
if err := w.opts.Encoder.Decode(data, &vals); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// update base changeset
|
||||
d := makeEvMap(w.opts.Encoder, vals, evs, w.stripPrefix)
|
||||
|
||||
// pack the changeset
|
||||
b, err := w.opts.Encoder.Encode(d)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// create new changeset
|
||||
cs := &source.ChangeSet{
|
||||
Timestamp: time.Now(),
|
||||
Source: w.name,
|
||||
Data: b,
|
||||
Format: w.opts.Encoder.String(),
|
||||
}
|
||||
cs.Checksum = cs.Sum()
|
||||
|
||||
// set base change set
|
||||
w.Lock()
|
||||
w.cs = cs
|
||||
w.Unlock()
|
||||
|
||||
// send update
|
||||
w.ch <- cs
|
||||
}
|
||||
|
||||
func (w *watcher) run(wc cetcd.Watcher, ch cetcd.WatchChan) {
|
||||
for {
|
||||
select {
|
||||
case rsp, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.handle(rsp.Events)
|
||||
case <-w.exit:
|
||||
wc.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) Next() (*source.ChangeSet, error) {
|
||||
select {
|
||||
case cs := <-w.ch:
|
||||
return cs, nil
|
||||
case <-w.exit:
|
||||
return nil, errors.New("watcher stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) Stop() error {
|
||||
select {
|
||||
case <-w.exit:
|
||||
return nil
|
||||
default:
|
||||
close(w.exit)
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -5,8 +5,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/config/source"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
type memory struct {
|
||||
@@ -29,7 +29,7 @@ func (s *memory) Read() (*source.ChangeSet, error) {
|
||||
|
||||
func (s *memory) Watch() (source.Watcher, error) {
|
||||
w := &watcher{
|
||||
Id: uuid.NewUUID().String(),
|
||||
Id: uuid.New().String(),
|
||||
Updates: make(chan *source.ChangeSet, 100),
|
||||
Source: s,
|
||||
}
|
||||
|
@@ -6,24 +6,26 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
)
|
||||
|
||||
type ckv struct {
|
||||
options.Options
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
func (c *ckv) Read(key string) (*data.Record, error) {
|
||||
func (c *ckv) Read(key string) (*store.Record, error) {
|
||||
keyval, _, err := c.client.KV().Get(key, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keyval == nil {
|
||||
return nil, data.ErrNotFound
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
|
||||
return &data.Record{
|
||||
return &store.Record{
|
||||
Key: keyval.Key,
|
||||
Value: keyval.Value,
|
||||
}, nil
|
||||
@@ -34,7 +36,7 @@ func (c *ckv) Delete(key string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ckv) Write(record *data.Record) error {
|
||||
func (c *ckv) Write(record *store.Record) error {
|
||||
_, err := c.client.KV().Put(&api.KVPair{
|
||||
Key: record.Key,
|
||||
Value: record.Value,
|
||||
@@ -42,17 +44,17 @@ func (c *ckv) Write(record *data.Record) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ckv) Dump() ([]*data.Record, error) {
|
||||
func (c *ckv) Dump() ([]*store.Record, error) {
|
||||
keyval, _, err := c.client.KV().List("/", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keyval == nil {
|
||||
return nil, data.ErrNotFound
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
var vals []*data.Record
|
||||
var vals []*store.Record
|
||||
for _, keyv := range keyval {
|
||||
vals = append(vals, &data.Record{
|
||||
vals = append(vals, &store.Record{
|
||||
Key: keyv.Key,
|
||||
Value: keyv.Value,
|
||||
})
|
||||
@@ -64,22 +66,22 @@ func (c *ckv) String() string {
|
||||
return "consul"
|
||||
}
|
||||
|
||||
func NewData(opts ...data.Option) data.Data {
|
||||
var options data.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
func NewStore(opts ...options.Option) store.Store {
|
||||
options := options.NewOptions(opts...)
|
||||
config := api.DefaultConfig()
|
||||
|
||||
var nodes []string
|
||||
|
||||
if n, ok := options.Values().Get("store.nodes"); ok {
|
||||
nodes = n.([]string)
|
||||
}
|
||||
|
||||
// set host
|
||||
// config.Host something
|
||||
// check if there are any addrs
|
||||
if len(options.Nodes) > 0 {
|
||||
addr, port, err := net.SplitHostPort(options.Nodes[0])
|
||||
if len(nodes) > 0 {
|
||||
addr, port, err := net.SplitHostPort(nodes[0])
|
||||
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
||||
port = "8500"
|
||||
config.Address = fmt.Sprintf("%s:%s", options.Nodes[0], port)
|
||||
config.Address = fmt.Sprintf("%s:%s", nodes[0], port)
|
||||
} else if err == nil {
|
||||
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
||||
}
|
||||
@@ -88,6 +90,7 @@ func NewData(opts ...data.Option) data.Data {
|
||||
client, _ := api.NewClient(config)
|
||||
|
||||
return &ckv{
|
||||
client: client,
|
||||
Options: options,
|
||||
client: client,
|
||||
}
|
||||
}
|
97
data/store/memory/memory.go
Normal file
97
data/store/memory/memory.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Package memory is a in-memory store store
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
)
|
||||
|
||||
type memoryStore struct {
|
||||
options.Options
|
||||
|
||||
sync.RWMutex
|
||||
values map[string]*memoryRecord
|
||||
}
|
||||
|
||||
type memoryRecord struct {
|
||||
r *store.Record
|
||||
c time.Time
|
||||
}
|
||||
|
||||
func (m *memoryStore) Dump() ([]*store.Record, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
var values []*store.Record
|
||||
|
||||
for _, v := range m.values {
|
||||
// get expiry
|
||||
d := v.r.Expiry
|
||||
t := time.Since(v.c)
|
||||
|
||||
// expired
|
||||
if d > time.Duration(0) && t > d {
|
||||
continue
|
||||
}
|
||||
values = append(values, v.r)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Read(key string) (*store.Record, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
v, ok := m.values[key]
|
||||
if !ok {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
|
||||
// get expiry
|
||||
d := v.r.Expiry
|
||||
t := time.Since(v.c)
|
||||
|
||||
// expired
|
||||
if d > time.Duration(0) && t > d {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
|
||||
return v.r, nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Write(r *store.Record) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// set the record
|
||||
m.values[r.Key] = &memoryRecord{
|
||||
r: r,
|
||||
c: time.Now(),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Delete(key string) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// delete the value
|
||||
delete(m.values, key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStore returns a new store.Store
|
||||
func NewStore(opts ...options.Option) store.Store {
|
||||
options := options.NewOptions(opts...)
|
||||
|
||||
return &memoryStore{
|
||||
Options: options,
|
||||
values: make(map[string]*memoryRecord),
|
||||
}
|
||||
}
|
15
data/store/options.go
Normal file
15
data/store/options.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/config/options"
|
||||
)
|
||||
|
||||
// Set the nodes used to back the store
|
||||
func Nodes(a ...string) options.Option {
|
||||
return options.WithValue("store.nodes", a)
|
||||
}
|
||||
|
||||
// Prefix sets a prefix to any key ids used
|
||||
func Prefix(p string) options.Option {
|
||||
return options.WithValue("store.prefix", p)
|
||||
}
|
@@ -1,17 +1,21 @@
|
||||
// Package data is an interface for key-value storage.
|
||||
package data
|
||||
// Package store is an interface for distribute data storage.
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/options"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// Data is a data storage interface
|
||||
type Data interface {
|
||||
// Store is a data storage interface
|
||||
type Store interface {
|
||||
// embed options
|
||||
options.Options
|
||||
// Dump the known records
|
||||
Dump() ([]*Record, error)
|
||||
// Read a record with key
|
||||
@@ -24,9 +28,7 @@ type Data interface {
|
||||
|
||||
// Record represents a data record
|
||||
type Record struct {
|
||||
Key string
|
||||
Value []byte
|
||||
Expiration time.Duration
|
||||
Key string
|
||||
Value []byte
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
type Option func(o *Options)
|
@@ -14,7 +14,7 @@ func TestFunction(t *testing.T) {
|
||||
wg.Add(1)
|
||||
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
r.(*memory.Registry).Services = testData
|
||||
|
||||
// create service
|
||||
fn := NewFunction(
|
||||
|
76
go.mod
76
go.mod
@@ -1,58 +1,38 @@
|
||||
module github.com/micro/go-micro
|
||||
|
||||
exclude (
|
||||
github.com/Sirupsen/logrus v1.1.0
|
||||
github.com/Sirupsen/logrus v1.1.1
|
||||
github.com/Sirupsen/logrus v1.2.0
|
||||
github.com/Sirupsen/logrus v1.3.0
|
||||
github.com/Sirupsen/logrus v1.4.0
|
||||
github.com/Sirupsen/logrus v1.4.1
|
||||
github.com/Sirupsen/logrus v1.4.2
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.1
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1
|
||||
github.com/nats-io/go-nats => github.com/nats-io/nats.go v1.8.1
|
||||
github.com/nats-io/go-nats-streaming => github.com/nats-io/stan.go v0.5.0
|
||||
github.com/testcontainers/testcontainer-go => github.com/testcontainers/testcontainers-go v0.0.0-20181115231424-8e868ca12c0f
|
||||
)
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.39.0 // indirect
|
||||
cloud.google.com/go v0.40.0 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/beevik/ntp v0.2.0
|
||||
github.com/bitly/go-simplejson v0.5.0
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
|
||||
github.com/bwmarrin/discordgo v0.19.0
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
|
||||
github.com/coreos/etcd v3.3.13+incompatible
|
||||
github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/fsouza/go-dockerclient v1.4.1
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gliderlabs/ssh v0.1.4 // indirect
|
||||
github.com/gliderlabs/ssh v0.2.2 // indirect
|
||||
github.com/go-log/log v0.1.0
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/go-redsync/redsync v1.2.0
|
||||
github.com/golang/mock v1.3.1 // indirect
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/gorilla/handlers v1.4.0
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/hashicorp/consul/api v1.1.0
|
||||
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
|
||||
github.com/hashicorp/go-msgpack v0.5.5 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.5.4 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.1 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0
|
||||
@@ -64,57 +44,43 @@ require (
|
||||
github.com/json-iterator/go v1.1.6
|
||||
github.com/kisielk/errcheck v1.2.0 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/kr/pty v1.1.4 // indirect
|
||||
github.com/kr/pty v1.1.5 // indirect
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.11.2
|
||||
github.com/marten-seemann/qtls v0.2.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/micro/cli v0.2.0
|
||||
github.com/micro/mdns v0.1.0
|
||||
github.com/miekg/dns v1.1.13 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/miekg/dns v1.1.14 // indirect
|
||||
github.com/mitchellh/gox v1.0.1 // indirect
|
||||
github.com/mitchellh/hashstructure v1.0.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nats-io/nats.go v1.8.1
|
||||
github.com/nlopes/slack v0.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/pborman/uuid v1.2.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/posener/complete v1.2.1 // indirect
|
||||
github.com/prometheus/client_golang v0.9.3 // indirect
|
||||
github.com/prometheus/common v0.4.1 // indirect
|
||||
github.com/prometheus/procfs v0.0.2 // indirect
|
||||
github.com/prometheus/tsdb v0.8.0 // indirect
|
||||
github.com/prometheus/common v0.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
go.etcd.io/etcd v3.3.13+incompatible
|
||||
go.opencensus.io v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 // indirect
|
||||
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff // indirect
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171 // indirect
|
||||
golang.org/x/image v0.0.0-20190618124811-92942e4437e2 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88 // indirect
|
||||
golang.org/x/mod v0.1.0 // indirect
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
|
||||
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
golang.org/x/sys v0.0.0-20190621062556-bf70e4678053 // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
golang.org/x/tools v0.0.0-20190603231351-8aaa1484dc10 // indirect
|
||||
google.golang.org/appengine v1.6.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 // indirect
|
||||
golang.org/x/tools v0.0.0-20190620191750-1fa568393b23 // indirect
|
||||
google.golang.org/appengine v1.6.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 // indirect
|
||||
google.golang.org/grpc v1.21.1
|
||||
gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.0
|
||||
gopkg.in/redis.v3 v3.6.4
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect
|
||||
gopkg.in/src-d/go-git.v4 v4.11.0
|
||||
gopkg.in/src-d/go-git.v4 v4.12.0
|
||||
gopkg.in/telegram-bot-api.v4 v4.6.4
|
||||
honnef.co/go/tools v0.0.0-20190604153307-63e9ff576adb // indirect
|
||||
honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b // indirect
|
||||
)
|
||||
|
47
network/network.go
Normal file
47
network/network.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Package network is a package for defining a network overlay
|
||||
package network
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/config/options"
|
||||
)
|
||||
|
||||
// Network is an interface defining networks or graphs
|
||||
type Network interface {
|
||||
options.Options
|
||||
// Id of this node
|
||||
Id() uint64
|
||||
// Connect to a node
|
||||
Connect(id uint64) (Link, error)
|
||||
// Close the network connection
|
||||
Close() error
|
||||
// Accept messages on the network
|
||||
Accept() (*Message, error)
|
||||
// Send a message to the network
|
||||
Send(*Message) error
|
||||
// Retrieve list of connections
|
||||
Links() ([]Link, error)
|
||||
}
|
||||
|
||||
// Node represents a network node
|
||||
type Node interface {
|
||||
// Node is a network. Network is a node.
|
||||
Network
|
||||
}
|
||||
|
||||
// Link is a connection to another node
|
||||
type Link interface {
|
||||
// remote node
|
||||
Node
|
||||
// length of link which dictates speed
|
||||
Length() int
|
||||
// weight of link which dictates curvature
|
||||
Weight() int
|
||||
}
|
||||
|
||||
// Message is the base type for opaque data
|
||||
type Message []byte
|
||||
|
||||
var (
|
||||
// TODO: set default network
|
||||
DefaultNetwork Network
|
||||
)
|
158
proxy/grpc/grpc.go
Normal file
158
proxy/grpc/grpc.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Package grpc transparently forwards the grpc protocol using a go-micro client.
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/client/grpc"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/proxy"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Proxy will transparently proxy requests to the backend.
|
||||
// If no backend is specified it will call a service using the client.
|
||||
// If the service matches the Name it will use the server.DefaultRouter.
|
||||
type Proxy struct {
|
||||
// The proxy options
|
||||
options.Options
|
||||
|
||||
// Endpoint specified the fixed endpoint to call.
|
||||
Endpoint string
|
||||
|
||||
// The client to use for outbound requests
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
// read client request and write to server
|
||||
func readLoop(r server.Request, s client.Stream) error {
|
||||
// request to backend server
|
||||
req := s.Request()
|
||||
|
||||
for {
|
||||
// get data from client
|
||||
// no need to decode it
|
||||
body, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the header from client
|
||||
hdr := r.Header()
|
||||
msg := &codec.Message{
|
||||
Type: codec.Request,
|
||||
Header: hdr,
|
||||
Body: body,
|
||||
}
|
||||
// write the raw request
|
||||
err = req.Codec().Write(msg, nil)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeRequest honours the server.Proxy interface
|
||||
func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
// set default client
|
||||
if p.Client == nil {
|
||||
p.Client = grpc.NewClient()
|
||||
}
|
||||
|
||||
opts := []client.CallOption{}
|
||||
|
||||
// service name
|
||||
service := req.Service()
|
||||
endpoint := req.Endpoint()
|
||||
|
||||
// call a specific backend
|
||||
if len(p.Endpoint) > 0 {
|
||||
// address:port
|
||||
if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
|
||||
opts = append(opts, client.WithAddress(p.Endpoint))
|
||||
// use as service name
|
||||
} else {
|
||||
service = p.Endpoint
|
||||
}
|
||||
}
|
||||
|
||||
// create new request with raw bytes body
|
||||
creq := p.Client.NewRequest(service, endpoint, nil, client.WithContentType(req.ContentType()))
|
||||
|
||||
// create new stream
|
||||
stream, err := p.Client.Stream(ctx, creq, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// create client request read loop
|
||||
go readLoop(req, stream)
|
||||
|
||||
// get raw response
|
||||
resp := stream.Response()
|
||||
|
||||
// create server response write loop
|
||||
for {
|
||||
// read backend response body
|
||||
body, err := resp.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read backend response header
|
||||
hdr := resp.Header()
|
||||
|
||||
// write raw response header to client
|
||||
rsp.WriteHeader(hdr)
|
||||
|
||||
// write raw response body to client
|
||||
err = rsp.Write(body)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewProxy returns a new grpc proxy server
|
||||
func NewProxy(opts ...options.Option) proxy.Proxy {
|
||||
p := new(Proxy)
|
||||
p.Options = options.NewOptions(opts...)
|
||||
p.Options.Init(options.WithString("grpc"))
|
||||
|
||||
// get endpoint
|
||||
ep, ok := p.Options.Values().Get("proxy.endpoint")
|
||||
if ok {
|
||||
p.Endpoint = ep.(string)
|
||||
}
|
||||
|
||||
// get client
|
||||
c, ok := p.Options.Values().Get("proxy.client")
|
||||
if ok {
|
||||
p.Client = c.(client.Client)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// NewSingleHostProxy returns a router which sends requests to a single backend
|
||||
func NewSingleHostProxy(url string) *Proxy {
|
||||
return &Proxy{
|
||||
Endpoint: url,
|
||||
}
|
||||
}
|
@@ -10,27 +10,23 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/proxy"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Router will proxy rpc requests as http POST requests. It is a server.Router
|
||||
type Router struct {
|
||||
// Proxy will proxy rpc requests as http POST requests. It is a server.Proxy
|
||||
type Proxy struct {
|
||||
options.Options
|
||||
|
||||
// The http backend to call
|
||||
Backend string
|
||||
Endpoint string
|
||||
|
||||
// first request
|
||||
first bool
|
||||
}
|
||||
|
||||
var (
|
||||
// The default backend
|
||||
DefaultBackend = "http://localhost:9090"
|
||||
// The default router
|
||||
DefaultRouter = &Router{}
|
||||
)
|
||||
|
||||
func getMethod(hdr map[string]string) string {
|
||||
switch hdr["Micro-Method"] {
|
||||
case "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH":
|
||||
@@ -49,9 +45,9 @@ func getEndpoint(hdr map[string]string) string {
|
||||
}
|
||||
|
||||
// ServeRequest honours the server.Router interface
|
||||
func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
if p.Backend == "" {
|
||||
p.Backend = DefaultBackend
|
||||
func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
if p.Endpoint == "" {
|
||||
p.Endpoint = proxy.DefaultEndpoint
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -75,10 +71,10 @@ func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp serve
|
||||
|
||||
// set the endpoint
|
||||
if len(endpoint) == 0 {
|
||||
endpoint = p.Backend
|
||||
endpoint = p.Endpoint
|
||||
} else {
|
||||
// add endpoint to backend
|
||||
u, err := url.Parse(p.Backend)
|
||||
u, err := url.Parse(p.Endpoint)
|
||||
if err != nil {
|
||||
return errors.InternalServerError(req.Service(), err.Error())
|
||||
}
|
||||
@@ -130,48 +126,24 @@ func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp serve
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSingleHostRouter returns a router which sends requests to a single http backend
|
||||
//
|
||||
// It is used by setting it in a new micro service to act as a proxy for a http backend.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// Create a new router to the http backend
|
||||
//
|
||||
// r := NewSingleHostRouter("http://localhost:10001")
|
||||
//
|
||||
// // Create your new service
|
||||
// service := micro.NewService(
|
||||
// micro.Name("greeter"),
|
||||
// // Set the router
|
||||
// http.WithRouter(r),
|
||||
// )
|
||||
//
|
||||
// // Run the service
|
||||
// service.Run()
|
||||
func NewSingleHostRouter(url string) *Router {
|
||||
return &Router{
|
||||
Backend: url,
|
||||
// NewSingleHostProxy returns a router which sends requests to a single http backend
|
||||
func NewSingleHostProxy(url string) proxy.Proxy {
|
||||
return &Proxy{
|
||||
Endpoint: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewService returns a new http proxy. It acts as a micro service proxy.
|
||||
// Any request on the transport is routed to a fixed http backend.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// service := NewService(
|
||||
// micro.Name("greeter"),
|
||||
// // Sets the default http endpoint
|
||||
// http.WithBackend("http://localhost:10001"),
|
||||
// )
|
||||
//
|
||||
func NewService(opts ...micro.Option) micro.Service {
|
||||
// prepend router to opts
|
||||
opts = append([]micro.Option{
|
||||
WithRouter(DefaultRouter),
|
||||
}, opts...)
|
||||
// NewProxy returns a new proxy which will route using a http client
|
||||
func NewProxy(opts ...options.Option) proxy.Proxy {
|
||||
p := new(Proxy)
|
||||
p.Options = options.NewOptions(opts...)
|
||||
p.Options.Init(options.WithString("http"))
|
||||
|
||||
// create the new service
|
||||
return micro.NewService(opts...)
|
||||
// get endpoint
|
||||
ep, ok := p.Options.Values().Get("proxy.endpoint")
|
||||
if ok {
|
||||
p.Endpoint = ep.(string)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
@@ -20,7 +20,7 @@ func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"hello": "world"}`))
|
||||
}
|
||||
|
||||
func TestHTTPRouter(t *testing.T) {
|
||||
func TestHTTPProxy(t *testing.T) {
|
||||
c, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -47,7 +47,7 @@ func TestHTTPRouter(t *testing.T) {
|
||||
http.Handle("/", new(testHandler))
|
||||
|
||||
// new proxy
|
||||
p := NewSingleHostRouter(url)
|
||||
p := NewSingleHostProxy(url)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -91,32 +91,3 @@ func TestHTTPRouter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPRouterOptions(t *testing.T) {
|
||||
// test endpoint
|
||||
service := NewService(
|
||||
WithBackend("http://foo.bar"),
|
||||
)
|
||||
|
||||
r := service.Server().Options().Router
|
||||
httpRouter, ok := r.(*Router)
|
||||
if !ok {
|
||||
t.Fatal("Expected http router to be installed")
|
||||
}
|
||||
if httpRouter.Backend != "http://foo.bar" {
|
||||
t.Fatalf("Expected endpoint http://foo.bar got %v", httpRouter.Backend)
|
||||
}
|
||||
|
||||
// test router
|
||||
service = NewService(
|
||||
WithRouter(&Router{Backend: "http://foo2.bar"}),
|
||||
)
|
||||
r = service.Server().Options().Router
|
||||
httpRouter, ok = r.(*Router)
|
||||
if !ok {
|
||||
t.Fatal("Expected http router to be installed")
|
||||
}
|
||||
if httpRouter.Backend != "http://foo2.bar" {
|
||||
t.Fatalf("Expected endpoint http://foo2.bar got %v", httpRouter.Backend)
|
||||
}
|
||||
}
|
166
proxy/mucp/mucp.go
Normal file
166
proxy/mucp/mucp.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// Package mucp transparently forwards the incoming request using a go-micro client.
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/proxy"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Proxy will transparently proxy requests to an endpoint.
|
||||
// If no endpoint is specified it will call a service using the client.
|
||||
type Proxy struct {
|
||||
// embed options
|
||||
options.Options
|
||||
|
||||
// Endpoint specified the fixed service endpoint to call.
|
||||
Endpoint string
|
||||
|
||||
// The client to use for outbound requests
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
// read client request and write to server
|
||||
func readLoop(r server.Request, s client.Stream) error {
|
||||
// request to backend server
|
||||
req := s.Request()
|
||||
|
||||
for {
|
||||
// get data from client
|
||||
// no need to decode it
|
||||
body, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the header from client
|
||||
hdr := r.Header()
|
||||
msg := &codec.Message{
|
||||
Type: codec.Request,
|
||||
Header: hdr,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// write the raw request
|
||||
err = req.Codec().Write(msg, nil)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeRequest honours the server.Router interface
|
||||
func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
// set default client
|
||||
if p.Client == nil {
|
||||
p.Client = client.DefaultClient
|
||||
}
|
||||
|
||||
opts := []client.CallOption{}
|
||||
|
||||
// service name
|
||||
service := req.Service()
|
||||
endpoint := req.Endpoint()
|
||||
|
||||
// call a specific backend
|
||||
if len(p.Endpoint) > 0 {
|
||||
// address:port
|
||||
if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
|
||||
opts = append(opts, client.WithAddress(p.Endpoint))
|
||||
// use as service name
|
||||
} else {
|
||||
service = p.Endpoint
|
||||
}
|
||||
}
|
||||
|
||||
// read initial request
|
||||
body, err := req.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create new request with raw bytes body
|
||||
creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType()))
|
||||
|
||||
// create new stream
|
||||
stream, err := p.Client.Stream(ctx, creq, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// create client request read loop
|
||||
go readLoop(req, stream)
|
||||
|
||||
// get raw response
|
||||
resp := stream.Response()
|
||||
|
||||
// create server response write loop
|
||||
for {
|
||||
// read backend response body
|
||||
body, err := resp.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read backend response header
|
||||
hdr := resp.Header()
|
||||
|
||||
// write raw response header to client
|
||||
rsp.WriteHeader(hdr)
|
||||
|
||||
// write raw response body to client
|
||||
err = rsp.Write(body)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSingleHostProxy returns a proxy which sends requests to a single backend
|
||||
func NewSingleHostProxy(endpoint string) *Proxy {
|
||||
return &Proxy{
|
||||
Options: options.NewOptions(),
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewProxy returns a new proxy which will route based on mucp headers
|
||||
func NewProxy(opts ...options.Option) proxy.Proxy {
|
||||
p := new(Proxy)
|
||||
p.Options = options.NewOptions(opts...)
|
||||
p.Options.Init(options.WithString("mucp"))
|
||||
|
||||
// get endpoint
|
||||
ep, ok := p.Options.Values().Get("proxy.endpoint")
|
||||
if ok {
|
||||
p.Endpoint = ep.(string)
|
||||
}
|
||||
|
||||
// get client
|
||||
c, ok := p.Options.Values().Get("proxy.client")
|
||||
if ok {
|
||||
p.Client = c.(client.Client)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
Binary file not shown.
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: github.com/micro/go-proxy/proto/proxy.proto
|
||||
|
||||
/*
|
||||
Package proxy is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
github.com/micro/go-proxy/proto/proxy.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Request
|
||||
Response
|
||||
Message
|
||||
Empty
|
||||
*/
|
||||
package proxy
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "context"
|
||||
client "github.com/micro/go-micro/client"
|
||||
server "github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Client API for Service service
|
||||
|
||||
type Service interface {
|
||||
Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error)
|
||||
Stream(ctx context.Context, opts ...client.CallOption) (Service_StreamService, error)
|
||||
Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Empty, error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewService(name string, c client.Client) Service {
|
||||
if c == nil {
|
||||
c = client.NewClient()
|
||||
}
|
||||
if len(name) == 0 {
|
||||
name = "proxy"
|
||||
}
|
||||
return &service{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *service) Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) {
|
||||
req := c.c.NewRequest(c.name, "Service.Call", in)
|
||||
out := new(Response)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *service) Stream(ctx context.Context, opts ...client.CallOption) (Service_StreamService, error) {
|
||||
req := c.c.NewRequest(c.name, "Service.Stream", &Request{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &serviceStream{stream}, nil
|
||||
}
|
||||
|
||||
type Service_StreamService interface {
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Request) error
|
||||
Recv() (*Response, error)
|
||||
}
|
||||
|
||||
type serviceStream struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *serviceStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *serviceStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *serviceStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *serviceStream) Send(m *Request) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *serviceStream) Recv() (*Response, error) {
|
||||
m := new(Response)
|
||||
err := x.stream.Recv(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *service) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Empty, error) {
|
||||
req := c.c.NewRequest(c.name, "Service.Publish", in)
|
||||
out := new(Empty)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Service service
|
||||
|
||||
type ServiceHandler interface {
|
||||
Call(context.Context, *Request, *Response) error
|
||||
Stream(context.Context, Service_StreamStream) error
|
||||
Publish(context.Context, *Message, *Empty) error
|
||||
}
|
||||
|
||||
func RegisterServiceHandler(s server.Server, hdlr ServiceHandler, opts ...server.HandlerOption) error {
|
||||
type service interface {
|
||||
Call(ctx context.Context, in *Request, out *Response) error
|
||||
Stream(ctx context.Context, stream server.Stream) error
|
||||
Publish(ctx context.Context, in *Message, out *Empty) error
|
||||
}
|
||||
type Service struct {
|
||||
service
|
||||
}
|
||||
h := &serviceHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Service{h}, opts...))
|
||||
}
|
||||
|
||||
type serviceHandler struct {
|
||||
ServiceHandler
|
||||
}
|
||||
|
||||
func (h *serviceHandler) Call(ctx context.Context, in *Request, out *Response) error {
|
||||
return h.ServiceHandler.Call(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *serviceHandler) Stream(ctx context.Context, stream server.Stream) error {
|
||||
return h.ServiceHandler.Stream(ctx, &serviceStreamStream{stream})
|
||||
}
|
||||
|
||||
type Service_StreamStream interface {
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Response) error
|
||||
Recv() (*Request, error)
|
||||
}
|
||||
|
||||
type serviceStreamStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *serviceStreamStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *serviceStreamStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *serviceStreamStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *serviceStreamStream) Send(m *Response) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *serviceStreamStream) Recv() (*Request, error) {
|
||||
m := new(Request)
|
||||
if err := x.stream.Recv(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (h *serviceHandler) Publish(ctx context.Context, in *Message, out *Empty) error {
|
||||
return h.ServiceHandler.Publish(ctx, in, out)
|
||||
}
|
@@ -1,345 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: github.com/micro/go-proxy/proto/proxy.proto
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// 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.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Request struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_242fe7b9d67fe7d7, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
type Response struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_242fe7b9d67fe7d7, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
type Message struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Message) Reset() { *m = Message{} }
|
||||
func (m *Message) String() string { return proto.CompactTextString(m) }
|
||||
func (*Message) ProtoMessage() {}
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_242fe7b9d67fe7d7, []int{2}
|
||||
}
|
||||
|
||||
func (m *Message) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Message.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Message) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Message.Merge(m, src)
|
||||
}
|
||||
func (m *Message) XXX_Size() int {
|
||||
return xxx_messageInfo_Message.Size(m)
|
||||
}
|
||||
func (m *Message) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Message.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Message proto.InternalMessageInfo
|
||||
|
||||
type Empty struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Empty) Reset() { *m = Empty{} }
|
||||
func (m *Empty) String() string { return proto.CompactTextString(m) }
|
||||
func (*Empty) ProtoMessage() {}
|
||||
func (*Empty) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_242fe7b9d67fe7d7, []int{3}
|
||||
}
|
||||
|
||||
func (m *Empty) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Empty.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Empty.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Empty) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Empty.Merge(m, src)
|
||||
}
|
||||
func (m *Empty) XXX_Size() int {
|
||||
return xxx_messageInfo_Empty.Size(m)
|
||||
}
|
||||
func (m *Empty) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Empty.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Empty proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "proxy.Request")
|
||||
proto.RegisterType((*Response)(nil), "proxy.Response")
|
||||
proto.RegisterType((*Message)(nil), "proxy.Message")
|
||||
proto.RegisterType((*Empty)(nil), "proxy.Empty")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("github.com/micro/go-proxy/proto/proxy.proto", fileDescriptor_242fe7b9d67fe7d7)
|
||||
}
|
||||
|
||||
var fileDescriptor_242fe7b9d67fe7d7 = []byte{
|
||||
// 186 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0xcf, 0x2c, 0xc9,
|
||||
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf, 0xcd, 0x4c, 0x2e, 0xca, 0xd7, 0x4f, 0xcf, 0xd7,
|
||||
0x2d, 0x28, 0xca, 0xaf, 0xa8, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x07, 0xb3, 0xf5, 0xc0,
|
||||
0x6c, 0x21, 0x56, 0x30, 0x47, 0x89, 0x93, 0x8b, 0x3d, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44,
|
||||
0x89, 0x8b, 0x8b, 0x23, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x15, 0x24, 0xec, 0x9b, 0x5a,
|
||||
0x5c, 0x9c, 0x98, 0x9e, 0xaa, 0xc4, 0xce, 0xc5, 0xea, 0x9a, 0x5b, 0x50, 0x52, 0x69, 0x34, 0x81,
|
||||
0x91, 0x8b, 0x3d, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x48, 0x93, 0x8b, 0xc5, 0x39, 0x31,
|
||||
0x27, 0x47, 0x88, 0x4f, 0x0f, 0x62, 0x26, 0xd4, 0x0c, 0x29, 0x7e, 0x38, 0x1f, 0x6a, 0x10, 0x83,
|
||||
0x90, 0x3e, 0x17, 0x5b, 0x70, 0x49, 0x51, 0x6a, 0x62, 0x2e, 0x11, 0x8a, 0x35, 0x18, 0x0d, 0x18,
|
||||
0x85, 0x34, 0xb9, 0xd8, 0x03, 0x4a, 0x93, 0x72, 0x32, 0x8b, 0x33, 0xe0, 0x3a, 0xa0, 0x6e, 0x91,
|
||||
0xe2, 0x81, 0xf2, 0xc1, 0x0e, 0x52, 0x62, 0x48, 0x62, 0x03, 0xfb, 0xc5, 0x18, 0x10, 0x00, 0x00,
|
||||
0xff, 0xff, 0x51, 0x0d, 0x40, 0x94, 0xfa, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// ServiceClient is the client API for Service service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type ServiceClient interface {
|
||||
Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
Stream(ctx context.Context, opts ...grpc.CallOption) (Service_StreamClient, error)
|
||||
Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Empty, error)
|
||||
}
|
||||
|
||||
type serviceClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewServiceClient(cc *grpc.ClientConn) ServiceClient {
|
||||
return &serviceClient{cc}
|
||||
}
|
||||
|
||||
func (c *serviceClient) Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/proxy.Service/Call", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *serviceClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Service_StreamClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_Service_serviceDesc.Streams[0], "/proxy.Service/Stream", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &serviceStreamClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Service_StreamClient interface {
|
||||
Send(*Request) error
|
||||
Recv() (*Response, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type serviceStreamClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *serviceStreamClient) Send(m *Request) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *serviceStreamClient) Recv() (*Response, error) {
|
||||
m := new(Response)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *serviceClient) Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, "/proxy.Service/Publish", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ServiceServer is the server API for Service service.
|
||||
type ServiceServer interface {
|
||||
Call(context.Context, *Request) (*Response, error)
|
||||
Stream(Service_StreamServer) error
|
||||
Publish(context.Context, *Message) (*Empty, error)
|
||||
}
|
||||
|
||||
func RegisterServiceServer(s *grpc.Server, srv ServiceServer) {
|
||||
s.RegisterService(&_Service_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Service_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ServiceServer).Call(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proxy.Service/Call",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ServiceServer).Call(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Service_Stream_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(ServiceServer).Stream(&serviceStreamServer{stream})
|
||||
}
|
||||
|
||||
type Service_StreamServer interface {
|
||||
Send(*Response) error
|
||||
Recv() (*Request, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type serviceStreamServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *serviceStreamServer) Send(m *Response) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *serviceStreamServer) Recv() (*Request, error) {
|
||||
m := new(Request)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _Service_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Message)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ServiceServer).Publish(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proxy.Service/Publish",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ServiceServer).Publish(ctx, req.(*Message))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Service_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "proxy.Service",
|
||||
HandlerType: (*ServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Call",
|
||||
Handler: _Service_Call_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Publish",
|
||||
Handler: _Service_Publish_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Stream",
|
||||
Handler: _Service_Stream_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "github.com/micro/go-proxy/proto/proxy.proto",
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package proxy;
|
||||
|
||||
service Service {
|
||||
rpc Call(Request) returns (Response) {};
|
||||
rpc Stream(stream Request) returns (stream Response) {};
|
||||
rpc Publish(Message) returns (Empty) {};
|
||||
rpc Subscribe(Message) returns (stream Message) {};
|
||||
}
|
||||
|
||||
message Request {}
|
||||
|
||||
message Response {}
|
||||
|
||||
message Message {}
|
||||
|
||||
message Empty {}
|
31
proxy/proxy.go
Normal file
31
proxy/proxy.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Package proxy is a transparent proxy built on the go-micro/server
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Proxy can be used as a proxy server for go-micro services
|
||||
type Proxy interface {
|
||||
options.Options
|
||||
// ServeRequest honours the server.Router interface
|
||||
ServeRequest(context.Context, server.Request, server.Response) error
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultEndpoint = "localhost:9090"
|
||||
)
|
||||
|
||||
// WithEndpoint sets a proxy endpoint
|
||||
func WithEndpoint(e string) options.Option {
|
||||
return options.WithValue("proxy.endpoint", e)
|
||||
}
|
||||
|
||||
// WithClient sets the client
|
||||
func WithClient(c client.Client) options.Option {
|
||||
return options.WithValue("proxy.client", c)
|
||||
}
|
@@ -1,235 +0,0 @@
|
||||
// Package grpc transparently forwards the grpc protocol using a go-micro client.
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"github.com/micro/go-micro/server"
|
||||
"github.com/micro/go-micro/service/grpc"
|
||||
)
|
||||
|
||||
// Router will transparently proxy requests to the backend.
|
||||
// If no backend is specified it will call a service using the client.
|
||||
// If the service matches the Name it will use the server.DefaultRouter.
|
||||
type Router struct {
|
||||
// Name of the local service. In the event it's to be left alone
|
||||
Name string
|
||||
|
||||
// Backend is a single backend to route to
|
||||
// If backend is of the form address:port it will call the address.
|
||||
// Otherwise it will use it as the service name to call.
|
||||
Backend string
|
||||
|
||||
// Endpoint specified the fixed endpoint to call.
|
||||
// In the event you proxy to a fixed backend this lets you
|
||||
// call a single endpoint
|
||||
Endpoint string
|
||||
|
||||
// The client to use for outbound requests
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
var (
|
||||
// The default name of this local service
|
||||
DefaultName = "go.micro.proxy"
|
||||
// The default router
|
||||
DefaultRouter = &Router{}
|
||||
)
|
||||
|
||||
// read client request and write to server
|
||||
func readLoop(r server.Request, s client.Stream) error {
|
||||
// request to backend server
|
||||
req := s.Request()
|
||||
|
||||
for {
|
||||
// get data from client
|
||||
// no need to decode it
|
||||
body, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the header from client
|
||||
hdr := r.Header()
|
||||
msg := &codec.Message{
|
||||
Type: codec.Request,
|
||||
Header: hdr,
|
||||
Body: body,
|
||||
}
|
||||
// write the raw request
|
||||
err = req.Codec().Write(msg, nil)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeRequest honours the server.Router interface
|
||||
func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
// set the default name e.g local proxy
|
||||
if p.Name == "" {
|
||||
p.Name = DefaultName
|
||||
}
|
||||
|
||||
// set default client
|
||||
if p.Client == nil {
|
||||
p.Client = client.DefaultClient
|
||||
}
|
||||
|
||||
// check service route
|
||||
if req.Service() == p.Name {
|
||||
// use the default router
|
||||
return server.DefaultRouter.ServeRequest(ctx, req, rsp)
|
||||
}
|
||||
|
||||
opts := []client.CallOption{}
|
||||
|
||||
// service name
|
||||
service := req.Service()
|
||||
endpoint := req.Endpoint()
|
||||
|
||||
// call a specific backend
|
||||
if len(p.Backend) > 0 {
|
||||
// address:port
|
||||
if parts := strings.Split(p.Backend, ":"); len(parts) > 0 {
|
||||
opts = append(opts, client.WithAddress(p.Backend))
|
||||
// use as service name
|
||||
} else {
|
||||
service = p.Backend
|
||||
}
|
||||
}
|
||||
|
||||
// call a specific endpoint
|
||||
if len(p.Endpoint) > 0 {
|
||||
endpoint = p.Endpoint
|
||||
}
|
||||
|
||||
// read initial request
|
||||
body, err := req.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create new request with raw bytes body
|
||||
creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType()))
|
||||
|
||||
// create new stream
|
||||
stream, err := p.Client.Stream(ctx, creq, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// create client request read loop
|
||||
go readLoop(req, stream)
|
||||
|
||||
// get raw response
|
||||
resp := stream.Response()
|
||||
|
||||
// create server response write loop
|
||||
for {
|
||||
// read backend response body
|
||||
body, err := resp.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read backend response header
|
||||
hdr := resp.Header()
|
||||
|
||||
// write raw response header to client
|
||||
rsp.WriteHeader(hdr)
|
||||
|
||||
// write raw response body to client
|
||||
err = rsp.Write(body)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSingleHostRouter returns a router which sends requests to a single backend
|
||||
//
|
||||
// It is used by setting it in a new micro service to act as a proxy for a backend.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// Create a new router to the http backend
|
||||
//
|
||||
// r := NewSingleHostRouter("localhost:10001")
|
||||
//
|
||||
// // Create your new service
|
||||
// service := micro.NewService(
|
||||
// micro.Name("greeter"),
|
||||
// // Set the router
|
||||
// http.WithRouter(r),
|
||||
// )
|
||||
//
|
||||
// // Run the service
|
||||
// service.Run()
|
||||
func NewSingleHostRouter(url string) *Router {
|
||||
return &Router{
|
||||
Backend: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewService returns a new proxy. It acts as a micro service proxy.
|
||||
// Any request on the transport is routed to via the client to a service.
|
||||
// In the event a backend is specified then it routes to that backend.
|
||||
// The name of the backend can be a local address:port or a service name.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// New micro proxy routes via micro client to any service
|
||||
//
|
||||
// proxy := NewService()
|
||||
//
|
||||
// OR with address:port routes to local service
|
||||
//
|
||||
// service := NewService(
|
||||
// // Sets the default http endpoint
|
||||
// proxy.WithBackend("localhost:10001"),
|
||||
// )
|
||||
//
|
||||
// OR with service name routes to a fixed backend service
|
||||
//
|
||||
// service := NewService(
|
||||
// // Sets the backend service
|
||||
// proxy.WithBackend("greeter"),
|
||||
// )
|
||||
//
|
||||
func NewService(opts ...micro.Option) micro.Service {
|
||||
router := DefaultRouter
|
||||
name := DefaultName
|
||||
|
||||
// prepend router to opts
|
||||
opts = append([]micro.Option{
|
||||
micro.Name(name),
|
||||
WithRouter(router),
|
||||
}, opts...)
|
||||
|
||||
// create the new service
|
||||
service := grpc.NewService(opts...)
|
||||
|
||||
// set router name
|
||||
router.Name = service.Server().Options().Name
|
||||
|
||||
return service
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// WithBackend provides an option to set the proxy backend url
|
||||
func WithBackend(url string) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
// get the router
|
||||
r := o.Server.Options().Router
|
||||
|
||||
// not set
|
||||
if r == nil {
|
||||
r = DefaultRouter
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
|
||||
// check its a proxy router
|
||||
if proxyRouter, ok := r.(*Router); ok {
|
||||
proxyRouter.Backend = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithRouter provides an option to set the proxy router
|
||||
func WithRouter(r server.Router) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// WithBackend provides an option to set the http backend url
|
||||
func WithBackend(url string) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
// get the router
|
||||
r := o.Server.Options().Router
|
||||
|
||||
// not set
|
||||
if r == nil {
|
||||
r = DefaultRouter
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
|
||||
// check its a http router
|
||||
if httpRouter, ok := r.(*Router); ok {
|
||||
httpRouter.Backend = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithRouter provides an option to set the http router
|
||||
func WithRouter(r server.Router) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
}
|
@@ -1,234 +0,0 @@
|
||||
// Package mucp transparently forwards the incoming request using a go-micro client.
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Router will transparently proxy requests to the backend.
|
||||
// If no backend is specified it will call a service using the client.
|
||||
// If the service matches the Name it will use the server.DefaultRouter.
|
||||
type Router struct {
|
||||
// Name of the local service. In the event it's to be left alone
|
||||
Name string
|
||||
|
||||
// Backend is a single backend to route to
|
||||
// If backend is of the form address:port it will call the address.
|
||||
// Otherwise it will use it as the service name to call.
|
||||
Backend string
|
||||
|
||||
// Endpoint specified the fixed endpoint to call.
|
||||
// In the event you proxy to a fixed backend this lets you
|
||||
// call a single endpoint
|
||||
Endpoint string
|
||||
|
||||
// The client to use for outbound requests
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
var (
|
||||
// The default name of this local service
|
||||
DefaultName = "go.micro.proxy"
|
||||
// The default router
|
||||
DefaultRouter = &Router{}
|
||||
)
|
||||
|
||||
// read client request and write to server
|
||||
func readLoop(r server.Request, s client.Stream) error {
|
||||
// request to backend server
|
||||
req := s.Request()
|
||||
|
||||
for {
|
||||
// get data from client
|
||||
// no need to decode it
|
||||
body, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the header from client
|
||||
hdr := r.Header()
|
||||
msg := &codec.Message{
|
||||
Type: codec.Request,
|
||||
Header: hdr,
|
||||
Body: body,
|
||||
}
|
||||
// write the raw request
|
||||
err = req.Codec().Write(msg, nil)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeRequest honours the server.Router interface
|
||||
func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
// set the default name e.g local proxy
|
||||
if p.Name == "" {
|
||||
p.Name = DefaultName
|
||||
}
|
||||
|
||||
// set default client
|
||||
if p.Client == nil {
|
||||
p.Client = client.DefaultClient
|
||||
}
|
||||
|
||||
// check service route
|
||||
if req.Service() == p.Name {
|
||||
// use the default router
|
||||
return server.DefaultRouter.ServeRequest(ctx, req, rsp)
|
||||
}
|
||||
|
||||
opts := []client.CallOption{}
|
||||
|
||||
// service name
|
||||
service := req.Service()
|
||||
endpoint := req.Endpoint()
|
||||
|
||||
// call a specific backend
|
||||
if len(p.Backend) > 0 {
|
||||
// address:port
|
||||
if parts := strings.Split(p.Backend, ":"); len(parts) > 0 {
|
||||
opts = append(opts, client.WithAddress(p.Backend))
|
||||
// use as service name
|
||||
} else {
|
||||
service = p.Backend
|
||||
}
|
||||
}
|
||||
|
||||
// call a specific endpoint
|
||||
if len(p.Endpoint) > 0 {
|
||||
endpoint = p.Endpoint
|
||||
}
|
||||
|
||||
// read initial request
|
||||
body, err := req.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create new request with raw bytes body
|
||||
creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType()))
|
||||
|
||||
// create new stream
|
||||
stream, err := p.Client.Stream(ctx, creq, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// create client request read loop
|
||||
go readLoop(req, stream)
|
||||
|
||||
// get raw response
|
||||
resp := stream.Response()
|
||||
|
||||
// create server response write loop
|
||||
for {
|
||||
// read backend response body
|
||||
body, err := resp.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read backend response header
|
||||
hdr := resp.Header()
|
||||
|
||||
// write raw response header to client
|
||||
rsp.WriteHeader(hdr)
|
||||
|
||||
// write raw response body to client
|
||||
err = rsp.Write(body)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSingleHostRouter returns a router which sends requests to a single backend
|
||||
//
|
||||
// It is used by setting it in a new micro service to act as a proxy for a backend.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// Create a new router to the http backend
|
||||
//
|
||||
// r := NewSingleHostRouter("localhost:10001")
|
||||
//
|
||||
// // Create your new service
|
||||
// service := micro.NewService(
|
||||
// micro.Name("greeter"),
|
||||
// // Set the router
|
||||
// http.WithRouter(r),
|
||||
// )
|
||||
//
|
||||
// // Run the service
|
||||
// service.Run()
|
||||
func NewSingleHostRouter(url string) *Router {
|
||||
return &Router{
|
||||
Backend: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewService returns a new proxy. It acts as a micro service proxy.
|
||||
// Any request on the transport is routed to via the client to a service.
|
||||
// In the event a backend is specified then it routes to that backend.
|
||||
// The name of the backend can be a local address:port or a service name.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// New micro proxy routes via micro client to any service
|
||||
//
|
||||
// proxy := NewService()
|
||||
//
|
||||
// OR with address:port routes to local service
|
||||
//
|
||||
// service := NewService(
|
||||
// // Sets the default http endpoint
|
||||
// proxy.WithBackend("localhost:10001"),
|
||||
// )
|
||||
//
|
||||
// OR with service name routes to a fixed backend service
|
||||
//
|
||||
// service := NewService(
|
||||
// // Sets the backend service
|
||||
// proxy.WithBackend("greeter"),
|
||||
// )
|
||||
//
|
||||
func NewService(opts ...micro.Option) micro.Service {
|
||||
router := DefaultRouter
|
||||
name := DefaultName
|
||||
|
||||
// prepend router to opts
|
||||
opts = append([]micro.Option{
|
||||
micro.Name(name),
|
||||
WithRouter(router),
|
||||
}, opts...)
|
||||
|
||||
// create the new service
|
||||
service := micro.NewService(opts...)
|
||||
|
||||
// set router name
|
||||
router.Name = service.Server().Options().Name
|
||||
|
||||
return service
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// WithBackend provides an option to set the proxy backend url
|
||||
func WithBackend(url string) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
// get the router
|
||||
r := o.Server.Options().Router
|
||||
|
||||
// not set
|
||||
if r == nil {
|
||||
r = DefaultRouter
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
|
||||
// check its a proxy router
|
||||
if proxyRouter, ok := r.(*Router); ok {
|
||||
proxyRouter.Backend = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithRouter provides an option to set the proxy router
|
||||
func WithRouter(r server.Router) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package consul
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -246,14 +245,16 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
|
||||
func (cw *consulWatcher) Next() (*registry.Result, error) {
|
||||
select {
|
||||
case <-cw.exit:
|
||||
return nil, errors.New("result chan closed")
|
||||
return nil, registry.ErrWatcherStopped
|
||||
case r, ok := <-cw.next:
|
||||
if !ok {
|
||||
return nil, errors.New("result chan closed")
|
||||
return nil, registry.ErrWatcherStopped
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
return nil, errors.New("result chan closed")
|
||||
// NOTE: This is a dead code path: e.g. it will never be reached
|
||||
// as we return in all previous code paths never leading to this return
|
||||
return nil, registry.ErrWatcherStopped
|
||||
}
|
||||
|
||||
func (cw *consulWatcher) Stop() {
|
||||
|
@@ -734,6 +734,12 @@ func (g *gossipRegistry) Register(s *registry.Service, opts ...registry.Register
|
||||
notify: nil,
|
||||
})
|
||||
|
||||
// send update to local watchers
|
||||
g.updates <- &update{
|
||||
Update: up,
|
||||
Service: s,
|
||||
}
|
||||
|
||||
// wait
|
||||
<-time.After(g.interval * 2)
|
||||
|
||||
@@ -770,6 +776,12 @@ func (g *gossipRegistry) Deregister(s *registry.Service) error {
|
||||
notify: nil,
|
||||
})
|
||||
|
||||
// send update to local watchers
|
||||
g.updates <- &update{
|
||||
Update: up,
|
||||
Service: s,
|
||||
}
|
||||
|
||||
// wait
|
||||
<-time.After(g.interval * 2)
|
||||
|
||||
|
@@ -115,27 +115,20 @@ func delNodes(old, del []*registry.Node) []*registry.Node {
|
||||
|
||||
func delServices(old, del []*registry.Service) []*registry.Service {
|
||||
var services []*registry.Service
|
||||
|
||||
for _, o := range old {
|
||||
srv := new(registry.Service)
|
||||
*srv = *o
|
||||
|
||||
var rem bool
|
||||
|
||||
for _, s := range del {
|
||||
if srv.Version == s.Version {
|
||||
srv.Nodes = delNodes(srv.Nodes, s.Nodes)
|
||||
|
||||
if len(srv.Nodes) == 0 {
|
||||
if o.Version == s.Version {
|
||||
s.Nodes = delNodes(s.Nodes, o.Nodes)
|
||||
if len(s.Nodes) == 0 {
|
||||
rem = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !rem {
|
||||
services = append(services, srv)
|
||||
services = append(services, o)
|
||||
}
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ var (
|
||||
timeout = time.Millisecond * 10
|
||||
)
|
||||
|
||||
/*
|
||||
// Setup sets mock data
|
||||
func (m *Registry) Setup() {
|
||||
m.Lock()
|
||||
@@ -30,6 +31,7 @@ func (m *Registry) Setup() {
|
||||
// add some memory data
|
||||
m.Services = Data
|
||||
}
|
||||
*/
|
||||
|
||||
func (m *Registry) watch(r *registry.Result) {
|
||||
var watchers []*Watcher
|
||||
|
323
router/default_router.go
Normal file
323
router/default_router.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
// AdvertiseTick defines how often in seconds do we scal the local registry
|
||||
// to advertise the local services to the network registry
|
||||
AdvertiseTick = 5 * time.Second
|
||||
// AdvertiseTTL defines network registry TTL in seconds
|
||||
// NOTE: this is a rather arbitrary picked value subject to change
|
||||
AdvertiseTTL = 120 * time.Second
|
||||
)
|
||||
|
||||
type router struct {
|
||||
opts Options
|
||||
exit chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// newRouter creates new router and returns it
|
||||
func newRouter(opts ...Option) Router {
|
||||
// get default options
|
||||
options := DefaultOptions()
|
||||
|
||||
// apply requested options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return &router{
|
||||
opts: options,
|
||||
exit: make(chan struct{}),
|
||||
wg: &sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes router with given options
|
||||
func (r *router) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&r.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options returns router options
|
||||
func (r *router) Options() Options {
|
||||
return r.opts
|
||||
}
|
||||
|
||||
// ID returns router ID
|
||||
func (r *router) ID() string {
|
||||
return r.opts.ID
|
||||
}
|
||||
|
||||
// Table returns routing table
|
||||
func (r *router) Table() Table {
|
||||
return r.opts.Table
|
||||
}
|
||||
|
||||
// Address returns router's bind address
|
||||
func (r *router) Address() string {
|
||||
return r.opts.Address
|
||||
}
|
||||
|
||||
// Network returns the address router advertises to the network
|
||||
func (r *router) Network() string {
|
||||
return r.opts.Advertise
|
||||
}
|
||||
|
||||
// Advertise advertises the router routes to the network.
|
||||
// Advertise is a blocking function. It launches multiple goroutines that watch
|
||||
// service registries and advertise the router routes to other routers in the network.
|
||||
// It returns error if any of the launched goroutines fail with error.
|
||||
func (r *router) Advertise() error {
|
||||
// add local service routes into the routing table
|
||||
if err := r.addServiceRoutes(r.opts.Registry, DefaultLocalMetric); err != nil {
|
||||
return fmt.Errorf("failed adding routes for local services: %v", err)
|
||||
}
|
||||
|
||||
// add network service routes into the routing table
|
||||
if err := r.addServiceRoutes(r.opts.Network, DefaultNetworkMetric); err != nil {
|
||||
return fmt.Errorf("failed adding routes for network services: %v", err)
|
||||
}
|
||||
|
||||
node, err := r.parseToNode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse router into service node: %v", err)
|
||||
}
|
||||
|
||||
localWatcher, err := r.opts.Registry.Watch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create local registry watcher: %v", err)
|
||||
}
|
||||
|
||||
networkWatcher, err := r.opts.Network.Watch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create network registry watcher: %v", err)
|
||||
}
|
||||
|
||||
// error channel collecting goroutine errors
|
||||
errChan := make(chan error, 3)
|
||||
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
// watch local registry and register routes in routine table
|
||||
errChan <- r.manageServiceRoutes(localWatcher, DefaultLocalMetric)
|
||||
}()
|
||||
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
// watch network registry and register routes in routine table
|
||||
errChan <- r.manageServiceRoutes(networkWatcher, DefaultNetworkMetric)
|
||||
}()
|
||||
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
// watch local registry and advertise local service to the network
|
||||
errChan <- r.advertiseToNetwork(node)
|
||||
}()
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// addServiceRoutes adds all services in given registry to the routing table.
|
||||
// NOTE: this is a one-off operation done when bootstrapping the routing table of the new router.
|
||||
// It returns error if either the services could not be listed or if the routes could not be added to the routing table.
|
||||
func (r *router) addServiceRoutes(reg registry.Registry, metric int) error {
|
||||
services, err := reg.ListServices()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list services: %v", err)
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
route := Route{
|
||||
Destination: service.Name,
|
||||
Router: r,
|
||||
Network: r.opts.Advertise,
|
||||
Metric: metric,
|
||||
}
|
||||
if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
|
||||
return fmt.Errorf("error adding route for service: %s", service.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseToNode parses router into registry.Node and returns the result.
|
||||
// It returns error if the router network address could not be parsed into host and port.
|
||||
func (r *router) parseToNode() (*registry.Node, error) {
|
||||
// split router address to host and port part
|
||||
addr, portStr, err := net.SplitHostPort(r.opts.Advertise)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse router address: %v", err)
|
||||
}
|
||||
|
||||
// try to parse network port into integer
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse router network address: %v", err)
|
||||
}
|
||||
|
||||
node := ®istry.Node{
|
||||
Id: r.opts.ID,
|
||||
Address: addr,
|
||||
Port: port,
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// advertiseToNetwork periodically scans local registry and registers (i.e. advertises) all the local services in the network registry.
|
||||
// It returns error if either the local services failed to be listed or if it fails to register local service in network registry.
|
||||
func (r *router) advertiseToNetwork(node *registry.Node) error {
|
||||
// ticker to periodically scan the local registry
|
||||
ticker := time.NewTicker(AdvertiseTick)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-r.exit:
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
// list all local services
|
||||
services, err := r.opts.Registry.ListServices()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list local services: %v", err)
|
||||
}
|
||||
// loop through all registered local services and register them in the network registry
|
||||
for _, service := range services {
|
||||
svc := ®istry.Service{
|
||||
Name: service.Name,
|
||||
Nodes: []*registry.Node{node},
|
||||
}
|
||||
// register the local service in the network registry
|
||||
if err := r.opts.Network.Register(svc, registry.RegisterTTL(AdvertiseTTL)); err != nil {
|
||||
return fmt.Errorf("failed to register service %s in network registry: %v", svc.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// manageServiceRoutes watches services in given registry and updates the routing table accordingly.
|
||||
// It returns error if the service registry watcher has stopped or if the routing table failed to be updated.
|
||||
func (r *router) manageServiceRoutes(w registry.Watcher, metric int) error {
|
||||
// wait in the background for the router to stop
|
||||
// when the router stops, stop the watcher and exit
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
<-r.exit
|
||||
w.Stop()
|
||||
}()
|
||||
|
||||
var watchErr error
|
||||
|
||||
for {
|
||||
res, err := w.Next()
|
||||
if err == registry.ErrWatcherStopped {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
watchErr = err
|
||||
break
|
||||
}
|
||||
|
||||
route := Route{
|
||||
Destination: res.Service.Name,
|
||||
Router: r,
|
||||
Network: r.opts.Advertise,
|
||||
Metric: metric,
|
||||
}
|
||||
|
||||
switch res.Action {
|
||||
case "create":
|
||||
if len(res.Service.Nodes) > 0 {
|
||||
// only return error if the route is not duplicate, but something else has failed
|
||||
if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
|
||||
return fmt.Errorf("failed to add route for service: %v", res.Service.Name)
|
||||
}
|
||||
}
|
||||
case "delete":
|
||||
if len(res.Service.Nodes) < 1 {
|
||||
// only return error if the route is present in the table, but something else has failed
|
||||
if err := r.opts.Table.Delete(route); err != nil && err != ErrRouteNotFound {
|
||||
return fmt.Errorf("failed to delete route for service: %v", res.Service.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return watchErr
|
||||
}
|
||||
|
||||
// Stop stops the router
|
||||
func (r *router) Stop() error {
|
||||
// notify all goroutines to finish
|
||||
close(r.exit)
|
||||
|
||||
// wait for all goroutines to finish
|
||||
r.wg.Wait()
|
||||
|
||||
// NOTE: we need a more efficient way of doing this e.g. network routes
|
||||
// should ideally be autodeleted when the router stops gossiping
|
||||
query := NewQuery(QueryRouter(r), QueryNetwork(r.opts.Advertise))
|
||||
routes, err := r.opts.Table.Lookup(query)
|
||||
if err != nil && err != ErrRouteNotFound {
|
||||
return fmt.Errorf("failed to lookup routes for router %s: %v", r.opts.ID, err)
|
||||
}
|
||||
|
||||
// parse router to registry.Node
|
||||
node, err := r.parseToNode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse router into service node: %v", err)
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
service := ®istry.Service{
|
||||
Name: route.Destination,
|
||||
Nodes: []*registry.Node{node},
|
||||
}
|
||||
if err := r.opts.Network.Deregister(service); err != nil {
|
||||
return fmt.Errorf("failed to deregister service %s from network registry: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String prints debugging information about router
|
||||
func (r *router) String() string {
|
||||
sb := &strings.Builder{}
|
||||
|
||||
table := tablewriter.NewWriter(sb)
|
||||
table.SetHeader([]string{"ID", "Address", "Network", "Table"})
|
||||
|
||||
data := []string{
|
||||
r.opts.ID,
|
||||
r.opts.Address,
|
||||
r.opts.Advertise,
|
||||
fmt.Sprintf("%d", r.opts.Table.Size()),
|
||||
}
|
||||
table.Append(data)
|
||||
|
||||
// render table into sb
|
||||
table.Render()
|
||||
|
||||
return sb.String()
|
||||
}
|
289
router/default_table.go
Normal file
289
router/default_table.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// TableOptions are routing table options
|
||||
// TODO: table options TBD in the future
|
||||
type TableOptions struct{}
|
||||
|
||||
// table is in memory routing table
|
||||
type table struct {
|
||||
// opts are table options
|
||||
opts TableOptions
|
||||
// m stores routing table map
|
||||
m map[string]map[uint64]Route
|
||||
// h hashes route entries
|
||||
h hash.Hash64
|
||||
// w is a list of table watchers
|
||||
w map[string]*tableWatcher
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// newTable creates in memory routing table and returns it
|
||||
func newTable(opts ...TableOption) Table {
|
||||
// default options
|
||||
var options TableOptions
|
||||
|
||||
// apply requested options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
h := fnv.New64()
|
||||
h.Reset()
|
||||
|
||||
return &table{
|
||||
opts: options,
|
||||
m: make(map[string]map[uint64]Route),
|
||||
w: make(map[string]*tableWatcher),
|
||||
h: h,
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes routing table with options
|
||||
func (t *table) Init(opts ...TableOption) error {
|
||||
for _, o := range opts {
|
||||
o(&t.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options returns routing table options
|
||||
func (t *table) Options() TableOptions {
|
||||
return t.opts
|
||||
}
|
||||
|
||||
// Add adds a route to the routing table
|
||||
func (t *table) Add(r Route) error {
|
||||
destAddr := r.Destination
|
||||
sum := t.hash(r)
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// check if the destination has any routes in the table
|
||||
if _, ok := t.m[destAddr]; !ok {
|
||||
t.m[destAddr] = make(map[uint64]Route)
|
||||
t.m[destAddr][sum] = r
|
||||
go t.sendEvent(&Event{Type: CreateEvent, Route: r})
|
||||
return nil
|
||||
}
|
||||
|
||||
// add new route to the table for the given destination
|
||||
if _, ok := t.m[destAddr][sum]; !ok {
|
||||
t.m[destAddr][sum] = r
|
||||
go t.sendEvent(&Event{Type: CreateEvent, Route: r})
|
||||
return nil
|
||||
}
|
||||
|
||||
// only add the route if the route override is explicitly requested
|
||||
if _, ok := t.m[destAddr][sum]; ok && r.Policy == OverrideIfExists {
|
||||
t.m[destAddr][sum] = r
|
||||
go t.sendEvent(&Event{Type: UpdateEvent, Route: r})
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we reached this point without already returning the route already exists
|
||||
// we return nil only if explicitly requested by the client
|
||||
if r.Policy == IgnoreIfExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrDuplicateRoute
|
||||
}
|
||||
|
||||
// Delete deletes the route from the routing table
|
||||
func (t *table) Delete(r Route) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
destAddr := r.Destination
|
||||
sum := t.hash(r)
|
||||
|
||||
if _, ok := t.m[destAddr]; !ok {
|
||||
return ErrRouteNotFound
|
||||
}
|
||||
|
||||
delete(t.m[destAddr], sum)
|
||||
go t.sendEvent(&Event{Type: DeleteEvent, Route: r})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates routing table with new route
|
||||
func (t *table) Update(r Route) error {
|
||||
destAddr := r.Destination
|
||||
sum := t.hash(r)
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// check if the destAddr has ANY routes in the table
|
||||
if _, ok := t.m[destAddr]; !ok {
|
||||
return ErrRouteNotFound
|
||||
}
|
||||
|
||||
// if the route has been found update it
|
||||
if _, ok := t.m[destAddr][sum]; ok {
|
||||
t.m[destAddr][sum] = r
|
||||
go t.sendEvent(&Event{Type: UpdateEvent, Route: r})
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrRouteNotFound
|
||||
}
|
||||
|
||||
// List returns a list of all routes in the table
|
||||
func (t *table) List() ([]Route, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
var routes []Route
|
||||
for _, rmap := range t.m {
|
||||
for _, route := range rmap {
|
||||
routes = append(routes, route)
|
||||
}
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// Lookup queries routing table and returns all routes that match it
|
||||
func (t *table) Lookup(q Query) ([]Route, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
var results []Route
|
||||
|
||||
for destAddr, routes := range t.m {
|
||||
if q.Options().Destination != "*" {
|
||||
if q.Options().Destination != destAddr {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
if q.Options().Network == "*" || q.Options().Network == route.Network {
|
||||
if q.Options().Router.ID() == "*" || q.Options().Router.ID() == route.Router.ID() {
|
||||
if route.Metric <= q.Options().Metric {
|
||||
results = append(results, route)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if q.Options().Destination == "*" {
|
||||
for _, route := range routes {
|
||||
if q.Options().Network == "*" || q.Options().Network == route.Router.Network() {
|
||||
if q.Options().Router.ID() == "*" || q.Options().Router.ID() == route.Router.ID() {
|
||||
if route.Metric <= q.Options().Metric {
|
||||
results = append(results, route)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) == 0 && q.Options().Policy != DiscardNoRoute {
|
||||
return nil, ErrRouteNotFound
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Watch returns routing table entry watcher
|
||||
func (t *table) Watch(opts ...WatchOption) (Watcher, error) {
|
||||
// by default watch everything
|
||||
wopts := WatchOptions{
|
||||
Destination: "*",
|
||||
Network: "*",
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&wopts)
|
||||
}
|
||||
|
||||
watcher := &tableWatcher{
|
||||
opts: wopts,
|
||||
resChan: make(chan *Event, 10),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
t.w[uuid.New().String()] = watcher
|
||||
t.Unlock()
|
||||
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
// sendEvent sends rules to all subscribe watchers
|
||||
func (t *table) sendEvent(r *Event) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
for _, w := range t.w {
|
||||
select {
|
||||
case w.resChan <- r:
|
||||
case <-w.done:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the size of the routing table
|
||||
func (t *table) Size() int {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
return len(t.m)
|
||||
}
|
||||
|
||||
// String returns debug information
|
||||
func (t *table) String() string {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
// this will help us build routing table string
|
||||
sb := &strings.Builder{}
|
||||
|
||||
// create nice table printing structure
|
||||
table := tablewriter.NewWriter(sb)
|
||||
table.SetHeader([]string{"Destination", "Router", "Network", "Metric"})
|
||||
|
||||
for _, destRoute := range t.m {
|
||||
for _, route := range destRoute {
|
||||
strRoute := []string{
|
||||
route.Destination,
|
||||
route.Router.Address(),
|
||||
route.Network,
|
||||
fmt.Sprintf("%d", route.Metric),
|
||||
}
|
||||
table.Append(strRoute)
|
||||
}
|
||||
}
|
||||
|
||||
// render table into sb
|
||||
table.Render()
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// hash hashes the route using router gateway and network address
|
||||
func (t *table) hash(r Route) uint64 {
|
||||
destAddr := r.Destination
|
||||
routerAddr := r.Router.Address()
|
||||
netAddr := r.Network
|
||||
|
||||
t.h.Reset()
|
||||
t.h.Write([]byte(destAddr + routerAddr + netAddr))
|
||||
|
||||
return t.h.Sum64()
|
||||
}
|
84
router/options.go
Normal file
84
router/options.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultAddress is default router address
|
||||
DefaultAddress = ":9093"
|
||||
// DefaultAdvertise is default address advertised to the network
|
||||
DefaultAdvertise = ":9094"
|
||||
)
|
||||
|
||||
// Options are router options
|
||||
type Options struct {
|
||||
// ID is router id
|
||||
ID string
|
||||
// Address is router address
|
||||
Address string
|
||||
// Advertise is the address advertised to the network
|
||||
Advertise string
|
||||
// Registry is the local registry
|
||||
Registry registry.Registry
|
||||
// Networkis the network registry
|
||||
Network registry.Registry
|
||||
// Table is routing table
|
||||
Table Table
|
||||
}
|
||||
|
||||
// ID sets Router ID
|
||||
func ID(id string) Option {
|
||||
return func(o *Options) {
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
// Address sets router service address
|
||||
func Address(a string) Option {
|
||||
return func(o *Options) {
|
||||
o.Address = a
|
||||
}
|
||||
}
|
||||
|
||||
// Advertise sets the address that is advertise to the network
|
||||
func Advertise(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Advertise = n
|
||||
}
|
||||
}
|
||||
|
||||
// RoutingTable sets the routing table
|
||||
func RoutingTable(t Table) Option {
|
||||
return func(o *Options) {
|
||||
o.Table = t
|
||||
}
|
||||
}
|
||||
|
||||
// Registry sets the local registry
|
||||
func Registry(r registry.Registry) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
}
|
||||
}
|
||||
|
||||
// Network sets the network registry
|
||||
func Network(r registry.Registry) Option {
|
||||
return func(o *Options) {
|
||||
o.Network = r
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultOptions returns router default options
|
||||
func DefaultOptions() Options {
|
||||
// NOTE: by default both local and network registies use default registry i.e. mdns
|
||||
return Options{
|
||||
ID: uuid.New().String(),
|
||||
Address: DefaultAddress,
|
||||
Advertise: DefaultAdvertise,
|
||||
Registry: registry.DefaultRegistry,
|
||||
Network: registry.DefaultRegistry,
|
||||
Table: NewTable(),
|
||||
}
|
||||
}
|
116
router/query.go
Normal file
116
router/query.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package router
|
||||
|
||||
// LookupPolicy defines query policy
|
||||
type LookupPolicy int
|
||||
|
||||
const (
|
||||
// DiscardNoRoute discards query when no route is found
|
||||
DiscardNoRoute LookupPolicy = iota
|
||||
// ClosestMatch returns closest match to supplied query
|
||||
ClosestMatch
|
||||
)
|
||||
|
||||
// String returns human representation of LookupPolicy
|
||||
func (lp LookupPolicy) String() string {
|
||||
switch lp {
|
||||
case DiscardNoRoute:
|
||||
return "DISCARD"
|
||||
case ClosestMatch:
|
||||
return "CLOSEST"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// QueryOption sets routing table query options
|
||||
type QueryOption func(*QueryOptions)
|
||||
|
||||
// QueryOptions are routing table query options
|
||||
type QueryOptions struct {
|
||||
// Destination is destination address
|
||||
Destination string
|
||||
// Network is network address
|
||||
Network string
|
||||
// Router is gateway address
|
||||
Router Router
|
||||
// Metric is route metric
|
||||
Metric int
|
||||
// Policy is query lookup policy
|
||||
Policy LookupPolicy
|
||||
}
|
||||
|
||||
// QueryDestination sets query destination address
|
||||
func QueryDestination(a string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
o.Destination = a
|
||||
}
|
||||
}
|
||||
|
||||
// QueryNetwork sets query network address
|
||||
func QueryNetwork(a string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
o.Network = a
|
||||
}
|
||||
}
|
||||
|
||||
// QueryRouter sets query gateway address
|
||||
func QueryRouter(r Router) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
o.Router = r
|
||||
}
|
||||
}
|
||||
|
||||
// QueryMetric sets query metric
|
||||
func QueryMetric(m int) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
o.Metric = m
|
||||
}
|
||||
}
|
||||
|
||||
// QueryPolicy sets query policy
|
||||
// NOTE: this might be renamed to filter or some such
|
||||
func QueryPolicy(p LookupPolicy) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
o.Policy = p
|
||||
}
|
||||
}
|
||||
|
||||
// Query is routing table query
|
||||
type Query interface {
|
||||
// Options returns query options
|
||||
Options() QueryOptions
|
||||
}
|
||||
|
||||
// query is a basic implementation of Query
|
||||
type query struct {
|
||||
opts QueryOptions
|
||||
}
|
||||
|
||||
// NewQuery creates new query and returns it
|
||||
func NewQuery(opts ...QueryOption) Query {
|
||||
// default gateway for wildcard router
|
||||
r := newRouter(ID("*"))
|
||||
|
||||
// default options
|
||||
// NOTE: by default we use DefaultNetworkMetric
|
||||
qopts := QueryOptions{
|
||||
Destination: "*",
|
||||
Network: "*",
|
||||
Router: r,
|
||||
Metric: DefaultNetworkMetric,
|
||||
Policy: DiscardNoRoute,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&qopts)
|
||||
}
|
||||
|
||||
return &query{
|
||||
opts: qopts,
|
||||
}
|
||||
}
|
||||
|
||||
// Options returns query options
|
||||
func (q *query) Options() QueryOptions {
|
||||
return q.opts
|
||||
}
|
74
router/route.go
Normal file
74
router/route.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultLocalMetric is default route cost for local network
|
||||
DefaultLocalMetric = 1
|
||||
// DefaultNetworkMetric is default route cost for micro network
|
||||
DefaultNetworkMetric = 10
|
||||
)
|
||||
|
||||
// RoutePolicy defines routing table addition policy
|
||||
type RoutePolicy int
|
||||
|
||||
const (
|
||||
// OverrideIfExists overrides route if it already exists
|
||||
OverrideIfExists RoutePolicy = iota
|
||||
// IgnoreIfExists does not modify existing route
|
||||
IgnoreIfExists
|
||||
)
|
||||
|
||||
// String returns human reprensentation of policy
|
||||
func (p RoutePolicy) String() string {
|
||||
switch p {
|
||||
case OverrideIfExists:
|
||||
return "OVERRIDE"
|
||||
case IgnoreIfExists:
|
||||
return "IGNORE"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// Route is network route
|
||||
type Route struct {
|
||||
// Destination is destination address
|
||||
Destination string
|
||||
// Router is the network router
|
||||
Router Router
|
||||
// Network is micro network address
|
||||
Network string
|
||||
// Metric is the route cost metric
|
||||
Metric int
|
||||
// Policy defines route policy
|
||||
Policy RoutePolicy
|
||||
}
|
||||
|
||||
// String allows to print the route
|
||||
func (r *Route) String() string {
|
||||
// this will help us build routing table string
|
||||
sb := &strings.Builder{}
|
||||
|
||||
// create nice table printing structure
|
||||
table := tablewriter.NewWriter(sb)
|
||||
table.SetHeader([]string{"Destination", "Router", "Network", "Metric"})
|
||||
|
||||
strRoute := []string{
|
||||
r.Destination,
|
||||
r.Router.Address(),
|
||||
r.Network,
|
||||
fmt.Sprintf("%d", r.Metric),
|
||||
}
|
||||
table.Append(strRoute)
|
||||
|
||||
// render table into sb
|
||||
table.Render()
|
||||
|
||||
return sb.String()
|
||||
}
|
32
router/router.go
Normal file
32
router/router.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Package router provides an interface for micro network router
|
||||
package router
|
||||
|
||||
// Router is micro network router
|
||||
type Router interface {
|
||||
// Init initializes the router with options
|
||||
Init(...Option) error
|
||||
// Options returns the router options
|
||||
Options() Options
|
||||
// ID returns the id of the router
|
||||
ID() string
|
||||
// Table returns the routing table
|
||||
Table() Table
|
||||
// Address returns the router adddress
|
||||
Address() string
|
||||
// Network returns the network address of the router
|
||||
Network() string
|
||||
// Advertise starts advertising the routes to the network
|
||||
Advertise() error
|
||||
// Stop stops the router
|
||||
Stop() error
|
||||
// String returns debug info
|
||||
String() string
|
||||
}
|
||||
|
||||
// Option used by the router
|
||||
type Option func(*Options)
|
||||
|
||||
// NewRouter creates new Router and returns it
|
||||
func NewRouter(opts ...Option) Router {
|
||||
return newRouter(opts...)
|
||||
}
|
44
router/table.go
Normal file
44
router/table.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrRouteNotFound is returned when no route was found in the routing table
|
||||
ErrRouteNotFound = errors.New("route not found")
|
||||
// ErrDuplicateRoute is returned when the route already exists
|
||||
ErrDuplicateRoute = errors.New("duplicate route")
|
||||
)
|
||||
|
||||
// Table defines routing table interface
|
||||
type Table interface {
|
||||
// Init initializes the router with options
|
||||
Init(...TableOption) error
|
||||
// Options returns the router options
|
||||
Options() TableOptions
|
||||
// Add adds new route to the routing table
|
||||
Add(Route) error
|
||||
// Delete deletes existing route from the routing table
|
||||
Delete(Route) error
|
||||
// Update updates route in the routing table
|
||||
Update(Route) error
|
||||
// List returns the list of all routes in the table
|
||||
List() ([]Route, error)
|
||||
// Lookup looks up routes in the routing table and returns them
|
||||
Lookup(Query) ([]Route, error)
|
||||
// Watch returns a watcher which allows to track updates to the routing table
|
||||
Watch(opts ...WatchOption) (Watcher, error)
|
||||
// Size returns the size of the routing table
|
||||
Size() int
|
||||
// String prints the routing table
|
||||
String() string
|
||||
}
|
||||
|
||||
// TableOption used by the routing table
|
||||
type TableOption func(*TableOptions)
|
||||
|
||||
// NewTable creates new routing table and returns it
|
||||
func NewTable(opts ...TableOption) Table {
|
||||
return newTable(opts...)
|
||||
}
|
147
router/table_watcher.go
Normal file
147
router/table_watcher.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrWatcherStopped is returned when routing table watcher has been stopped
|
||||
ErrWatcherStopped = errors.New("routing table watcher stopped")
|
||||
)
|
||||
|
||||
// EventType defines routing table event
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
// CreateEvent is emitted when new route has been created
|
||||
CreateEvent EventType = iota
|
||||
// DeleteEvent is emitted when an existing route has been deleted
|
||||
DeleteEvent
|
||||
// UpdateEvent is emitted when a routing table has been updated
|
||||
UpdateEvent
|
||||
)
|
||||
|
||||
// String returns string representation of the event
|
||||
func (et EventType) String() string {
|
||||
switch et {
|
||||
case CreateEvent:
|
||||
return "CREATE"
|
||||
case DeleteEvent:
|
||||
return "DELETE"
|
||||
case UpdateEvent:
|
||||
return "UPDATE"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// Event is returned by a call to Next on the watcher.
|
||||
type Event struct {
|
||||
// Type defines type of event
|
||||
Type EventType
|
||||
// Route is table rout
|
||||
Route Route
|
||||
}
|
||||
|
||||
// WatchOption is used to define what routes to watch in the table
|
||||
type WatchOption func(*WatchOptions)
|
||||
|
||||
// Watcher defines routing table watcher interface
|
||||
// Watcher returns updates to the routing table
|
||||
type Watcher interface {
|
||||
// Next is a blocking call that returns watch result
|
||||
Next() (*Event, error)
|
||||
// Chan returns event channel
|
||||
Chan() (<-chan *Event, error)
|
||||
// Stop stops watcher
|
||||
Stop()
|
||||
}
|
||||
|
||||
// WatchOptions are table watcher options
|
||||
type WatchOptions struct {
|
||||
// Specify destination address to watch
|
||||
Destination string
|
||||
// Specify network to watch
|
||||
Network string
|
||||
}
|
||||
|
||||
// WatchDestination sets what destination to watch
|
||||
// Destination is usually microservice name
|
||||
func WatchDestination(a string) WatchOption {
|
||||
return func(o *WatchOptions) {
|
||||
o.Destination = a
|
||||
}
|
||||
}
|
||||
|
||||
// WatchNetwork sets what network to watch
|
||||
func WatchNetwork(n string) WatchOption {
|
||||
return func(o *WatchOptions) {
|
||||
o.Network = n
|
||||
}
|
||||
}
|
||||
|
||||
type tableWatcher struct {
|
||||
opts WatchOptions
|
||||
resChan chan *Event
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// Next returns the next noticed action taken on table
|
||||
// TODO: this needs to be thought through properly
|
||||
// we are aiming to provide the same options Query provides
|
||||
func (w *tableWatcher) Next() (*Event, error) {
|
||||
for {
|
||||
select {
|
||||
case res := <-w.resChan:
|
||||
switch w.opts.Destination {
|
||||
case "*", "":
|
||||
if w.opts.Network == "*" || w.opts.Network == res.Route.Network {
|
||||
return res, nil
|
||||
}
|
||||
case res.Route.Destination:
|
||||
if w.opts.Network == "*" || w.opts.Network == res.Route.Network {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
case <-w.done:
|
||||
return nil, ErrWatcherStopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chan returns watcher events channel
|
||||
func (w *tableWatcher) Chan() (<-chan *Event, error) {
|
||||
return w.resChan, nil
|
||||
}
|
||||
|
||||
// Stop stops routing table watcher
|
||||
func (w *tableWatcher) Stop() {
|
||||
select {
|
||||
case <-w.done:
|
||||
return
|
||||
default:
|
||||
close(w.done)
|
||||
}
|
||||
}
|
||||
|
||||
// String prints debug information
|
||||
func (w *tableWatcher) String() string {
|
||||
sb := &strings.Builder{}
|
||||
|
||||
table := tablewriter.NewWriter(sb)
|
||||
table.SetHeader([]string{"Destination", "Network"})
|
||||
|
||||
data := []string{
|
||||
w.opts.Destination,
|
||||
w.opts.Network,
|
||||
}
|
||||
table.Append(data)
|
||||
|
||||
// render table into sb
|
||||
table.Render()
|
||||
|
||||
return sb.String()
|
||||
}
|
51
selector/common_test.go
Normal file
51
selector/common_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost",
|
||||
Port: 6666,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost",
|
||||
Port: 8888,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -10,7 +10,8 @@ func TestRegistrySelector(t *testing.T) {
|
||||
counts := map[string]int{}
|
||||
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
rg := r.(*memory.Registry)
|
||||
rg.Services = testData
|
||||
cache := NewSelector(Registry(r))
|
||||
|
||||
next, err := cache.Select("foo")
|
||||
|
@@ -3,17 +3,22 @@ package grpc
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"github.com/micro/go-micro/codec/jsonrpc"
|
||||
"github.com/micro/go-micro/codec/protorpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type jsonCodec struct{}
|
||||
type bytesCodec struct{}
|
||||
type protoCodec struct{}
|
||||
type wrapCodec struct{ encoding.Codec }
|
||||
|
||||
var (
|
||||
defaultGRPCCodecs = map[string]encoding.Codec{
|
||||
@@ -36,6 +41,27 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (w wrapCodec) String() string {
|
||||
return w.Codec.Name()
|
||||
}
|
||||
|
||||
func (w wrapCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
b, ok := v.(*bytes.Frame)
|
||||
if ok {
|
||||
return b.Data, nil
|
||||
}
|
||||
return w.Codec.Marshal(v)
|
||||
}
|
||||
|
||||
func (w wrapCodec) Unmarshal(data []byte, v interface{}) error {
|
||||
b, ok := v.(*bytes.Frame)
|
||||
if ok {
|
||||
b.Data = data
|
||||
return nil
|
||||
}
|
||||
return w.Codec.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
return proto.Marshal(v.(proto.Message))
|
||||
}
|
||||
@@ -80,3 +106,61 @@ func (bytesCodec) Unmarshal(data []byte, v interface{}) error {
|
||||
func (bytesCodec) Name() string {
|
||||
return "bytes"
|
||||
}
|
||||
|
||||
type grpcCodec struct {
|
||||
// headers
|
||||
id string
|
||||
target string
|
||||
method string
|
||||
endpoint string
|
||||
|
||||
s grpc.ServerStream
|
||||
c encoding.Codec
|
||||
}
|
||||
|
||||
func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
|
||||
md, _ := metadata.FromIncomingContext(g.s.Context())
|
||||
if m == nil {
|
||||
m = new(codec.Message)
|
||||
}
|
||||
if m.Header == nil {
|
||||
m.Header = make(map[string]string)
|
||||
}
|
||||
for k, v := range md {
|
||||
m.Header[k] = strings.Join(v, ",")
|
||||
}
|
||||
m.Id = g.id
|
||||
m.Target = g.target
|
||||
m.Method = g.method
|
||||
m.Endpoint = g.endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *grpcCodec) ReadBody(v interface{}) error {
|
||||
// caller has requested a frame
|
||||
if f, ok := v.(*bytes.Frame); ok {
|
||||
return g.s.RecvMsg(f)
|
||||
}
|
||||
return g.s.RecvMsg(v)
|
||||
}
|
||||
|
||||
func (g *grpcCodec) Write(m *codec.Message, v interface{}) error {
|
||||
// if we don't have a body
|
||||
if v != nil {
|
||||
b, err := g.c.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Body = b
|
||||
}
|
||||
// write the body using the framing codec
|
||||
return g.s.SendMsg(&bytes.Frame{m.Body})
|
||||
}
|
||||
|
||||
func (g *grpcCodec) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *grpcCodec) String() string {
|
||||
return g.c.Name()
|
||||
}
|
||||
|
@@ -56,8 +56,9 @@ type grpcServer struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
encoding.RegisterCodec(jsonCodec{})
|
||||
encoding.RegisterCodec(bytesCodec{})
|
||||
encoding.RegisterCodec(wrapCodec{protoCodec{}})
|
||||
encoding.RegisterCodec(wrapCodec{jsonCodec{}})
|
||||
encoding.RegisterCodec(wrapCodec{bytesCodec{}})
|
||||
}
|
||||
|
||||
func newGRPCServer(opts ...server.Option) server.Server {
|
||||
@@ -81,6 +82,14 @@ func newGRPCServer(opts ...server.Option) server.Server {
|
||||
return srv
|
||||
}
|
||||
|
||||
type grpcRouter struct {
|
||||
h func(context.Context, server.Request, interface{}) error
|
||||
}
|
||||
|
||||
func (r grpcRouter) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
return r.h(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func (g *grpcServer) configure(opts ...server.Option) {
|
||||
// Don't reprocess where there's no config
|
||||
if len(opts) == 0 && g.srv != nil {
|
||||
@@ -167,19 +176,6 @@ func (g *grpcServer) handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return status.New(codes.InvalidArgument, err.Error()).Err()
|
||||
}
|
||||
|
||||
g.rpc.mu.Lock()
|
||||
service := g.rpc.serviceMap[serviceName]
|
||||
g.rpc.mu.Unlock()
|
||||
|
||||
if service == nil {
|
||||
return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err()
|
||||
}
|
||||
|
||||
mtype := service.method[methodName]
|
||||
if mtype == nil {
|
||||
return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err()
|
||||
}
|
||||
|
||||
// get grpc metadata
|
||||
gmd, ok := metadata.FromIncomingContext(stream.Context())
|
||||
if !ok {
|
||||
@@ -214,6 +210,67 @@ func (g *grpcServer) handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
}
|
||||
}
|
||||
|
||||
// process via router
|
||||
if g.opts.Router != nil {
|
||||
cc, err := g.newGRPCCodec(ct)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.server", err.Error())
|
||||
}
|
||||
codec := &grpcCodec{
|
||||
method: fmt.Sprintf("%s.%s", serviceName, methodName),
|
||||
endpoint: fmt.Sprintf("%s.%s", serviceName, methodName),
|
||||
target: g.opts.Name,
|
||||
s: stream,
|
||||
c: cc,
|
||||
}
|
||||
|
||||
// create a client.Request
|
||||
request := &rpcRequest{
|
||||
service: mgrpc.ServiceFromMethod(fullMethod),
|
||||
contentType: ct,
|
||||
method: fmt.Sprintf("%s.%s", serviceName, methodName),
|
||||
codec: codec,
|
||||
}
|
||||
|
||||
response := &rpcResponse{
|
||||
header: make(map[string]string),
|
||||
codec: codec,
|
||||
}
|
||||
|
||||
// create a wrapped function
|
||||
handler := func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
return g.opts.Router.ServeRequest(ctx, req, rsp.(server.Response))
|
||||
}
|
||||
|
||||
// execute the wrapper for it
|
||||
for i := len(g.opts.HdlrWrappers); i > 0; i-- {
|
||||
handler = g.opts.HdlrWrappers[i-1](handler)
|
||||
}
|
||||
|
||||
r := grpcRouter{handler}
|
||||
|
||||
// serve the actual request using the request router
|
||||
if err := r.ServeRequest(ctx, request, response); err != nil {
|
||||
return status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// process the standard request flow
|
||||
g.rpc.mu.Lock()
|
||||
service := g.rpc.serviceMap[serviceName]
|
||||
g.rpc.mu.Unlock()
|
||||
|
||||
if service == nil {
|
||||
return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err()
|
||||
}
|
||||
|
||||
mtype := service.method[methodName]
|
||||
if mtype == nil {
|
||||
return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err()
|
||||
}
|
||||
|
||||
// process unary
|
||||
if !mtype.stream {
|
||||
return g.processRequest(stream, service, mtype, ct, ctx)
|
||||
|
@@ -2,6 +2,7 @@ package grpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
)
|
||||
|
||||
type rpcRequest struct {
|
||||
@@ -46,7 +47,11 @@ func (r *rpcRequest) Header() map[string]string {
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Read() ([]byte, error) {
|
||||
return r.body, nil
|
||||
f := &bytes.Frame{}
|
||||
if err := r.codec.ReadBody(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Data, nil
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Stream() bool {
|
||||
|
27
server/grpc/response.go
Normal file
27
server/grpc/response.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/codec"
|
||||
)
|
||||
|
||||
type rpcResponse struct {
|
||||
header map[string]string
|
||||
codec codec.Codec
|
||||
}
|
||||
|
||||
func (r *rpcResponse) Codec() codec.Writer {
|
||||
return r.codec
|
||||
}
|
||||
|
||||
func (r *rpcResponse) WriteHeader(hdr map[string]string) {
|
||||
for k, v := range hdr {
|
||||
r.header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcResponse) Write(b []byte) error {
|
||||
return r.codec.Write(&codec.Message{
|
||||
Header: r.header,
|
||||
Body: b,
|
||||
}, nil)
|
||||
}
|
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
"github.com/micro/go-micro/codec"
|
||||
@@ -169,6 +170,10 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke
|
||||
return func(p broker.Publication) error {
|
||||
msg := p.Message()
|
||||
ct := msg.Header["Content-Type"]
|
||||
if len(ct) == 0 {
|
||||
msg.Header["Content-Type"] = defaultContentType
|
||||
ct = defaultContentType
|
||||
}
|
||||
cf, err := g.newCodec(ct)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -181,6 +186,8 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke
|
||||
delete(hdr, "Content-Type")
|
||||
ctx := metadata.NewContext(context.Background(), hdr)
|
||||
|
||||
results := make(chan error, len(sb.handlers))
|
||||
|
||||
for i := 0; i < len(sb.handlers); i++ {
|
||||
handler := sb.handlers[i]
|
||||
|
||||
@@ -231,16 +238,29 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke
|
||||
fn = opts.SubWrappers[i-1](fn)
|
||||
}
|
||||
|
||||
g.wg.Add(1)
|
||||
if g.wg != nil {
|
||||
g.wg.Add(1)
|
||||
}
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
fn(ctx, &rpcMessage{
|
||||
if g.wg != nil {
|
||||
defer g.wg.Done()
|
||||
}
|
||||
results <- fn(ctx, &rpcMessage{
|
||||
topic: sb.topic,
|
||||
contentType: ct,
|
||||
payload: req.Interface(),
|
||||
})
|
||||
}()
|
||||
}
|
||||
var errors []string
|
||||
for i := 0; i < len(sb.handlers); i++ {
|
||||
if err := <-results; err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Package rpc provides an rpc server
|
||||
package rpc
|
||||
// Package mucp provides an mucp server
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/server"
|
@@ -14,6 +14,7 @@ type rpcRequest struct {
|
||||
codec codec.Codec
|
||||
header map[string]string
|
||||
body []byte
|
||||
rawBody interface{}
|
||||
stream bool
|
||||
}
|
||||
|
||||
@@ -48,8 +49,7 @@ func (r *rpcRequest) Header() map[string]string {
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Body() interface{} {
|
||||
// TODO: convert to interface value
|
||||
return r.body
|
||||
return r.rawBody
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Read() ([]byte, error) {
|
||||
|
@@ -190,6 +190,11 @@ func (s *service) call(ctx context.Context, router *router, sending *sync.Mutex,
|
||||
body: req.msg.Body,
|
||||
}
|
||||
|
||||
// only set if not nil
|
||||
if argv.IsValid() {
|
||||
r.rawBody = argv.Interface()
|
||||
}
|
||||
|
||||
if !mtype.stream {
|
||||
fn := func(ctx context.Context, req Request, rsp interface{}) error {
|
||||
returnValues = function.Call([]reflect.Value{s.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)})
|
||||
@@ -202,6 +207,11 @@ func (s *service) call(ctx context.Context, router *router, sending *sync.Mutex,
|
||||
return nil
|
||||
}
|
||||
|
||||
// wrap the handler
|
||||
for i := len(router.hdlrWrappers); i > 0; i-- {
|
||||
fn = router.hdlrWrappers[i-1](fn)
|
||||
}
|
||||
|
||||
// execute handler
|
||||
if err := fn(ctx, r, replyv.Interface()); err != nil {
|
||||
return err
|
||||
@@ -235,6 +245,11 @@ func (s *service) call(ctx context.Context, router *router, sending *sync.Mutex,
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the handler
|
||||
for i := len(router.hdlrWrappers); i > 0; i-- {
|
||||
fn = router.hdlrWrappers[i-1](fn)
|
||||
}
|
||||
|
||||
// client.Stream request
|
||||
r.stream = true
|
||||
|
||||
|
@@ -35,9 +35,12 @@ type rpcServer struct {
|
||||
|
||||
func newRpcServer(opts ...Option) Server {
|
||||
options := newOptions(opts...)
|
||||
router := newRpcRouter()
|
||||
router.hdlrWrappers = options.HdlrWrappers
|
||||
|
||||
return &rpcServer{
|
||||
opts: options,
|
||||
router: DefaultRouter,
|
||||
router: router,
|
||||
handlers: make(map[string]Handler),
|
||||
subscribers: make(map[*subscriber][]broker.Subscriber),
|
||||
exit: make(chan chan error),
|
||||
@@ -45,6 +48,14 @@ func newRpcServer(opts ...Option) Server {
|
||||
}
|
||||
}
|
||||
|
||||
type rpcRouter struct {
|
||||
h func(context.Context, Request, interface{}) error
|
||||
}
|
||||
|
||||
func (r rpcRouter) ServeRequest(ctx context.Context, req Request, rsp Response) error {
|
||||
return r.h(ctx, req, rsp)
|
||||
}
|
||||
|
||||
// ServeConn serves a single connection
|
||||
func (s *rpcServer) ServeConn(sock transport.Socket) {
|
||||
defer func() {
|
||||
@@ -143,24 +154,26 @@ func (s *rpcServer) ServeConn(sock transport.Socket) {
|
||||
}
|
||||
|
||||
// set router
|
||||
r := s.opts.Router
|
||||
r := Router(s.router)
|
||||
|
||||
// if nil use default router
|
||||
if s.opts.Router == nil {
|
||||
r = s.router
|
||||
// if not nil use the router specified
|
||||
if s.opts.Router != nil {
|
||||
// create a wrapped function
|
||||
handler := func(ctx context.Context, req Request, rsp interface{}) error {
|
||||
return s.opts.Router.ServeRequest(ctx, req, rsp.(Response))
|
||||
}
|
||||
|
||||
// execute the wrapper for it
|
||||
for i := len(s.opts.HdlrWrappers); i > 0; i-- {
|
||||
handler = s.opts.HdlrWrappers[i-1](handler)
|
||||
}
|
||||
|
||||
// set the router
|
||||
r = rpcRouter{handler}
|
||||
}
|
||||
|
||||
// create a wrapped function
|
||||
handler := func(ctx context.Context, req Request, rsp interface{}) error {
|
||||
return r.ServeRequest(ctx, req, rsp.(Response))
|
||||
}
|
||||
|
||||
for i := len(s.opts.HdlrWrappers); i > 0; i-- {
|
||||
handler = s.opts.HdlrWrappers[i-1](handler)
|
||||
}
|
||||
|
||||
// TODO: handle error better
|
||||
if err := handler(ctx, request, response); err != nil {
|
||||
// serve the actual request using the request router
|
||||
if err := r.ServeRequest(ctx, request, response); err != nil {
|
||||
// write an error response
|
||||
err = rcodec.Write(&codec.Message{
|
||||
Header: msg.Header,
|
||||
@@ -206,6 +219,15 @@ func (s *rpcServer) Init(opts ...Option) error {
|
||||
for _, opt := range opts {
|
||||
opt(&s.opts)
|
||||
}
|
||||
|
||||
// update router if its the default
|
||||
if s.opts.Router == nil {
|
||||
r := newRpcRouter()
|
||||
r.hdlrWrappers = s.opts.HdlrWrappers
|
||||
r.serviceMap = s.router.serviceMap
|
||||
s.router = r
|
||||
}
|
||||
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -486,7 +508,7 @@ func (s *rpcServer) Start() error {
|
||||
} else {
|
||||
// announce self to the world
|
||||
if err = s.Register(); err != nil {
|
||||
log.Log("Server %s-%s register error: %s", config.Name, config.Id, err)
|
||||
log.Logf("Server %s-%s register error: %s", config.Name, config.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,5 +611,5 @@ func (s *rpcServer) Stop() error {
|
||||
}
|
||||
|
||||
func (s *rpcServer) String() string {
|
||||
return "rpc"
|
||||
return "mucp"
|
||||
}
|
||||
|
21
service/mucp/mucp.go
Normal file
21
service/mucp/mucp.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Package mucp initialises a mucp service
|
||||
package mucp
|
||||
|
||||
import (
|
||||
// TODO: change to go-micro/service
|
||||
"github.com/micro/go-micro"
|
||||
cmucp "github.com/micro/go-micro/client/mucp"
|
||||
smucp "github.com/micro/go-micro/server/mucp"
|
||||
)
|
||||
|
||||
// NewService returns a new mucp service
|
||||
func NewService(opts ...micro.Option) micro.Service {
|
||||
options := []micro.Option{
|
||||
micro.Client(cmucp.NewClient()),
|
||||
micro.Server(smucp.NewServer()),
|
||||
}
|
||||
|
||||
options = append(options, opts...)
|
||||
|
||||
return micro.NewService(opts...)
|
||||
}
|
2
service/service.go
Normal file
2
service/service.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package service encapsulates the client, server and other interfaces to provide a complete micro service.
|
||||
package service
|
@@ -30,7 +30,7 @@ func testService(ctx context.Context, wg *sync.WaitGroup, name string) Service {
|
||||
wg.Add(1)
|
||||
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
r.(*memory.Registry).Services = testData
|
||||
|
||||
// create service
|
||||
return NewService(
|
||||
|
@@ -10,7 +10,6 @@ an external database or eventing system. Go Sync provides a framework for synchr
|
||||
|
||||
## Getting Started
|
||||
|
||||
- [Data](#data) - simple distributed data storage
|
||||
- [Leader](#leader) - leadership election for group coordination
|
||||
- [Lock](#lock) - distributed locking for exclusive resource access
|
||||
- [Task](#task) - distributed job execution
|
||||
@@ -70,30 +69,6 @@ for {
|
||||
e.Resign()
|
||||
```
|
||||
|
||||
## Data
|
||||
|
||||
Data provides a simple interface for distributed data storage.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
"github.com/micro/go-micro/sync/data/consul"
|
||||
)
|
||||
|
||||
keyval := consul.NewData()
|
||||
|
||||
err := keyval.Write(&data.Record{
|
||||
Key: "foo",
|
||||
Value: []byte(`bar`),
|
||||
})
|
||||
// handle err
|
||||
|
||||
v, err := keyval.Read("foo")
|
||||
// handle err
|
||||
|
||||
err = keyval.Delete("foo")
|
||||
```
|
||||
|
||||
## Task
|
||||
|
||||
Task provides distributed job execution. It's a simple way to distribute work across a coordinated pool of workers.
|
||||
|
@@ -1,93 +0,0 @@
|
||||
// Package etcd is an etcd v3 implementation of kv
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
client "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
type ekv struct {
|
||||
kv client.KV
|
||||
}
|
||||
|
||||
func (e *ekv) Read(key string) (*data.Record, error) {
|
||||
keyval, err := e.kv.Get(context.Background(), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keyval == nil || len(keyval.Kvs) == 0 {
|
||||
return nil, data.ErrNotFound
|
||||
}
|
||||
|
||||
return &data.Record{
|
||||
Key: string(keyval.Kvs[0].Key),
|
||||
Value: keyval.Kvs[0].Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *ekv) Delete(key string) error {
|
||||
_, err := e.kv.Delete(context.Background(), key)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *ekv) Write(record *data.Record) error {
|
||||
_, err := e.kv.Put(context.Background(), record.Key, string(record.Value))
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *ekv) Dump() ([]*data.Record, error) {
|
||||
keyval, err := e.kv.Get(context.Background(), "/", client.WithPrefix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vals []*data.Record
|
||||
if keyval == nil || len(keyval.Kvs) == 0 {
|
||||
return vals, nil
|
||||
}
|
||||
for _, keyv := range keyval.Kvs {
|
||||
vals = append(vals, &data.Record{
|
||||
Key: string(keyv.Key),
|
||||
Value: keyv.Value,
|
||||
})
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func (e *ekv) String() string {
|
||||
return "etcd"
|
||||
}
|
||||
|
||||
func NewData(opts ...data.Option) data.Data {
|
||||
var options data.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
var endpoints []string
|
||||
|
||||
for _, addr := range options.Nodes {
|
||||
if len(addr) > 0 {
|
||||
endpoints = append(endpoints, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) == 0 {
|
||||
endpoints = []string{"http://127.0.0.1:2379"}
|
||||
}
|
||||
|
||||
// TODO: parse addresses
|
||||
c, err := client.New(client.Config{
|
||||
Endpoints: endpoints,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return &ekv{
|
||||
kv: client.NewKV(c),
|
||||
}
|
||||
}
|
@@ -1,178 +0,0 @@
|
||||
package memcached
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mc "github.com/bradfitz/gomemcache/memcache"
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
)
|
||||
|
||||
type mkv struct {
|
||||
Server *mc.ServerList
|
||||
Client *mc.Client
|
||||
}
|
||||
|
||||
func (m *mkv) Read(key string) (*data.Record, error) {
|
||||
keyval, err := m.Client.Get(key)
|
||||
if err != nil && err == mc.ErrCacheMiss {
|
||||
return nil, data.ErrNotFound
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keyval == nil {
|
||||
return nil, data.ErrNotFound
|
||||
}
|
||||
|
||||
return &data.Record{
|
||||
Key: keyval.Key,
|
||||
Value: keyval.Value,
|
||||
Expiration: time.Second * time.Duration(keyval.Expiration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mkv) Delete(key string) error {
|
||||
return m.Client.Delete(key)
|
||||
}
|
||||
|
||||
func (m *mkv) Write(record *data.Record) error {
|
||||
return m.Client.Set(&mc.Item{
|
||||
Key: record.Key,
|
||||
Value: record.Value,
|
||||
Expiration: int32(record.Expiration.Seconds()),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *mkv) Dump() ([]*data.Record, error) {
|
||||
// stats
|
||||
// cachedump
|
||||
// get keys
|
||||
|
||||
var keys []string
|
||||
|
||||
//data := make(map[string]string)
|
||||
if err := m.Server.Each(func(c net.Addr) error {
|
||||
cc, err := net.Dial("tcp", c.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
b := bufio.NewReadWriter(bufio.NewReader(cc), bufio.NewWriter(cc))
|
||||
|
||||
// get records
|
||||
if _, err := fmt.Fprintf(b, "stats records\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Flush()
|
||||
|
||||
v, err := b.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts := bytes.Split(v, []byte("\n"))
|
||||
if len(parts) < 1 {
|
||||
return nil
|
||||
}
|
||||
vals := strings.Split(string(parts[0]), ":")
|
||||
records := vals[1]
|
||||
|
||||
// drain
|
||||
for {
|
||||
buf, err := b.ReadSlice('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(string(buf), "END") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.Writer.Reset(cc)
|
||||
b.Reader.Reset(cc)
|
||||
|
||||
if _, err := fmt.Fprintf(b, "lru_crawler metadump %s\r\n", records); err != nil {
|
||||
return err
|
||||
}
|
||||
b.Flush()
|
||||
|
||||
for {
|
||||
v, err := b.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(v, "END") {
|
||||
break
|
||||
}
|
||||
key := strings.Split(v, " ")[0]
|
||||
keys = append(keys, strings.TrimPrefix(key, "key="))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vals []*data.Record
|
||||
|
||||
// concurrent op
|
||||
ch := make(chan *data.Record, len(keys))
|
||||
|
||||
for _, k := range keys {
|
||||
go func(key string) {
|
||||
i, _ := m.Read(key)
|
||||
ch <- i
|
||||
}(k)
|
||||
}
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
record := <-ch
|
||||
|
||||
if record == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
vals = append(vals, record)
|
||||
}
|
||||
|
||||
close(ch)
|
||||
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func (m *mkv) String() string {
|
||||
return "memcached"
|
||||
}
|
||||
|
||||
func NewData(opts ...data.Option) data.Data {
|
||||
var options data.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if len(options.Nodes) == 0 {
|
||||
options.Nodes = []string{"127.0.0.1:11211"}
|
||||
}
|
||||
|
||||
ss := new(mc.ServerList)
|
||||
ss.SetServers(options.Nodes...)
|
||||
|
||||
return &mkv{
|
||||
Server: ss,
|
||||
Client: mc.New(options.Nodes...),
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
package data
|
||||
|
||||
type Options struct {
|
||||
Nodes []string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func Nodes(a ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.Nodes = a
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix sets a prefix to any lock ids used
|
||||
func Prefix(p string) Option {
|
||||
return func(o *Options) {
|
||||
o.Prefix = p
|
||||
}
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
redis "gopkg.in/redis.v3"
|
||||
)
|
||||
|
||||
type rkv struct {
|
||||
Client *redis.Client
|
||||
}
|
||||
|
||||
func (r *rkv) Read(key string) (*data.Record, error) {
|
||||
val, err := r.Client.Get(key).Bytes()
|
||||
|
||||
if err != nil && err == redis.Nil {
|
||||
return nil, data.ErrNotFound
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
return nil, data.ErrNotFound
|
||||
}
|
||||
|
||||
d, err := r.Client.TTL(key).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data.Record{
|
||||
Key: key,
|
||||
Value: val,
|
||||
Expiration: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *rkv) Delete(key string) error {
|
||||
return r.Client.Del(key).Err()
|
||||
}
|
||||
|
||||
func (r *rkv) Write(record *data.Record) error {
|
||||
return r.Client.Set(record.Key, record.Value, record.Expiration).Err()
|
||||
}
|
||||
|
||||
func (r *rkv) Dump() ([]*data.Record, error) {
|
||||
keys, err := r.Client.Keys("*").Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vals []*data.Record
|
||||
for _, k := range keys {
|
||||
i, err := r.Read(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals = append(vals, i)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func (r *rkv) String() string {
|
||||
return "redis"
|
||||
}
|
||||
|
||||
func NewData(opts ...data.Option) data.Data {
|
||||
var options data.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if len(options.Nodes) == 0 {
|
||||
options.Nodes = []string{"127.0.0.1:6379"}
|
||||
}
|
||||
|
||||
return &rkv{
|
||||
Client: redis.NewClient(&redis.Options{
|
||||
Addr: options.Nodes[0],
|
||||
Password: "", // no password set
|
||||
DB: 0, // use default DB
|
||||
}),
|
||||
}
|
||||
}
|
@@ -1,145 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
client "github.com/coreos/etcd/clientv3"
|
||||
cc "github.com/coreos/etcd/clientv3/concurrency"
|
||||
"github.com/micro/go-micro/sync/leader"
|
||||
)
|
||||
|
||||
type etcdLeader struct {
|
||||
opts leader.Options
|
||||
path string
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
type etcdElected struct {
|
||||
s *cc.Session
|
||||
e *cc.Election
|
||||
id string
|
||||
}
|
||||
|
||||
func (e *etcdLeader) Elect(id string, opts ...leader.ElectOption) (leader.Elected, error) {
|
||||
var options leader.ElectOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// make path
|
||||
path := path.Join(e.path, strings.Replace(id, "/", "-", -1))
|
||||
|
||||
s, err := cc.NewSession(e.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := cc.NewElection(s, path)
|
||||
|
||||
ctx, _ := context.WithCancel(context.Background())
|
||||
|
||||
if err := l.Campaign(ctx, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &etcdElected{
|
||||
e: l,
|
||||
id: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *etcdLeader) Follow() chan string {
|
||||
ch := make(chan string)
|
||||
|
||||
s, err := cc.NewSession(e.client)
|
||||
if err != nil {
|
||||
return ch
|
||||
}
|
||||
|
||||
l := cc.NewElection(s, e.path)
|
||||
ech := l.Observe(context.Background())
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case r, ok := <-ech:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- string(r.Kvs[0].Value)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (e *etcdLeader) String() string {
|
||||
return "etcd"
|
||||
}
|
||||
|
||||
func (e *etcdElected) Reelect() error {
|
||||
ctx, _ := context.WithCancel(context.Background())
|
||||
return e.e.Campaign(ctx, e.id)
|
||||
}
|
||||
|
||||
func (e *etcdElected) Revoked() chan bool {
|
||||
ch := make(chan bool, 1)
|
||||
ech := e.e.Observe(context.Background())
|
||||
|
||||
go func() {
|
||||
for r := range ech {
|
||||
if string(r.Kvs[0].Value) != e.id {
|
||||
ch <- true
|
||||
close(ch)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (e *etcdElected) Resign() error {
|
||||
return e.e.Resign(context.Background())
|
||||
}
|
||||
|
||||
func (e *etcdElected) Id() string {
|
||||
return e.id
|
||||
}
|
||||
|
||||
func NewLeader(opts ...leader.Option) leader.Leader {
|
||||
var options leader.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
var endpoints []string
|
||||
|
||||
for _, addr := range options.Nodes {
|
||||
if len(addr) > 0 {
|
||||
endpoints = append(endpoints, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) == 0 {
|
||||
endpoints = []string{"http://127.0.0.1:2379"}
|
||||
}
|
||||
|
||||
// TODO: parse addresses
|
||||
c, err := client.New(client.Config{
|
||||
Endpoints: endpoints,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return &etcdLeader{
|
||||
path: "/micro/leader",
|
||||
client: c,
|
||||
opts: options,
|
||||
}
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
// Package etcd is an etcd implementation of lock
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
client "github.com/coreos/etcd/clientv3"
|
||||
cc "github.com/coreos/etcd/clientv3/concurrency"
|
||||
"github.com/micro/go-micro/sync/lock"
|
||||
)
|
||||
|
||||
type etcdLock struct {
|
||||
opts lock.Options
|
||||
path string
|
||||
client *client.Client
|
||||
|
||||
sync.Mutex
|
||||
locks map[string]*elock
|
||||
}
|
||||
|
||||
type elock struct {
|
||||
s *cc.Session
|
||||
m *cc.Mutex
|
||||
}
|
||||
|
||||
func (e *etcdLock) Acquire(id string, opts ...lock.AcquireOption) error {
|
||||
var options lock.AcquireOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// make path
|
||||
path := path.Join(e.path, strings.Replace(e.opts.Prefix+id, "/", "-", -1))
|
||||
|
||||
var sopts []cc.SessionOption
|
||||
if options.TTL > 0 {
|
||||
sopts = append(sopts, cc.WithTTL(int(options.TTL.Seconds())))
|
||||
}
|
||||
|
||||
s, err := cc.NewSession(e.client, sopts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := cc.NewMutex(s, path)
|
||||
|
||||
ctx, _ := context.WithCancel(context.Background())
|
||||
|
||||
if err := m.Lock(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Lock()
|
||||
e.locks[id] = &elock{
|
||||
s: s,
|
||||
m: m,
|
||||
}
|
||||
e.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *etcdLock) Release(id string) error {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
v, ok := e.locks[id]
|
||||
if !ok {
|
||||
return errors.New("lock not found")
|
||||
}
|
||||
err := v.m.Unlock(context.Background())
|
||||
delete(e.locks, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *etcdLock) String() string {
|
||||
return "etcd"
|
||||
}
|
||||
|
||||
func NewLock(opts ...lock.Option) lock.Lock {
|
||||
var options lock.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
var endpoints []string
|
||||
|
||||
for _, addr := range options.Nodes {
|
||||
if len(addr) > 0 {
|
||||
endpoints = append(endpoints, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) == 0 {
|
||||
endpoints = []string{"http://127.0.0.1:2379"}
|
||||
}
|
||||
|
||||
// TODO: parse addresses
|
||||
c, err := client.New(client.Config{
|
||||
Endpoints: endpoints,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return &etcdLock{
|
||||
path: "/micro/lock",
|
||||
client: c,
|
||||
opts: options,
|
||||
locks: make(map[string]*elock),
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
type pool struct {
|
||||
sync.Mutex
|
||||
i int
|
||||
addrs []string
|
||||
}
|
||||
|
||||
func (p *pool) Get() redis.Conn {
|
||||
for i := 0; i < 3; i++ {
|
||||
p.Lock()
|
||||
addr := p.addrs[p.i%len(p.addrs)]
|
||||
p.i++
|
||||
p.Unlock()
|
||||
|
||||
c, err := redis.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -1,94 +0,0 @@
|
||||
// Package redis is a redis implemenation of lock
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redsync/redsync"
|
||||
"github.com/micro/go-micro/sync/lock"
|
||||
)
|
||||
|
||||
type redisLock struct {
|
||||
sync.Mutex
|
||||
|
||||
locks map[string]*redsync.Mutex
|
||||
opts lock.Options
|
||||
c *redsync.Redsync
|
||||
}
|
||||
|
||||
func (r *redisLock) Acquire(id string, opts ...lock.AcquireOption) error {
|
||||
var options lock.AcquireOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
var ropts []redsync.Option
|
||||
|
||||
if options.Wait > time.Duration(0) {
|
||||
ropts = append(ropts, redsync.SetRetryDelay(options.Wait))
|
||||
ropts = append(ropts, redsync.SetTries(1))
|
||||
}
|
||||
|
||||
if options.TTL > time.Duration(0) {
|
||||
ropts = append(ropts, redsync.SetExpiry(options.TTL))
|
||||
}
|
||||
|
||||
m := r.c.NewMutex(r.opts.Prefix+id, ropts...)
|
||||
err := m.Lock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
r.locks[id] = m
|
||||
r.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *redisLock) Release(id string) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
m, ok := r.locks[id]
|
||||
if !ok {
|
||||
return errors.New("lock not found")
|
||||
}
|
||||
|
||||
unlocked := m.Unlock()
|
||||
delete(r.locks, id)
|
||||
|
||||
if !unlocked {
|
||||
return errors.New("lock not unlocked")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *redisLock) String() string {
|
||||
return "redis"
|
||||
}
|
||||
|
||||
func NewLock(opts ...lock.Option) lock.Lock {
|
||||
var options lock.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
nodes := options.Nodes
|
||||
|
||||
if len(nodes) == 0 {
|
||||
nodes = []string{"127.0.0.1:6379"}
|
||||
}
|
||||
|
||||
rpool := redsync.New([]redsync.Pool{&pool{
|
||||
addrs: nodes,
|
||||
}})
|
||||
|
||||
return &redisLock{
|
||||
locks: make(map[string]*redsync.Mutex),
|
||||
opts: options,
|
||||
c: rpool,
|
||||
}
|
||||
}
|
@@ -6,12 +6,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
ckv "github.com/micro/go-micro/sync/data/consul"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
ckv "github.com/micro/go-micro/data/store/consul"
|
||||
lock "github.com/micro/go-micro/sync/lock/consul"
|
||||
)
|
||||
|
||||
type syncDB struct {
|
||||
type syncMap struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func ekey(k interface{}) string {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func (m *syncDB) Read(key, val interface{}) error {
|
||||
func (m *syncMap) Read(key, val interface{}) error {
|
||||
if key == nil {
|
||||
return fmt.Errorf("key is nil")
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func (m *syncDB) Read(key, val interface{}) error {
|
||||
defer m.opts.Lock.Release(kstr)
|
||||
|
||||
// get key
|
||||
kval, err := m.opts.Data.Read(kstr)
|
||||
kval, err := m.opts.Store.Read(kstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func (m *syncDB) Read(key, val interface{}) error {
|
||||
return json.Unmarshal(kval.Value, val)
|
||||
}
|
||||
|
||||
func (m *syncDB) Write(key, val interface{}) error {
|
||||
func (m *syncMap) Write(key, val interface{}) error {
|
||||
if key == nil {
|
||||
return fmt.Errorf("key is nil")
|
||||
}
|
||||
@@ -63,13 +63,13 @@ func (m *syncDB) Write(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// set key
|
||||
return m.opts.Data.Write(&data.Record{
|
||||
return m.opts.Store.Write(&store.Record{
|
||||
Key: kstr,
|
||||
Value: b,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *syncDB) Delete(key interface{}) error {
|
||||
func (m *syncMap) Delete(key interface{}) error {
|
||||
if key == nil {
|
||||
return fmt.Errorf("key is nil")
|
||||
}
|
||||
@@ -81,11 +81,11 @@ func (m *syncDB) Delete(key interface{}) error {
|
||||
return err
|
||||
}
|
||||
defer m.opts.Lock.Release(kstr)
|
||||
return m.opts.Data.Delete(kstr)
|
||||
return m.opts.Store.Delete(kstr)
|
||||
}
|
||||
|
||||
func (m *syncDB) Iterate(fn func(key, val interface{}) error) error {
|
||||
keyvals, err := m.opts.Data.Dump()
|
||||
func (m *syncMap) Iterate(fn func(key, val interface{}) error) error {
|
||||
keyvals, err := m.opts.Store.Dump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func (m *syncDB) Iterate(fn func(key, val interface{}) error) error {
|
||||
}
|
||||
|
||||
// set key
|
||||
if err := m.opts.Data.Write(&data.Record{
|
||||
if err := m.opts.Store.Write(&store.Record{
|
||||
Key: keyval.Key,
|
||||
Value: b,
|
||||
}); err != nil {
|
||||
@@ -137,7 +137,7 @@ func (m *syncDB) Iterate(fn func(key, val interface{}) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDB(opts ...Option) DB {
|
||||
func NewMap(opts ...Option) Map {
|
||||
var options Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@@ -147,11 +147,11 @@ func NewDB(opts ...Option) DB {
|
||||
options.Lock = lock.NewLock()
|
||||
}
|
||||
|
||||
if options.Data == nil {
|
||||
options.Data = ckv.NewData()
|
||||
if options.Store == nil {
|
||||
options.Store = ckv.NewStore()
|
||||
}
|
||||
|
||||
return &syncDB{
|
||||
return &syncMap{
|
||||
opts: options,
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
"github.com/micro/go-micro/sync/leader"
|
||||
"github.com/micro/go-micro/sync/lock"
|
||||
"github.com/micro/go-micro/sync/time"
|
||||
@@ -21,10 +21,10 @@ func WithLock(l lock.Lock) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithData sets the data implementation option
|
||||
func WithData(s data.Data) Option {
|
||||
// WithStore sets the store implementation option
|
||||
func WithStore(s store.Store) Option {
|
||||
return func(o *Options) {
|
||||
o.Data = s
|
||||
o.Store = s
|
||||
}
|
||||
}
|
||||
|
||||
|
10
sync/sync.go
10
sync/sync.go
@@ -2,17 +2,17 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
"github.com/micro/go-micro/sync/leader"
|
||||
"github.com/micro/go-micro/sync/lock"
|
||||
"github.com/micro/go-micro/sync/task"
|
||||
"github.com/micro/go-micro/sync/time"
|
||||
)
|
||||
|
||||
// DB provides synchronized access to key-value storage.
|
||||
// It uses the data interface and lock interface to
|
||||
// Map provides synchronized access to key-value storage.
|
||||
// It uses the store interface and lock interface to
|
||||
// provide a consistent storage mechanism.
|
||||
type DB interface {
|
||||
type Map interface {
|
||||
// Read value with given key
|
||||
Read(key, val interface{}) error
|
||||
// Write value with given key
|
||||
@@ -33,7 +33,7 @@ type Cron interface {
|
||||
type Options struct {
|
||||
Leader leader.Leader
|
||||
Lock lock.Lock
|
||||
Data data.Data
|
||||
Store store.Store
|
||||
Task task.Task
|
||||
Time time.Time
|
||||
}
|
||||
|
@@ -38,3 +38,20 @@ func ServiceMethod(m string) (string, string, error) {
|
||||
|
||||
return parts[0], parts[1], nil
|
||||
}
|
||||
|
||||
// ServiceFromMethod returns the service
|
||||
// /service.Foo/Bar => service
|
||||
func ServiceFromMethod(m string) string {
|
||||
if len(m) == 0 {
|
||||
return m
|
||||
}
|
||||
if m[0] != '/' {
|
||||
return m
|
||||
}
|
||||
parts := strings.Split(m, "/")
|
||||
if len(parts) < 3 {
|
||||
return m
|
||||
}
|
||||
parts = strings.Split(parts[1], ".")
|
||||
return strings.Join(parts[:len(parts)-1], ".")
|
||||
}
|
||||
|
Reference in New Issue
Block a user