v3 refactor (#1868)
* Move to v3 Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
59
client/mucp/common_test.go
Normal file
59
client/mucp/common_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": {
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
Metadata: map[string]string{
|
||||
"protocol": "mucp",
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
Metadata: map[string]string{
|
||||
"protocol": "mucp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
Metadata: map[string]string{
|
||||
"protocol": "mucp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost:8888",
|
||||
Metadata: map[string]string{
|
||||
"protocol": "mucp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -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"
|
||||
}
|
||||
|
267
client/mucp/mucp_codec.go
Normal file
267
client/mucp/mucp_codec.go
Normal file
@@ -0,0 +1,267 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
errs "errors"
|
||||
|
||||
"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 (
|
||||
lastStreamResponseError = "EOS"
|
||||
)
|
||||
|
||||
// serverError represents an error that has been returned from
|
||||
// the remote side of the RPC connection.
|
||||
type serverError string
|
||||
|
||||
func (e serverError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// errShutdown holds the specific error for closing/closed connections
|
||||
var (
|
||||
errShutdown = errs.New("connection is shut down")
|
||||
)
|
||||
|
||||
type rpcCodec struct {
|
||||
client transport.Client
|
||||
codec codec.Codec
|
||||
|
||||
req *transport.Message
|
||||
buf *readWriteCloser
|
||||
|
||||
// signify if its a stream
|
||||
stream string
|
||||
}
|
||||
|
||||
type readWriteCloser struct {
|
||||
wbuf *bytes.Buffer
|
||||
rbuf *bytes.Buffer
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultContentType = "application/protobuf"
|
||||
|
||||
DefaultCodecs = map[string]codec.NewCodec{
|
||||
"application/grpc": grpc.NewCodec,
|
||||
"application/grpc+json": grpc.NewCodec,
|
||||
"application/grpc+proto": grpc.NewCodec,
|
||||
"application/protobuf": proto.NewCodec,
|
||||
"application/json": json.NewCodec,
|
||||
"application/json-rpc": jsonrpc.NewCodec,
|
||||
"application/proto-rpc": protorpc.NewCodec,
|
||||
"application/octet-stream": raw.NewCodec,
|
||||
}
|
||||
|
||||
// TODO: remove legacy codec list
|
||||
defaultCodecs = map[string]codec.NewCodec{
|
||||
"application/json": jsonrpc.NewCodec,
|
||||
"application/json-rpc": jsonrpc.NewCodec,
|
||||
"application/protobuf": protorpc.NewCodec,
|
||||
"application/proto-rpc": protorpc.NewCodec,
|
||||
"application/octet-stream": protorpc.NewCodec,
|
||||
}
|
||||
)
|
||||
|
||||
func (rwc *readWriteCloser) Read(p []byte) (n int, err error) {
|
||||
return rwc.rbuf.Read(p)
|
||||
}
|
||||
|
||||
func (rwc *readWriteCloser) Write(p []byte) (n int, err error) {
|
||||
return rwc.wbuf.Write(p)
|
||||
}
|
||||
|
||||
func (rwc *readWriteCloser) Close() error {
|
||||
rwc.rbuf.Reset()
|
||||
rwc.wbuf.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHeaders(m *codec.Message) {
|
||||
set := func(v, hdr string) string {
|
||||
if len(v) > 0 {
|
||||
return v
|
||||
}
|
||||
return m.Header[hdr]
|
||||
}
|
||||
|
||||
// check error in header
|
||||
m.Error = set(m.Error, "Micro-Error")
|
||||
|
||||
// check endpoint in header
|
||||
m.Endpoint = set(m.Endpoint, "Micro-Endpoint")
|
||||
|
||||
// check method in header
|
||||
m.Method = set(m.Method, "Micro-Method")
|
||||
|
||||
// set the request id
|
||||
m.Id = set(m.Id, "Micro-Id")
|
||||
}
|
||||
|
||||
func setHeaders(m *codec.Message, stream string) {
|
||||
set := func(hdr, v string) {
|
||||
if len(v) == 0 {
|
||||
return
|
||||
}
|
||||
m.Header[hdr] = v
|
||||
}
|
||||
|
||||
set("Micro-Id", m.Id)
|
||||
set("Micro-Service", m.Target)
|
||||
set("Micro-Method", m.Method)
|
||||
set("Micro-Endpoint", m.Endpoint)
|
||||
set("Micro-Error", m.Error)
|
||||
|
||||
if len(stream) > 0 {
|
||||
set("Micro-Stream", stream)
|
||||
}
|
||||
}
|
||||
|
||||
// setupProtocol sets up the old protocol
|
||||
func setupProtocol(msg *transport.Message, node *registry.Node) codec.NewCodec {
|
||||
// get the protocol from node metadata
|
||||
if protocol := node.Metadata["protocol"]; len(protocol) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// processing topic publishing
|
||||
if len(msg.Header["Micro-Topic"]) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// no protocol use old codecs
|
||||
switch msg.Header["Content-Type"] {
|
||||
case "application/json":
|
||||
msg.Header["Content-Type"] = "application/json-rpc"
|
||||
case "application/protobuf":
|
||||
msg.Header["Content-Type"] = "application/proto-rpc"
|
||||
}
|
||||
|
||||
// now return codec
|
||||
return defaultCodecs[msg.Header["Content-Type"]]
|
||||
}
|
||||
|
||||
func newRpcCodec(req *transport.Message, client transport.Client, c codec.NewCodec, stream string) codec.Codec {
|
||||
rwc := &readWriteCloser{
|
||||
wbuf: bytes.NewBuffer(nil),
|
||||
rbuf: bytes.NewBuffer(nil),
|
||||
}
|
||||
r := &rpcCodec{
|
||||
buf: rwc,
|
||||
client: client,
|
||||
codec: c(rwc),
|
||||
req: req,
|
||||
stream: stream,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *rpcCodec) Write(m *codec.Message, body interface{}) error {
|
||||
c.buf.wbuf.Reset()
|
||||
|
||||
// create header
|
||||
if m.Header == nil {
|
||||
m.Header = map[string]string{}
|
||||
}
|
||||
|
||||
// copy original header
|
||||
for k, v := range c.req.Header {
|
||||
m.Header[k] = v
|
||||
}
|
||||
|
||||
// set the mucp headers
|
||||
setHeaders(m, c.stream)
|
||||
|
||||
// if body is bytes Frame don't encode
|
||||
if body != nil {
|
||||
if b, ok := body.(*raw.Frame); ok {
|
||||
// set body
|
||||
m.Body = b.Data
|
||||
} else {
|
||||
// write to codec
|
||||
if err := c.codec.Write(m, body); err != nil {
|
||||
return errors.InternalServerError("go.micro.client.codec", err.Error())
|
||||
}
|
||||
// set body
|
||||
m.Body = c.buf.wbuf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// create new transport message
|
||||
msg := transport.Message{
|
||||
Header: m.Header,
|
||||
Body: m.Body,
|
||||
}
|
||||
|
||||
// send the request
|
||||
if err := c.client.Send(&msg); err != nil {
|
||||
return errors.InternalServerError("go.micro.client.transport", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rpcCodec) ReadHeader(m *codec.Message, r codec.MessageType) error {
|
||||
var tm transport.Message
|
||||
|
||||
// read message from transport
|
||||
if err := c.client.Recv(&tm); err != nil {
|
||||
return errors.InternalServerError("go.micro.client.transport", err.Error())
|
||||
}
|
||||
|
||||
c.buf.rbuf.Reset()
|
||||
c.buf.rbuf.Write(tm.Body)
|
||||
|
||||
// set headers from transport
|
||||
m.Header = tm.Header
|
||||
|
||||
// read header
|
||||
err := c.codec.ReadHeader(m, r)
|
||||
|
||||
// get headers
|
||||
getHeaders(m)
|
||||
|
||||
// return header error
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client.codec", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rpcCodec) ReadBody(b interface{}) error {
|
||||
// read body
|
||||
// read raw data
|
||||
if v, ok := b.(*raw.Frame); ok {
|
||||
v.Data = c.buf.rbuf.Bytes()
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.codec.ReadBody(b); err != nil {
|
||||
return errors.InternalServerError("go.micro.client.codec", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rpcCodec) Close() error {
|
||||
c.buf.Close()
|
||||
c.codec.Close()
|
||||
if err := c.client.Close(); err != nil {
|
||||
return errors.InternalServerError("go.micro.client.transport", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rpcCodec) String() string {
|
||||
return "rpc"
|
||||
}
|
40
client/mucp/mucp_message.go
Normal file
40
client/mucp/mucp_message.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
topic string
|
||||
contentType string
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func newMessage(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message {
|
||||
var options client.MessageOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if len(options.ContentType) > 0 {
|
||||
contentType = options.ContentType
|
||||
}
|
||||
|
||||
return &message{
|
||||
payload: payload,
|
||||
topic: topic,
|
||||
contentType: contentType,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *message) ContentType() string {
|
||||
return m.contentType
|
||||
}
|
||||
|
||||
func (m *message) Topic() string {
|
||||
return m.topic
|
||||
}
|
||||
|
||||
func (m *message) Payload() interface{} {
|
||||
return m.payload
|
||||
}
|
66
client/mucp/mucp_request.go
Normal file
66
client/mucp/mucp_request.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
type rpcRequest struct {
|
||||
service string
|
||||
method string
|
||||
endpoint string
|
||||
contentType string
|
||||
codec codec.Codec
|
||||
body interface{}
|
||||
opts client.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)
|
||||
}
|
||||
|
||||
// set the content-type specified
|
||||
if len(opts.ContentType) > 0 {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
|
||||
return &rpcRequest{
|
||||
service: service,
|
||||
method: endpoint,
|
||||
endpoint: endpoint,
|
||||
body: request,
|
||||
contentType: contentType,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcRequest) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Service() string {
|
||||
return r.service
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Method() string {
|
||||
return r.method
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Endpoint() string {
|
||||
return r.endpoint
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Body() interface{} {
|
||||
return r.body
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Codec() codec.Writer {
|
||||
return r.codec
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Stream() bool {
|
||||
return r.opts.Stream
|
||||
}
|
25
client/mucp/mucp_request_test.go
Normal file
25
client/mucp/mucp_request_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
)
|
||||
|
||||
func TestRequestOptions(t *testing.T) {
|
||||
r := newRequest("service", "endpoint", nil, "application/json")
|
||||
if r.Service() != "service" {
|
||||
t.Fatalf("expected 'service' got %s", r.Service())
|
||||
}
|
||||
if r.Endpoint() != "endpoint" {
|
||||
t.Fatalf("expected 'endpoint' got %s", r.Endpoint())
|
||||
}
|
||||
if r.ContentType() != "application/json" {
|
||||
t.Fatalf("expected 'endpoint' got %s", r.ContentType())
|
||||
}
|
||||
|
||||
r2 := newRequest("service", "endpoint", nil, "application/json", client.WithContentType("application/protobuf"))
|
||||
if r2.ContentType() != "application/protobuf" {
|
||||
t.Fatalf("expected 'endpoint' got %s", r2.ContentType())
|
||||
}
|
||||
}
|
35
client/mucp/mucp_response.go
Normal file
35
client/mucp/mucp_response.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
type rpcResponse struct {
|
||||
header map[string]string
|
||||
body []byte
|
||||
socket transport.Socket
|
||||
codec codec.Codec
|
||||
}
|
||||
|
||||
func (r *rpcResponse) Codec() codec.Reader {
|
||||
return r.codec
|
||||
}
|
||||
|
||||
func (r *rpcResponse) Header() map[string]string {
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (r *rpcResponse) Read() ([]byte, error) {
|
||||
var msg transport.Message
|
||||
|
||||
if err := r.socket.Recv(&msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set internals
|
||||
r.header = msg.Header
|
||||
r.body = msg.Body
|
||||
|
||||
return msg.Body, nil
|
||||
}
|
165
client/mucp/mucp_stream.go
Normal file
165
client/mucp/mucp_stream.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
// Implements the streamer interface
|
||||
type rpcStream struct {
|
||||
sync.RWMutex
|
||||
id string
|
||||
closed chan bool
|
||||
err error
|
||||
request client.Request
|
||||
response client.Response
|
||||
codec codec.Codec
|
||||
context context.Context
|
||||
|
||||
// signal whether we should send EOS
|
||||
sendEOS bool
|
||||
|
||||
// release releases the connection back to the pool
|
||||
release func(err error)
|
||||
}
|
||||
|
||||
func (r *rpcStream) isClosed() bool {
|
||||
select {
|
||||
case <-r.closed:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcStream) Context() context.Context {
|
||||
return r.context
|
||||
}
|
||||
|
||||
func (r *rpcStream) Request() client.Request {
|
||||
return r.request
|
||||
}
|
||||
|
||||
func (r *rpcStream) Response() client.Response {
|
||||
return r.response
|
||||
}
|
||||
|
||||
func (r *rpcStream) Send(msg interface{}) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.isClosed() {
|
||||
r.err = errShutdown
|
||||
return errShutdown
|
||||
}
|
||||
|
||||
req := codec.Message{
|
||||
Id: r.id,
|
||||
Target: r.request.Service(),
|
||||
Method: r.request.Method(),
|
||||
Endpoint: r.request.Endpoint(),
|
||||
Type: codec.Request,
|
||||
}
|
||||
|
||||
if err := r.codec.Write(&req, msg); err != nil {
|
||||
r.err = err
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rpcStream) Recv(msg interface{}) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.isClosed() {
|
||||
r.err = errShutdown
|
||||
return errShutdown
|
||||
}
|
||||
|
||||
var resp codec.Message
|
||||
|
||||
r.Unlock()
|
||||
err := r.codec.ReadHeader(&resp, codec.Response)
|
||||
r.Lock()
|
||||
if err != nil {
|
||||
if err == io.EOF && !r.isClosed() {
|
||||
r.err = io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
r.err = err
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(resp.Error) > 0:
|
||||
// We've got an error response. Give this to the request;
|
||||
// any subsequent requests will get the ReadResponseBody
|
||||
// error if there is one.
|
||||
if resp.Error != lastStreamResponseError {
|
||||
r.err = serverError(resp.Error)
|
||||
} else {
|
||||
r.err = io.EOF
|
||||
}
|
||||
r.Unlock()
|
||||
err = r.codec.ReadBody(nil)
|
||||
r.Lock()
|
||||
if err != nil {
|
||||
r.err = err
|
||||
}
|
||||
default:
|
||||
r.Unlock()
|
||||
err = r.codec.ReadBody(msg)
|
||||
r.Lock()
|
||||
if err != nil {
|
||||
r.err = err
|
||||
}
|
||||
}
|
||||
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *rpcStream) Error() error {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *rpcStream) Close() error {
|
||||
r.Lock()
|
||||
|
||||
select {
|
||||
case <-r.closed:
|
||||
r.Unlock()
|
||||
return nil
|
||||
default:
|
||||
close(r.closed)
|
||||
r.Unlock()
|
||||
|
||||
// send the end of stream message
|
||||
if r.sendEOS {
|
||||
// no need to check for error
|
||||
r.codec.Write(&codec.Message{
|
||||
Id: r.id,
|
||||
Target: r.request.Service(),
|
||||
Method: r.request.Method(),
|
||||
Endpoint: r.request.Endpoint(),
|
||||
Type: codec.Error,
|
||||
Error: lastStreamResponseError,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
err := r.codec.Close()
|
||||
|
||||
// release the connection
|
||||
r.release(r.Error())
|
||||
|
||||
// return the codec error
|
||||
return err
|
||||
}
|
||||
}
|
158
client/mucp/mucp_test.go
Normal file
158
client/mucp/mucp_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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 {
|
||||
return memory.NewRegistry(memory.Services(testData))
|
||||
}
|
||||
|
||||
func TestCallAddress(t *testing.T) {
|
||||
var called bool
|
||||
service := "test.service"
|
||||
endpoint := "Test.Endpoint"
|
||||
address := "10.1.10.1:8080"
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("expected service: %s got %s", service, req.Service())
|
||||
}
|
||||
|
||||
if req.Endpoint() != endpoint {
|
||||
return fmt.Errorf("expected service: %s got %s", endpoint, req.Endpoint())
|
||||
}
|
||||
|
||||
if node.Address != address {
|
||||
return fmt.Errorf("expected address: %s got %s", address, node.Address)
|
||||
}
|
||||
|
||||
// don't do the call
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
r := newTestRegistry()
|
||||
c := NewClient(
|
||||
client.Registry(r),
|
||||
client.WrapCall(wrap),
|
||||
)
|
||||
|
||||
req := c.NewRequest(service, endpoint, nil)
|
||||
|
||||
// test calling remote address
|
||||
if err := c.Call(context.Background(), req, nil, client.WithAddress(address)); err != nil {
|
||||
t.Fatal("call with address error", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatal("wrapper not called")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCallRetry(t *testing.T) {
|
||||
service := "test.service"
|
||||
endpoint := "Test.Endpoint"
|
||||
address := "10.1.10.1"
|
||||
|
||||
var called int
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// don't do the call
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
r := newTestRegistry()
|
||||
c := NewClient(
|
||||
client.Registry(r),
|
||||
client.WrapCall(wrap),
|
||||
)
|
||||
|
||||
req := c.NewRequest(service, endpoint, nil)
|
||||
|
||||
// test calling remote address
|
||||
if err := c.Call(context.Background(), req, nil, client.WithAddress(address)); err != nil {
|
||||
t.Fatal("call with address error", err)
|
||||
}
|
||||
|
||||
// num calls
|
||||
if called < c.Options().CallOptions.Retries+1 {
|
||||
t.Fatal("request not retried")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWrapper(t *testing.T) {
|
||||
var called bool
|
||||
id := "test.1"
|
||||
service := "test.service"
|
||||
endpoint := "Test.Endpoint"
|
||||
address := "10.1.10.1:8080"
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("expected service: %s got %s", service, req.Service())
|
||||
}
|
||||
|
||||
if req.Endpoint() != endpoint {
|
||||
return fmt.Errorf("expected service: %s got %s", endpoint, req.Endpoint())
|
||||
}
|
||||
|
||||
if node.Address != address {
|
||||
return fmt.Errorf("expected address: %s got %s", address, node.Address)
|
||||
}
|
||||
|
||||
// don't do the call
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
r := newTestRegistry()
|
||||
c := NewClient(
|
||||
client.Registry(r),
|
||||
client.WrapCall(wrap),
|
||||
)
|
||||
|
||||
r.Register(®istry.Service{
|
||||
Name: service,
|
||||
Version: "latest",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: id,
|
||||
Address: address,
|
||||
Metadata: map[string]string{
|
||||
"protocol": "mucp",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
req := c.NewRequest(service, endpoint, nil)
|
||||
if err := c.Call(context.Background(), req, nil); err != nil {
|
||||
t.Fatal("call wrapper error", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatal("wrapper not called")
|
||||
}
|
||||
}
|
86
client/mucp/options_test.go
Normal file
86
client/mucp/options_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
func TestCallOptions(t *testing.T) {
|
||||
testData := []struct {
|
||||
set bool
|
||||
retries int
|
||||
rtimeout time.Duration
|
||||
dtimeout time.Duration
|
||||
}{
|
||||
{false, client.DefaultRetries, client.DefaultRequestTimeout, transport.DefaultDialTimeout},
|
||||
{true, 10, time.Second, time.Second * 2},
|
||||
}
|
||||
|
||||
for _, d := range testData {
|
||||
var opts client.Options
|
||||
var cl client.Client
|
||||
|
||||
if d.set {
|
||||
opts = client.NewOptions(
|
||||
client.Retries(d.retries),
|
||||
client.RequestTimeout(d.rtimeout),
|
||||
client.DialTimeout(d.dtimeout),
|
||||
)
|
||||
|
||||
cl = NewClient(
|
||||
client.Retries(d.retries),
|
||||
client.RequestTimeout(d.rtimeout),
|
||||
client.DialTimeout(d.dtimeout),
|
||||
)
|
||||
} else {
|
||||
opts = client.NewOptions()
|
||||
cl = NewClient()
|
||||
}
|
||||
|
||||
// test options and those set in client
|
||||
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)
|
||||
}
|
||||
|
||||
if o.CallOptions.RequestTimeout != d.rtimeout {
|
||||
t.Fatalf("Expected request timeout %v got %v", d.rtimeout, o.CallOptions.RequestTimeout)
|
||||
}
|
||||
|
||||
if o.CallOptions.DialTimeout != d.dtimeout {
|
||||
t.Fatalf("Expected %v got %v", d.dtimeout, o.CallOptions.DialTimeout)
|
||||
}
|
||||
|
||||
// copy CallOptions
|
||||
callOpts := o.CallOptions
|
||||
|
||||
// create new opts
|
||||
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 []client.CallOption{cretries, crtimeout, cdtimeout} {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// check call options
|
||||
if e := o.CallOptions.Retries * 10; callOpts.Retries != e {
|
||||
t.Fatalf("Expected retries %v got %v", e, callOpts.Retries)
|
||||
}
|
||||
|
||||
if e := o.CallOptions.RequestTimeout * (time.Second * 10); callOpts.RequestTimeout != e {
|
||||
t.Fatalf("Expected request timeout %v got %v", e, callOpts.RequestTimeout)
|
||||
}
|
||||
|
||||
if e := o.CallOptions.DialTimeout * (time.Second * 10); callOpts.DialTimeout != e {
|
||||
t.Fatalf("Expected %v got %v", e, callOpts.DialTimeout)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user