v3 refactor (#1868)

* Move to v3

Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
Asim Aslam
2020-07-27 13:22:00 +01:00
committed by GitHub
parent 9dfeb98111
commit 563768b58a
424 changed files with 6383 additions and 22490 deletions

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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(),
},
})

View File

@@ -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")

View File

@@ -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()
}

View File

@@ -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"

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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))

View File

@@ -1,7 +1,7 @@
package grpc
import (
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v3/client"
)
type grpcEvent struct {

View File

@@ -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"
)

View File

@@ -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 {

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -1,7 +1,7 @@
package client
package mucp
import (
"github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v3/registry"
)
var (

View File

@@ -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 := &registry.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 := &registry.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"
}

View File

@@ -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 (

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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())
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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(&registry.Service{

View File

@@ -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)
}

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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 := &registry.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 := &registry.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"
}

View File

@@ -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
View 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
}

View File

@@ -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

View File

@@ -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