v3 refactor (#1868)
* Move to v3 Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/util/backoff"
|
||||
"github.com/micro/go-micro/v3/util/backoff"
|
||||
)
|
||||
|
||||
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)
|
||||
|
@@ -16,10 +16,13 @@ func TestBackoff(t *testing.T) {
|
||||
7900 * time.Millisecond,
|
||||
}
|
||||
|
||||
c := NewClient()
|
||||
r := &testRequest{
|
||||
service: "test",
|
||||
method: "test",
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
d, err := exponentialBackoff(context.TODO(), c.NewRequest("test", "test", nil), i)
|
||||
d, err := exponentialBackoff(context.TODO(), r, i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"hash/fnv"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
cache "github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
@@ -24,12 +24,12 @@ type Cache struct {
|
||||
}
|
||||
|
||||
// Get a response from the cache
|
||||
func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) {
|
||||
func (c *Cache) Get(ctx context.Context, req Request) (interface{}, bool) {
|
||||
return c.cache.Get(key(ctx, req))
|
||||
}
|
||||
|
||||
// Set a response in the cache
|
||||
func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) {
|
||||
func (c *Cache) Set(ctx context.Context, req Request, rsp interface{}, expiry time.Duration) {
|
||||
c.cache.Set(key(ctx, req), rsp, expiry)
|
||||
}
|
||||
|
||||
@@ -47,16 +47,16 @@ func (c *Cache) List() map[string]string {
|
||||
}
|
||||
|
||||
// key returns a hash for the context and request
|
||||
func key(ctx context.Context, req *Request) string {
|
||||
func key(ctx context.Context, req Request) string {
|
||||
ns, _ := metadata.Get(ctx, "Micro-Namespace")
|
||||
|
||||
bytes, _ := json.Marshal(map[string]interface{}{
|
||||
"namespace": ns,
|
||||
"request": map[string]interface{}{
|
||||
"service": (*req).Service(),
|
||||
"endpoint": (*req).Endpoint(),
|
||||
"method": (*req).Method(),
|
||||
"body": (*req).Body(),
|
||||
"service": req.Service(),
|
||||
"endpoint": req.Endpoint(),
|
||||
"method": req.Method(),
|
||||
"body": req.Body(),
|
||||
},
|
||||
})
|
||||
|
||||
|
@@ -5,15 +5,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
req := NewRequest("go.micro.service.foo", "Foo.Bar", nil)
|
||||
req := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar"}
|
||||
|
||||
t.Run("CacheMiss", func(t *testing.T) {
|
||||
if _, ok := NewCache().Get(ctx, &req); ok {
|
||||
if _, ok := NewCache().Get(ctx, req); ok {
|
||||
t.Errorf("Expected to get no result from Get")
|
||||
}
|
||||
})
|
||||
@@ -22,9 +22,9 @@ func TestCache(t *testing.T) {
|
||||
c := NewCache()
|
||||
|
||||
rsp := "theresponse"
|
||||
c.Set(ctx, &req, rsp, time.Minute)
|
||||
c.Set(ctx, req, rsp, time.Minute)
|
||||
|
||||
if res, ok := c.Get(ctx, &req); !ok {
|
||||
if res, ok := c.Get(ctx, req); !ok {
|
||||
t.Errorf("Expected a result, got nothing")
|
||||
} else if res != rsp {
|
||||
t.Errorf("Expected '%v' result, got '%v'", rsp, res)
|
||||
@@ -34,21 +34,22 @@ func TestCache(t *testing.T) {
|
||||
|
||||
func TestCacheKey(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
req1 := NewRequest("go.micro.service.foo", "Foo.Bar", nil)
|
||||
req2 := NewRequest("go.micro.service.foo", "Foo.Baz", nil)
|
||||
req3 := NewRequest("go.micro.service.foo", "Foo.Baz", "customquery")
|
||||
|
||||
req1 := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar"}
|
||||
req2 := &testRequest{service: "go.micro.service.foo", method: "Foo.Baz"}
|
||||
req3 := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar", body: "customquery"}
|
||||
|
||||
t.Run("IdenticalRequests", func(t *testing.T) {
|
||||
key1 := key(ctx, &req1)
|
||||
key2 := key(ctx, &req1)
|
||||
key1 := key(ctx, req1)
|
||||
key2 := key(ctx, req1)
|
||||
if key1 != key2 {
|
||||
t.Errorf("Expected the keys to match for identical requests and context")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DifferentRequestEndpoints", func(t *testing.T) {
|
||||
key1 := key(ctx, &req1)
|
||||
key2 := key(ctx, &req2)
|
||||
key1 := key(ctx, req1)
|
||||
key2 := key(ctx, req2)
|
||||
|
||||
if key1 == key2 {
|
||||
t.Errorf("Expected the keys to differ for different request endpoints")
|
||||
@@ -56,8 +57,8 @@ func TestCacheKey(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("DifferentRequestBody", func(t *testing.T) {
|
||||
key1 := key(ctx, &req2)
|
||||
key2 := key(ctx, &req3)
|
||||
key1 := key(ctx, req2)
|
||||
key2 := key(ctx, req3)
|
||||
|
||||
if key1 == key2 {
|
||||
t.Errorf("Expected the keys to differ for different request bodies")
|
||||
@@ -66,8 +67,8 @@ func TestCacheKey(t *testing.T) {
|
||||
|
||||
t.Run("DifferentMetadata", func(t *testing.T) {
|
||||
mdCtx := metadata.Set(context.TODO(), "Micro-Namespace", "bar")
|
||||
key1 := key(mdCtx, &req1)
|
||||
key2 := key(ctx, &req1)
|
||||
key1 := key(mdCtx, req1)
|
||||
key2 := key(ctx, req1)
|
||||
|
||||
if key1 == key2 {
|
||||
t.Errorf("Expected the keys to differ for different metadata")
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
// Client is the interface used to make requests to services.
|
||||
@@ -91,8 +91,6 @@ type MessageOption func(*MessageOptions)
|
||||
type RequestOption func(*RequestOptions)
|
||||
|
||||
var (
|
||||
// DefaultClient is a default client to use out of the box
|
||||
DefaultClient Client = newRpcClient()
|
||||
// DefaultBackoff is the default backoff function for retries
|
||||
DefaultBackoff = exponentialBackoff
|
||||
// DefaultRetry is the default check-for-retry function for retries
|
||||
@@ -105,39 +103,4 @@ var (
|
||||
DefaultPoolSize = 100
|
||||
// DefaultPoolTTL sets the connection pool ttl
|
||||
DefaultPoolTTL = time.Minute
|
||||
|
||||
// NewClient returns a new client
|
||||
NewClient func(...Option) Client = newRpcClient
|
||||
)
|
||||
|
||||
// Makes a synchronous call to a service using the default client
|
||||
func Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
|
||||
return DefaultClient.Call(ctx, request, response, opts...)
|
||||
}
|
||||
|
||||
// Publishes a publication using the default client. Using the underlying broker
|
||||
// set within the options.
|
||||
func Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
|
||||
return DefaultClient.Publish(ctx, msg, opts...)
|
||||
}
|
||||
|
||||
// Creates a new message using the default client
|
||||
func NewMessage(topic string, payload interface{}, opts ...MessageOption) Message {
|
||||
return DefaultClient.NewMessage(topic, payload, opts...)
|
||||
}
|
||||
|
||||
// Creates a new request using the default client. Content Type will
|
||||
// be set to the default within options and use the appropriate codec
|
||||
func NewRequest(service, endpoint string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return DefaultClient.NewRequest(service, endpoint, request, reqOpts...)
|
||||
}
|
||||
|
||||
// Creates a streaming connection with a service and returns responses on the
|
||||
// channel passed in. It's up to the user to close the streamer.
|
||||
func NewStream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
|
||||
return DefaultClient.Stream(ctx, request, opts...)
|
||||
}
|
||||
|
||||
func String() string {
|
||||
return DefaultClient.String()
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v2/codec/bytes"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/codec/bytes"
|
||||
"github.com/oxtoacart/bpool"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
|
@@ -11,12 +11,12 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/broker"
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
raw "github.com/micro/go-micro/v2/codec/bytes"
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/broker"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
raw "github.com/micro/go-micro/v3/codec/bytes"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
@@ -5,11 +5,12 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v2/registry/memory"
|
||||
"github.com/micro/go-micro/v2/router"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/registry/memory"
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
regRouter "github.com/micro/go-micro/v3/router/registry"
|
||||
pgrpc "google.golang.org/grpc"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
@@ -57,7 +58,7 @@ func TestGRPCClient(t *testing.T) {
|
||||
})
|
||||
|
||||
// create router
|
||||
rtr := router.NewRouter(router.Registry(r))
|
||||
rtr := regRouter.NewRouter(router.Registry(r))
|
||||
|
||||
// create client
|
||||
c := NewClient(client.Router(rtr))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
)
|
||||
|
||||
type grpcEvent struct {
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
)
|
||||
|
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
type grpcRequest struct {
|
||||
|
@@ -3,8 +3,8 @@ package grpc
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v2/codec/bytes"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/codec/bytes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
var (
|
@@ -2,10 +2,623 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/broker"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
raw "github.com/micro/go-micro/v3/codec/bytes"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/util/buf"
|
||||
"github.com/micro/go-micro/v3/util/net"
|
||||
"github.com/micro/go-micro/v3/util/pool"
|
||||
)
|
||||
|
||||
// NewClient returns a new micro client interface
|
||||
func NewClient(opts ...client.Option) client.Client {
|
||||
return client.NewClient(opts...)
|
||||
return newClient(opts...)
|
||||
}
|
||||
|
||||
type rpcClient struct {
|
||||
once atomic.Value
|
||||
opts client.Options
|
||||
pool pool.Pool
|
||||
seq uint64
|
||||
}
|
||||
|
||||
func newClient(opt ...client.Option) client.Client {
|
||||
opts := client.NewOptions(opt...)
|
||||
|
||||
p := pool.NewPool(
|
||||
pool.Size(opts.PoolSize),
|
||||
pool.TTL(opts.PoolTTL),
|
||||
pool.Transport(opts.Transport),
|
||||
)
|
||||
|
||||
rc := &rpcClient{
|
||||
opts: opts,
|
||||
pool: p,
|
||||
seq: 0,
|
||||
}
|
||||
rc.once.Store(false)
|
||||
|
||||
c := client.Client(rc)
|
||||
|
||||
// wrap in reverse
|
||||
for i := len(opts.Wrappers); i > 0; i-- {
|
||||
c = opts.Wrappers[i-1](c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
|
||||
if c, ok := r.opts.Codecs[contentType]; ok {
|
||||
return c, nil
|
||||
}
|
||||
if cf, ok := DefaultCodecs[contentType]; ok {
|
||||
return cf, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
|
||||
}
|
||||
|
||||
func (r *rpcClient) call(ctx context.Context, node *registry.Node, req client.Request, resp interface{}, opts client.CallOptions) error {
|
||||
msg := &transport.Message{
|
||||
Header: make(map[string]string),
|
||||
}
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if ok {
|
||||
for k, v := range md {
|
||||
// don't copy Micro-Topic header, that used for pub/sub
|
||||
// this fix case then client uses the same context that received in subscriber
|
||||
if k == "Micro-Topic" {
|
||||
continue
|
||||
}
|
||||
msg.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
|
||||
// set the content type for the request
|
||||
msg.Header["Content-Type"] = req.ContentType()
|
||||
// set the accept header
|
||||
msg.Header["Accept"] = req.ContentType()
|
||||
|
||||
// setup old protocol
|
||||
cf := setupProtocol(msg, node)
|
||||
|
||||
// no codec specified
|
||||
if cf == nil {
|
||||
var err error
|
||||
cf, err = r.newCodec(req.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
dOpts := []transport.DialOption{
|
||||
transport.WithStream(),
|
||||
}
|
||||
|
||||
if opts.DialTimeout >= 0 {
|
||||
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
|
||||
}
|
||||
|
||||
c, err := r.pool.Get(node.Address, dOpts...)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
|
||||
}
|
||||
|
||||
seq := atomic.AddUint64(&r.seq, 1) - 1
|
||||
codec := newRpcCodec(msg, c, cf, "")
|
||||
|
||||
rsp := &rpcResponse{
|
||||
socket: c,
|
||||
codec: codec,
|
||||
}
|
||||
|
||||
stream := &rpcStream{
|
||||
id: fmt.Sprintf("%v", seq),
|
||||
context: ctx,
|
||||
request: req,
|
||||
response: rsp,
|
||||
codec: codec,
|
||||
closed: make(chan bool),
|
||||
release: func(err error) { r.pool.Release(c, err) },
|
||||
sendEOS: false,
|
||||
}
|
||||
// close the stream on exiting this function
|
||||
defer stream.Close()
|
||||
|
||||
// wait for error response
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// send request
|
||||
if err := stream.Send(req.Body()); err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// recv request
|
||||
if err := stream.Recv(resp); err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// success
|
||||
ch <- nil
|
||||
}()
|
||||
|
||||
var grr error
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
}
|
||||
|
||||
// set the stream error
|
||||
if grr != nil {
|
||||
stream.Lock()
|
||||
stream.err = grr
|
||||
stream.Unlock()
|
||||
|
||||
return grr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, opts client.CallOptions) (client.Stream, error) {
|
||||
msg := &transport.Message{
|
||||
Header: make(map[string]string),
|
||||
}
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if ok {
|
||||
for k, v := range md {
|
||||
msg.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
if opts.StreamTimeout > time.Duration(0) {
|
||||
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
|
||||
}
|
||||
// set the content type for the request
|
||||
msg.Header["Content-Type"] = req.ContentType()
|
||||
// set the accept header
|
||||
msg.Header["Accept"] = req.ContentType()
|
||||
|
||||
// set old codecs
|
||||
cf := setupProtocol(msg, node)
|
||||
|
||||
// no codec specified
|
||||
if cf == nil {
|
||||
var err error
|
||||
cf, err = r.newCodec(req.ContentType())
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
dOpts := []transport.DialOption{
|
||||
transport.WithStream(),
|
||||
}
|
||||
|
||||
if opts.DialTimeout >= 0 {
|
||||
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
|
||||
}
|
||||
|
||||
c, err := r.opts.Transport.Dial(node.Address, dOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
|
||||
}
|
||||
|
||||
// increment the sequence number
|
||||
seq := atomic.AddUint64(&r.seq, 1) - 1
|
||||
id := fmt.Sprintf("%v", seq)
|
||||
|
||||
// create codec with stream id
|
||||
codec := newRpcCodec(msg, c, cf, id)
|
||||
|
||||
rsp := &rpcResponse{
|
||||
socket: c,
|
||||
codec: codec,
|
||||
}
|
||||
|
||||
// set request codec
|
||||
if r, ok := req.(*rpcRequest); ok {
|
||||
r.codec = codec
|
||||
}
|
||||
|
||||
stream := &rpcStream{
|
||||
id: id,
|
||||
context: ctx,
|
||||
request: req,
|
||||
response: rsp,
|
||||
codec: codec,
|
||||
// used to close the stream
|
||||
closed: make(chan bool),
|
||||
// signal the end of stream,
|
||||
sendEOS: true,
|
||||
// release func
|
||||
release: func(err error) { c.Close() },
|
||||
}
|
||||
|
||||
// wait for error response
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
// send the first message
|
||||
ch <- stream.Send(req.Body())
|
||||
}()
|
||||
|
||||
var grr error
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
grr = err
|
||||
case <-ctx.Done():
|
||||
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
}
|
||||
|
||||
if grr != nil {
|
||||
// set the error
|
||||
stream.Lock()
|
||||
stream.err = grr
|
||||
stream.Unlock()
|
||||
|
||||
// close the stream
|
||||
stream.Close()
|
||||
return nil, grr
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) Init(opts ...client.Option) error {
|
||||
size := r.opts.PoolSize
|
||||
ttl := r.opts.PoolTTL
|
||||
tr := r.opts.Transport
|
||||
|
||||
for _, o := range opts {
|
||||
o(&r.opts)
|
||||
}
|
||||
|
||||
// update pool configuration if the options changed
|
||||
if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport {
|
||||
// close existing pool
|
||||
r.pool.Close()
|
||||
// create new pool
|
||||
r.pool = pool.NewPool(
|
||||
pool.Size(r.opts.PoolSize),
|
||||
pool.TTL(r.opts.PoolTTL),
|
||||
pool.Transport(r.opts.Transport),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) Options() client.Options {
|
||||
return r.opts
|
||||
}
|
||||
|
||||
func (r *rpcClient) Call(ctx context.Context, request client.Request, response interface{}, opts ...client.CallOption) error {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// check if we already have a deadline
|
||||
if d, ok := ctx.Deadline(); !ok {
|
||||
// no deadline so we create a new one
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
|
||||
defer cancel()
|
||||
} else {
|
||||
// got a deadline so no need to setup context
|
||||
// but we need to set the timeout we pass along
|
||||
remaining := d.Sub(time.Now())
|
||||
client.WithRequestTimeout(remaining)(&callOpts)
|
||||
}
|
||||
|
||||
// should we noop right here?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
default:
|
||||
}
|
||||
|
||||
// make copy of call method
|
||||
rcall := r.call
|
||||
|
||||
// wrap the call in reverse
|
||||
for i := len(callOpts.CallWrappers); i > 0; i-- {
|
||||
rcall = callOpts.CallWrappers[i-1](rcall)
|
||||
}
|
||||
|
||||
// return errors.New("go.micro.client", "request timeout", 408)
|
||||
call := func(i int) error {
|
||||
// call backoff first. Someone may want an initial start delay
|
||||
t, err := callOpts.Backoff(ctx, request, i)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
|
||||
}
|
||||
|
||||
// only sleep if greater than 0
|
||||
if t.Seconds() > 0 {
|
||||
time.Sleep(t)
|
||||
}
|
||||
|
||||
// use the router passed as a call option, or fallback to the rpc clients router
|
||||
if callOpts.Router == nil {
|
||||
callOpts.Router = r.opts.Router
|
||||
}
|
||||
// use the selector passed as a call option, or fallback to the rpc clients selector
|
||||
if callOpts.Selector == nil {
|
||||
callOpts.Selector = r.opts.Selector
|
||||
}
|
||||
|
||||
// lookup the route to send the request via
|
||||
route, err := client.LookupRoute(request, callOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// pass a node to enable backwards comparability as changing the
|
||||
// call func would be a breaking change.
|
||||
// todo v3: change the call func to accept a route
|
||||
node := ®istry.Node{Address: route.Address, Metadata: route.Metadata}
|
||||
|
||||
// make the call
|
||||
err = rcall(ctx, node, request, response, callOpts)
|
||||
|
||||
// record the result of the call to inform future routing decisions
|
||||
r.opts.Selector.Record(*route, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// get the retries
|
||||
retries := callOpts.Retries
|
||||
|
||||
// disable retries when using a proxy
|
||||
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
|
||||
retries = 0
|
||||
}
|
||||
|
||||
ch := make(chan error, retries+1)
|
||||
var gerr error
|
||||
|
||||
for i := 0; i <= retries; i++ {
|
||||
go func(i int) {
|
||||
ch <- call(i)
|
||||
}(i)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
|
||||
case err := <-ch:
|
||||
// if the call succeeded lets bail early
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
retry, rerr := callOpts.Retry(ctx, request, i, err)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
||||
if !retry {
|
||||
return err
|
||||
}
|
||||
|
||||
gerr = err
|
||||
}
|
||||
}
|
||||
|
||||
return gerr
|
||||
}
|
||||
|
||||
func (r *rpcClient) Stream(ctx context.Context, request client.Request, opts ...client.CallOption) (client.Stream, error) {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// should we noop right here?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
default:
|
||||
}
|
||||
|
||||
call := func(i int) (client.Stream, error) {
|
||||
// call backoff first. Someone may want an initial start delay
|
||||
t, err := callOpts.Backoff(ctx, request, i)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
|
||||
}
|
||||
|
||||
// only sleep if greater than 0
|
||||
if t.Seconds() > 0 {
|
||||
time.Sleep(t)
|
||||
}
|
||||
|
||||
// use the router passed as a call option, or fallback to the rpc clients router
|
||||
if callOpts.Router == nil {
|
||||
callOpts.Router = r.opts.Router
|
||||
}
|
||||
// use the selector passed as a call option, or fallback to the rpc clients selector
|
||||
if callOpts.Selector == nil {
|
||||
callOpts.Selector = r.opts.Selector
|
||||
}
|
||||
|
||||
// lookup the route to send the request via
|
||||
route, err := client.LookupRoute(request, callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// pass a node to enable backwards compatability as changing the
|
||||
// call func would be a breaking change.
|
||||
// todo v3: change the call func to accept a route
|
||||
node := ®istry.Node{Address: route.Address, Metadata: route.Metadata}
|
||||
|
||||
// perform the call
|
||||
stream, err := r.stream(ctx, node, request, callOpts)
|
||||
|
||||
// record the result of the call to inform future routing decisions
|
||||
r.opts.Selector.Record(*route, err)
|
||||
|
||||
return stream, err
|
||||
}
|
||||
|
||||
type response struct {
|
||||
stream client.Stream
|
||||
err error
|
||||
}
|
||||
|
||||
// get the retries
|
||||
retries := callOpts.Retries
|
||||
|
||||
// disable retries when using a proxy
|
||||
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
|
||||
retries = 0
|
||||
}
|
||||
|
||||
ch := make(chan response, retries+1)
|
||||
var grr error
|
||||
|
||||
for i := 0; i <= retries; i++ {
|
||||
go func(i int) {
|
||||
s, err := call(i)
|
||||
ch <- response{s, err}
|
||||
}(i)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
|
||||
case rsp := <-ch:
|
||||
// if the call succeeded lets bail early
|
||||
if rsp.err == nil {
|
||||
return rsp.stream, nil
|
||||
}
|
||||
|
||||
retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
|
||||
if rerr != nil {
|
||||
return nil, rerr
|
||||
}
|
||||
|
||||
if !retry {
|
||||
return nil, rsp.err
|
||||
}
|
||||
|
||||
grr = rsp.err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, grr
|
||||
}
|
||||
|
||||
func (r *rpcClient) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
|
||||
options := client.PublishOptions{
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if !ok {
|
||||
md = make(map[string]string)
|
||||
}
|
||||
|
||||
id := uuid.New().String()
|
||||
md["Content-Type"] = msg.ContentType()
|
||||
md["Micro-Topic"] = msg.Topic()
|
||||
md["Micro-Id"] = id
|
||||
|
||||
// set the topic
|
||||
topic := msg.Topic()
|
||||
|
||||
// get the exchange
|
||||
if len(options.Exchange) > 0 {
|
||||
topic = options.Exchange
|
||||
}
|
||||
|
||||
// encode message body
|
||||
cf, err := r.newCodec(msg.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
var body []byte
|
||||
|
||||
// passed in raw data
|
||||
if d, ok := msg.Payload().(*raw.Frame); ok {
|
||||
body = d.Data
|
||||
} else {
|
||||
// new buffer
|
||||
b := buf.New(nil)
|
||||
|
||||
if err := cf(b).Write(&codec.Message{
|
||||
Target: topic,
|
||||
Type: codec.Event,
|
||||
Header: map[string]string{
|
||||
"Micro-Id": id,
|
||||
"Micro-Topic": msg.Topic(),
|
||||
},
|
||||
}, msg.Payload()); err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
// set the body
|
||||
body = b.Bytes()
|
||||
}
|
||||
|
||||
if !r.once.Load().(bool) {
|
||||
if err = r.opts.Broker.Connect(); err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
r.once.Store(true)
|
||||
}
|
||||
|
||||
return r.opts.Broker.Publish(topic, &broker.Message{
|
||||
Header: md,
|
||||
Body: body,
|
||||
}, broker.PublishContext(options.Context))
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...client.MessageOption) client.Message {
|
||||
return newMessage(topic, message, r.opts.ContentType, opts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...client.RequestOption) client.Request {
|
||||
return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) String() string {
|
||||
return "mucp"
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
errs "errors"
|
||||
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
raw "github.com/micro/go-micro/v2/codec/bytes"
|
||||
"github.com/micro/go-micro/v2/codec/grpc"
|
||||
"github.com/micro/go-micro/v2/codec/json"
|
||||
"github.com/micro/go-micro/v2/codec/jsonrpc"
|
||||
"github.com/micro/go-micro/v2/codec/proto"
|
||||
"github.com/micro/go-micro/v2/codec/protorpc"
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v2/transport"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
raw "github.com/micro/go-micro/v3/codec/bytes"
|
||||
"github.com/micro/go-micro/v3/codec/grpc"
|
||||
"github.com/micro/go-micro/v3/codec/json"
|
||||
"github.com/micro/go-micro/v3/codec/jsonrpc"
|
||||
"github.com/micro/go-micro/v3/codec/proto"
|
||||
"github.com/micro/go-micro/v3/codec/protorpc"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
const (
|
@@ -1,4 +1,8 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
topic string
|
||||
@@ -6,8 +10,8 @@ type message struct {
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func newMessage(topic string, payload interface{}, contentType string, opts ...MessageOption) Message {
|
||||
var options MessageOptions
|
||||
func newMessage(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message {
|
||||
var options client.MessageOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
type rpcRequest struct {
|
||||
@@ -11,11 +12,11 @@ type rpcRequest struct {
|
||||
contentType string
|
||||
codec codec.Codec
|
||||
body interface{}
|
||||
opts RequestOptions
|
||||
opts client.RequestOptions
|
||||
}
|
||||
|
||||
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||
var opts RequestOptions
|
||||
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...client.RequestOption) client.Request {
|
||||
var opts client.RequestOptions
|
||||
|
||||
for _, o := range reqOpts {
|
||||
o(&opts)
|
@@ -1,7 +1,9 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
)
|
||||
|
||||
func TestRequestOptions(t *testing.T) {
|
||||
@@ -16,7 +18,7 @@ func TestRequestOptions(t *testing.T) {
|
||||
t.Fatalf("expected 'endpoint' got %s", r.ContentType())
|
||||
}
|
||||
|
||||
r2 := newRequest("service", "endpoint", nil, "application/json", WithContentType("application/protobuf"))
|
||||
r2 := newRequest("service", "endpoint", nil, "application/json", client.WithContentType("application/protobuf"))
|
||||
if r2.ContentType() != "application/protobuf" {
|
||||
t.Fatalf("expected 'endpoint' got %s", r2.ContentType())
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v2/transport"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
type rpcResponse struct {
|
@@ -1,11 +1,12 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
// Implements the streamer interface
|
||||
@@ -14,8 +15,8 @@ type rpcStream struct {
|
||||
id string
|
||||
closed chan bool
|
||||
err error
|
||||
request Request
|
||||
response Response
|
||||
request client.Request
|
||||
response client.Response
|
||||
codec codec.Codec
|
||||
context context.Context
|
||||
|
||||
@@ -39,11 +40,11 @@ func (r *rpcStream) Context() context.Context {
|
||||
return r.context
|
||||
}
|
||||
|
||||
func (r *rpcStream) Request() Request {
|
||||
func (r *rpcStream) Request() client.Request {
|
||||
return r.request
|
||||
}
|
||||
|
||||
func (r *rpcStream) Response() Response {
|
||||
func (r *rpcStream) Response() client.Response {
|
||||
return r.response
|
||||
}
|
||||
|
@@ -1,13 +1,14 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v2/registry/memory"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/registry/memory"
|
||||
)
|
||||
|
||||
func newTestRegistry() registry.Registry {
|
||||
@@ -20,8 +21,8 @@ func TestCallAddress(t *testing.T) {
|
||||
endpoint := "Test.Endpoint"
|
||||
address := "10.1.10.1:8080"
|
||||
|
||||
wrap := func(cf CallFunc) CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error {
|
||||
wrap := func(cf client.CallFunc) client.CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
called = true
|
||||
|
||||
if req.Service() != service {
|
||||
@@ -43,14 +44,14 @@ func TestCallAddress(t *testing.T) {
|
||||
|
||||
r := newTestRegistry()
|
||||
c := NewClient(
|
||||
Registry(r),
|
||||
WrapCall(wrap),
|
||||
client.Registry(r),
|
||||
client.WrapCall(wrap),
|
||||
)
|
||||
|
||||
req := c.NewRequest(service, endpoint, nil)
|
||||
|
||||
// test calling remote address
|
||||
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil {
|
||||
if err := c.Call(context.Background(), req, nil, client.WithAddress(address)); err != nil {
|
||||
t.Fatal("call with address error", err)
|
||||
}
|
||||
|
||||
@@ -67,8 +68,8 @@ func TestCallRetry(t *testing.T) {
|
||||
|
||||
var called int
|
||||
|
||||
wrap := func(cf CallFunc) CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error {
|
||||
wrap := func(cf client.CallFunc) client.CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
called++
|
||||
if called == 1 {
|
||||
return errors.InternalServerError("test.error", "retry request")
|
||||
@@ -81,14 +82,14 @@ func TestCallRetry(t *testing.T) {
|
||||
|
||||
r := newTestRegistry()
|
||||
c := NewClient(
|
||||
Registry(r),
|
||||
WrapCall(wrap),
|
||||
client.Registry(r),
|
||||
client.WrapCall(wrap),
|
||||
)
|
||||
|
||||
req := c.NewRequest(service, endpoint, nil)
|
||||
|
||||
// test calling remote address
|
||||
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil {
|
||||
if err := c.Call(context.Background(), req, nil, client.WithAddress(address)); err != nil {
|
||||
t.Fatal("call with address error", err)
|
||||
}
|
||||
|
||||
@@ -105,8 +106,8 @@ func TestCallWrapper(t *testing.T) {
|
||||
endpoint := "Test.Endpoint"
|
||||
address := "10.1.10.1:8080"
|
||||
|
||||
wrap := func(cf CallFunc) CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error {
|
||||
wrap := func(cf client.CallFunc) client.CallFunc {
|
||||
return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
called = true
|
||||
|
||||
if req.Service() != service {
|
||||
@@ -128,8 +129,8 @@ func TestCallWrapper(t *testing.T) {
|
||||
|
||||
r := newTestRegistry()
|
||||
c := NewClient(
|
||||
Registry(r),
|
||||
WrapCall(wrap),
|
||||
client.Registry(r),
|
||||
client.WrapCall(wrap),
|
||||
)
|
||||
|
||||
r.Register(®istry.Service{
|
@@ -1,10 +1,11 @@
|
||||
package client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/transport"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
func TestCallOptions(t *testing.T) {
|
||||
@@ -14,33 +15,33 @@ func TestCallOptions(t *testing.T) {
|
||||
rtimeout time.Duration
|
||||
dtimeout time.Duration
|
||||
}{
|
||||
{false, DefaultRetries, DefaultRequestTimeout, transport.DefaultDialTimeout},
|
||||
{false, client.DefaultRetries, client.DefaultRequestTimeout, transport.DefaultDialTimeout},
|
||||
{true, 10, time.Second, time.Second * 2},
|
||||
}
|
||||
|
||||
for _, d := range testData {
|
||||
var opts Options
|
||||
var cl Client
|
||||
var opts client.Options
|
||||
var cl client.Client
|
||||
|
||||
if d.set {
|
||||
opts = NewOptions(
|
||||
Retries(d.retries),
|
||||
RequestTimeout(d.rtimeout),
|
||||
DialTimeout(d.dtimeout),
|
||||
opts = client.NewOptions(
|
||||
client.Retries(d.retries),
|
||||
client.RequestTimeout(d.rtimeout),
|
||||
client.DialTimeout(d.dtimeout),
|
||||
)
|
||||
|
||||
cl = NewClient(
|
||||
Retries(d.retries),
|
||||
RequestTimeout(d.rtimeout),
|
||||
DialTimeout(d.dtimeout),
|
||||
client.Retries(d.retries),
|
||||
client.RequestTimeout(d.rtimeout),
|
||||
client.DialTimeout(d.dtimeout),
|
||||
)
|
||||
} else {
|
||||
opts = NewOptions()
|
||||
opts = client.NewOptions()
|
||||
cl = NewClient()
|
||||
}
|
||||
|
||||
// test options and those set in client
|
||||
for _, o := range []Options{opts, cl.Options()} {
|
||||
for _, o := range []client.Options{opts, cl.Options()} {
|
||||
if o.CallOptions.Retries != d.retries {
|
||||
t.Fatalf("Expected retries %v got %v", d.retries, o.CallOptions.Retries)
|
||||
}
|
||||
@@ -57,12 +58,12 @@ func TestCallOptions(t *testing.T) {
|
||||
callOpts := o.CallOptions
|
||||
|
||||
// create new opts
|
||||
cretries := WithRetries(o.CallOptions.Retries * 10)
|
||||
crtimeout := WithRequestTimeout(o.CallOptions.RequestTimeout * (time.Second * 10))
|
||||
cdtimeout := WithDialTimeout(o.CallOptions.DialTimeout * (time.Second * 10))
|
||||
cretries := client.WithRetries(o.CallOptions.Retries * 10)
|
||||
crtimeout := client.WithRequestTimeout(o.CallOptions.RequestTimeout * (time.Second * 10))
|
||||
cdtimeout := client.WithDialTimeout(o.CallOptions.DialTimeout * (time.Second * 10))
|
||||
|
||||
// set call options
|
||||
for _, opt := range []CallOption{cretries, crtimeout, cdtimeout} {
|
||||
for _, opt := range []client.CallOption{cretries, crtimeout, cdtimeout} {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
@@ -4,12 +4,14 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/broker"
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v2/router"
|
||||
"github.com/micro/go-micro/v2/selector"
|
||||
"github.com/micro/go-micro/v2/transport"
|
||||
"github.com/micro/go-micro/v3/broker"
|
||||
"github.com/micro/go-micro/v3/broker/http"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
regRouter "github.com/micro/go-micro/v3/router/registry"
|
||||
"github.com/micro/go-micro/v3/selector"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -102,7 +104,7 @@ func NewOptions(options ...Option) Options {
|
||||
opts := Options{
|
||||
Cache: NewCache(),
|
||||
Context: context.Background(),
|
||||
ContentType: DefaultContentType,
|
||||
ContentType: "application/protobuf",
|
||||
Codecs: make(map[string]codec.NewCodec),
|
||||
CallOptions: CallOptions{
|
||||
Backoff: DefaultBackoff,
|
||||
@@ -113,8 +115,8 @@ func NewOptions(options ...Option) Options {
|
||||
},
|
||||
PoolSize: DefaultPoolSize,
|
||||
PoolTTL: DefaultPoolTTL,
|
||||
Broker: broker.DefaultBroker,
|
||||
Router: router.DefaultRouter,
|
||||
Broker: http.NewBroker(),
|
||||
Router: regRouter.NewRouter(),
|
||||
Selector: selector.DefaultSelector,
|
||||
Transport: transport.DefaultTransport,
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ package client
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
)
|
||||
|
||||
// note that returning either false or a non-nil error will result in the call not being retried
|
||||
|
@@ -1,617 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v2/broker"
|
||||
"github.com/micro/go-micro/v2/codec"
|
||||
raw "github.com/micro/go-micro/v2/codec/bytes"
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v2/transport"
|
||||
"github.com/micro/go-micro/v2/util/buf"
|
||||
"github.com/micro/go-micro/v2/util/net"
|
||||
"github.com/micro/go-micro/v2/util/pool"
|
||||
)
|
||||
|
||||
type rpcClient struct {
|
||||
once atomic.Value
|
||||
opts Options
|
||||
pool pool.Pool
|
||||
seq uint64
|
||||
}
|
||||
|
||||
func newRpcClient(opt ...Option) Client {
|
||||
opts := NewOptions(opt...)
|
||||
|
||||
p := pool.NewPool(
|
||||
pool.Size(opts.PoolSize),
|
||||
pool.TTL(opts.PoolTTL),
|
||||
pool.Transport(opts.Transport),
|
||||
)
|
||||
|
||||
rc := &rpcClient{
|
||||
opts: opts,
|
||||
pool: p,
|
||||
seq: 0,
|
||||
}
|
||||
rc.once.Store(false)
|
||||
|
||||
c := Client(rc)
|
||||
|
||||
// wrap in reverse
|
||||
for i := len(opts.Wrappers); i > 0; i-- {
|
||||
c = opts.Wrappers[i-1](c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
|
||||
if c, ok := r.opts.Codecs[contentType]; ok {
|
||||
return c, nil
|
||||
}
|
||||
if cf, ok := DefaultCodecs[contentType]; ok {
|
||||
return cf, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
|
||||
}
|
||||
|
||||
func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error {
|
||||
msg := &transport.Message{
|
||||
Header: make(map[string]string),
|
||||
}
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if ok {
|
||||
for k, v := range md {
|
||||
// don't copy Micro-Topic header, that used for pub/sub
|
||||
// this fix case then client uses the same context that received in subscriber
|
||||
if k == "Micro-Topic" {
|
||||
continue
|
||||
}
|
||||
msg.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
|
||||
// set the content type for the request
|
||||
msg.Header["Content-Type"] = req.ContentType()
|
||||
// set the accept header
|
||||
msg.Header["Accept"] = req.ContentType()
|
||||
|
||||
// setup old protocol
|
||||
cf := setupProtocol(msg, node)
|
||||
|
||||
// no codec specified
|
||||
if cf == nil {
|
||||
var err error
|
||||
cf, err = r.newCodec(req.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
dOpts := []transport.DialOption{
|
||||
transport.WithStream(),
|
||||
}
|
||||
|
||||
if opts.DialTimeout >= 0 {
|
||||
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
|
||||
}
|
||||
|
||||
c, err := r.pool.Get(node.Address, dOpts...)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
|
||||
}
|
||||
|
||||
seq := atomic.AddUint64(&r.seq, 1) - 1
|
||||
codec := newRpcCodec(msg, c, cf, "")
|
||||
|
||||
rsp := &rpcResponse{
|
||||
socket: c,
|
||||
codec: codec,
|
||||
}
|
||||
|
||||
stream := &rpcStream{
|
||||
id: fmt.Sprintf("%v", seq),
|
||||
context: ctx,
|
||||
request: req,
|
||||
response: rsp,
|
||||
codec: codec,
|
||||
closed: make(chan bool),
|
||||
release: func(err error) { r.pool.Release(c, err) },
|
||||
sendEOS: false,
|
||||
}
|
||||
// close the stream on exiting this function
|
||||
defer stream.Close()
|
||||
|
||||
// wait for error response
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// send request
|
||||
if err := stream.Send(req.Body()); err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// recv request
|
||||
if err := stream.Recv(resp); err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// success
|
||||
ch <- nil
|
||||
}()
|
||||
|
||||
var grr error
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
}
|
||||
|
||||
// set the stream error
|
||||
if grr != nil {
|
||||
stream.Lock()
|
||||
stream.err = grr
|
||||
stream.Unlock()
|
||||
|
||||
return grr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) {
|
||||
msg := &transport.Message{
|
||||
Header: make(map[string]string),
|
||||
}
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if ok {
|
||||
for k, v := range md {
|
||||
msg.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
if opts.StreamTimeout > time.Duration(0) {
|
||||
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
|
||||
}
|
||||
// set the content type for the request
|
||||
msg.Header["Content-Type"] = req.ContentType()
|
||||
// set the accept header
|
||||
msg.Header["Accept"] = req.ContentType()
|
||||
|
||||
// set old codecs
|
||||
cf := setupProtocol(msg, node)
|
||||
|
||||
// no codec specified
|
||||
if cf == nil {
|
||||
var err error
|
||||
cf, err = r.newCodec(req.ContentType())
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
dOpts := []transport.DialOption{
|
||||
transport.WithStream(),
|
||||
}
|
||||
|
||||
if opts.DialTimeout >= 0 {
|
||||
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
|
||||
}
|
||||
|
||||
c, err := r.opts.Transport.Dial(node.Address, dOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
|
||||
}
|
||||
|
||||
// increment the sequence number
|
||||
seq := atomic.AddUint64(&r.seq, 1) - 1
|
||||
id := fmt.Sprintf("%v", seq)
|
||||
|
||||
// create codec with stream id
|
||||
codec := newRpcCodec(msg, c, cf, id)
|
||||
|
||||
rsp := &rpcResponse{
|
||||
socket: c,
|
||||
codec: codec,
|
||||
}
|
||||
|
||||
// set request codec
|
||||
if r, ok := req.(*rpcRequest); ok {
|
||||
r.codec = codec
|
||||
}
|
||||
|
||||
stream := &rpcStream{
|
||||
id: id,
|
||||
context: ctx,
|
||||
request: req,
|
||||
response: rsp,
|
||||
codec: codec,
|
||||
// used to close the stream
|
||||
closed: make(chan bool),
|
||||
// signal the end of stream,
|
||||
sendEOS: true,
|
||||
// release func
|
||||
release: func(err error) { c.Close() },
|
||||
}
|
||||
|
||||
// wait for error response
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
// send the first message
|
||||
ch <- stream.Send(req.Body())
|
||||
}()
|
||||
|
||||
var grr error
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
grr = err
|
||||
case <-ctx.Done():
|
||||
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
}
|
||||
|
||||
if grr != nil {
|
||||
// set the error
|
||||
stream.Lock()
|
||||
stream.err = grr
|
||||
stream.Unlock()
|
||||
|
||||
// close the stream
|
||||
stream.Close()
|
||||
return nil, grr
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) Init(opts ...Option) error {
|
||||
size := r.opts.PoolSize
|
||||
ttl := r.opts.PoolTTL
|
||||
tr := r.opts.Transport
|
||||
|
||||
for _, o := range opts {
|
||||
o(&r.opts)
|
||||
}
|
||||
|
||||
// update pool configuration if the options changed
|
||||
if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport {
|
||||
// close existing pool
|
||||
r.pool.Close()
|
||||
// create new pool
|
||||
r.pool = pool.NewPool(
|
||||
pool.Size(r.opts.PoolSize),
|
||||
pool.TTL(r.opts.PoolTTL),
|
||||
pool.Transport(r.opts.Transport),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) Options() Options {
|
||||
return r.opts
|
||||
}
|
||||
|
||||
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// check if we already have a deadline
|
||||
if d, ok := ctx.Deadline(); !ok {
|
||||
// no deadline so we create a new one
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
|
||||
defer cancel()
|
||||
} else {
|
||||
// got a deadline so no need to setup context
|
||||
// but we need to set the timeout we pass along
|
||||
remaining := d.Sub(time.Now())
|
||||
WithRequestTimeout(remaining)(&callOpts)
|
||||
}
|
||||
|
||||
// should we noop right here?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
default:
|
||||
}
|
||||
|
||||
// make copy of call method
|
||||
rcall := r.call
|
||||
|
||||
// wrap the call in reverse
|
||||
for i := len(callOpts.CallWrappers); i > 0; i-- {
|
||||
rcall = callOpts.CallWrappers[i-1](rcall)
|
||||
}
|
||||
|
||||
// return errors.New("go.micro.client", "request timeout", 408)
|
||||
call := func(i int) error {
|
||||
// call backoff first. Someone may want an initial start delay
|
||||
t, err := callOpts.Backoff(ctx, request, i)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
|
||||
}
|
||||
|
||||
// only sleep if greater than 0
|
||||
if t.Seconds() > 0 {
|
||||
time.Sleep(t)
|
||||
}
|
||||
|
||||
// use the router passed as a call option, or fallback to the rpc clients router
|
||||
if callOpts.Router == nil {
|
||||
callOpts.Router = r.opts.Router
|
||||
}
|
||||
// use the selector passed as a call option, or fallback to the rpc clients selector
|
||||
if callOpts.Selector == nil {
|
||||
callOpts.Selector = r.opts.Selector
|
||||
}
|
||||
|
||||
// lookup the route to send the request via
|
||||
route, err := LookupRoute(request, callOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// pass a node to enable backwards comparability as changing the
|
||||
// call func would be a breaking change.
|
||||
// todo v3: change the call func to accept a route
|
||||
node := ®istry.Node{Address: route.Address, Metadata: route.Metadata}
|
||||
|
||||
// make the call
|
||||
err = rcall(ctx, node, request, response, callOpts)
|
||||
|
||||
// record the result of the call to inform future routing decisions
|
||||
r.opts.Selector.Record(*route, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// get the retries
|
||||
retries := callOpts.Retries
|
||||
|
||||
// disable retries when using a proxy
|
||||
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
|
||||
retries = 0
|
||||
}
|
||||
|
||||
ch := make(chan error, retries+1)
|
||||
var gerr error
|
||||
|
||||
for i := 0; i <= retries; i++ {
|
||||
go func(i int) {
|
||||
ch <- call(i)
|
||||
}(i)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
|
||||
case err := <-ch:
|
||||
// if the call succeeded lets bail early
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
retry, rerr := callOpts.Retry(ctx, request, i, err)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
||||
if !retry {
|
||||
return err
|
||||
}
|
||||
|
||||
gerr = err
|
||||
}
|
||||
}
|
||||
|
||||
return gerr
|
||||
}
|
||||
|
||||
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// should we noop right here?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
default:
|
||||
}
|
||||
|
||||
call := func(i int) (Stream, error) {
|
||||
// call backoff first. Someone may want an initial start delay
|
||||
t, err := callOpts.Backoff(ctx, request, i)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
|
||||
}
|
||||
|
||||
// only sleep if greater than 0
|
||||
if t.Seconds() > 0 {
|
||||
time.Sleep(t)
|
||||
}
|
||||
|
||||
// use the router passed as a call option, or fallback to the rpc clients router
|
||||
if callOpts.Router == nil {
|
||||
callOpts.Router = r.opts.Router
|
||||
}
|
||||
// use the selector passed as a call option, or fallback to the rpc clients selector
|
||||
if callOpts.Selector == nil {
|
||||
callOpts.Selector = r.opts.Selector
|
||||
}
|
||||
|
||||
// lookup the route to send the request via
|
||||
route, err := LookupRoute(request, callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// pass a node to enable backwards compatability as changing the
|
||||
// call func would be a breaking change.
|
||||
// todo v3: change the call func to accept a route
|
||||
node := ®istry.Node{Address: route.Address, Metadata: route.Metadata}
|
||||
|
||||
// perform the call
|
||||
stream, err := r.stream(ctx, node, request, callOpts)
|
||||
|
||||
// record the result of the call to inform future routing decisions
|
||||
r.opts.Selector.Record(*route, err)
|
||||
|
||||
return stream, err
|
||||
}
|
||||
|
||||
type response struct {
|
||||
stream Stream
|
||||
err error
|
||||
}
|
||||
|
||||
// get the retries
|
||||
retries := callOpts.Retries
|
||||
|
||||
// disable retries when using a proxy
|
||||
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
|
||||
retries = 0
|
||||
}
|
||||
|
||||
ch := make(chan response, retries+1)
|
||||
var grr error
|
||||
|
||||
for i := 0; i <= retries; i++ {
|
||||
go func(i int) {
|
||||
s, err := call(i)
|
||||
ch <- response{s, err}
|
||||
}(i)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
|
||||
case rsp := <-ch:
|
||||
// if the call succeeded lets bail early
|
||||
if rsp.err == nil {
|
||||
return rsp.stream, nil
|
||||
}
|
||||
|
||||
retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
|
||||
if rerr != nil {
|
||||
return nil, rerr
|
||||
}
|
||||
|
||||
if !retry {
|
||||
return nil, rsp.err
|
||||
}
|
||||
|
||||
grr = rsp.err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, grr
|
||||
}
|
||||
|
||||
func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
|
||||
options := PublishOptions{
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if !ok {
|
||||
md = make(map[string]string)
|
||||
}
|
||||
|
||||
id := uuid.New().String()
|
||||
md["Content-Type"] = msg.ContentType()
|
||||
md["Micro-Topic"] = msg.Topic()
|
||||
md["Micro-Id"] = id
|
||||
|
||||
// set the topic
|
||||
topic := msg.Topic()
|
||||
|
||||
// get the exchange
|
||||
if len(options.Exchange) > 0 {
|
||||
topic = options.Exchange
|
||||
}
|
||||
|
||||
// encode message body
|
||||
cf, err := r.newCodec(msg.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
var body []byte
|
||||
|
||||
// passed in raw data
|
||||
if d, ok := msg.Payload().(*raw.Frame); ok {
|
||||
body = d.Data
|
||||
} else {
|
||||
// new buffer
|
||||
b := buf.New(nil)
|
||||
|
||||
if err := cf(b).Write(&codec.Message{
|
||||
Target: topic,
|
||||
Type: codec.Event,
|
||||
Header: map[string]string{
|
||||
"Micro-Id": id,
|
||||
"Micro-Topic": msg.Topic(),
|
||||
},
|
||||
}, msg.Payload()); err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
// set the body
|
||||
body = b.Bytes()
|
||||
}
|
||||
|
||||
if !r.once.Load().(bool) {
|
||||
if err = r.opts.Broker.Connect(); err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
r.once.Store(true)
|
||||
}
|
||||
|
||||
return r.opts.Broker.Publish(topic, &broker.Message{
|
||||
Header: md,
|
||||
Body: body,
|
||||
}, broker.PublishContext(options.Context))
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...MessageOption) Message {
|
||||
return newMessage(topic, message, r.opts.ContentType, opts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) String() string {
|
||||
return "mucp"
|
||||
}
|
@@ -11,9 +11,9 @@ import (
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/micro/go-micro/v2/api"
|
||||
client "github.com/micro/go-micro/v2/client"
|
||||
server "github.com/micro/go-micro/v2/server"
|
||||
api "github.com/micro/go-micro/v3/api"
|
||||
client "github.com/micro/go-micro/v3/client"
|
||||
server "github.com/micro/go-micro/v3/server"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
65
client/test_request.go
Normal file
65
client/test_request.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
service string
|
||||
method string
|
||||
endpoint string
|
||||
contentType string
|
||||
codec codec.Codec
|
||||
body interface{}
|
||||
opts RequestOptions
|
||||
}
|
||||
|
||||
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||
var opts RequestOptions
|
||||
|
||||
for _, o := range reqOpts {
|
||||
o(&opts)
|
||||
}
|
||||
|
||||
// set the content-type specified
|
||||
if len(opts.ContentType) > 0 {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
|
||||
return &testRequest{
|
||||
service: service,
|
||||
method: endpoint,
|
||||
endpoint: endpoint,
|
||||
body: request,
|
||||
contentType: contentType,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRequest) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *testRequest) Service() string {
|
||||
return r.service
|
||||
}
|
||||
|
||||
func (r *testRequest) Method() string {
|
||||
return r.method
|
||||
}
|
||||
|
||||
func (r *testRequest) Endpoint() string {
|
||||
return r.endpoint
|
||||
}
|
||||
|
||||
func (r *testRequest) Body() interface{} {
|
||||
return r.body
|
||||
}
|
||||
|
||||
func (r *testRequest) Codec() codec.Writer {
|
||||
return r.codec
|
||||
}
|
||||
|
||||
func (r *testRequest) Stream() bool {
|
||||
return r.opts.Stream
|
||||
}
|
@@ -3,10 +3,10 @@ package client
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/router"
|
||||
"github.com/micro/go-micro/v2/selector"
|
||||
pnet "github.com/micro/go-micro/v2/util/net"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
"github.com/micro/go-micro/v3/selector"
|
||||
pnet "github.com/micro/go-micro/v3/util/net"
|
||||
)
|
||||
|
||||
// LookupRoute for a request using the router and then choose one using the selector
|
||||
|
@@ -3,7 +3,7 @@ package client
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
// CallFunc represents the individual call func
|
||||
|
Reference in New Issue
Block a user