diff --git a/client/options.go b/client/options.go index d11a8e50..4d14a804 100644 --- a/client/options.go +++ b/client/options.go @@ -12,6 +12,7 @@ type options struct { broker broker.Broker registry registry.Registry transport transport.Transport + wrappers []Wrapper } // Broker to be used for pub/sub @@ -48,3 +49,10 @@ func Transport(t transport.Transport) Option { o.transport = t } } + +// Adds a Wrapper to a list of options passed into the client +func Wrap(w Wrapper) Option { + return func(o *options) { + o.wrappers = append(o.wrappers, w) + } +} diff --git a/client/rpc_client.go b/client/rpc_client.go index 7b6c1ffd..b2184162 100644 --- a/client/rpc_client.go +++ b/client/rpc_client.go @@ -23,6 +23,8 @@ type rpcClient struct { } func newRpcClient(opt ...Option) Client { + var once sync.Once + opts := options{ codecs: make(map[string]CodecFunc), } @@ -43,12 +45,19 @@ func newRpcClient(opt ...Option) Client { opts.broker = broker.DefaultBroker } - var once sync.Once - - return &rpcClient{ + rc := &rpcClient{ once: once, opts: opts, } + + 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) codecFunc(contentType string) (codecFunc, error) { diff --git a/client/wrapper.go b/client/wrapper.go new file mode 100644 index 00000000..b0df7fcc --- /dev/null +++ b/client/wrapper.go @@ -0,0 +1,37 @@ +/* +Wrapper is a type of middleware for the go-micro client. It allows +the client to be "wrapped" so that requests and responses can be intercepted +to perform extra requirements such as auth, tracing, monitoring, logging, etc. + +Example usage: + + import ( + "log" + "github.com/micro/go-micro/client" + + ) + + type LogWrapper struct { + client.Client + } + + func (l *LogWrapper) Call(ctx context.Context, req Request, rsp interface{}) error { + log.Println("Making request to service " + req.Service() + " method " + req.Method()) + return w.Client.Call(ctx, req, rsp) + } + + func Wrapper(c client.Client) client.Client { + return &LogWrapper{c} + } + + func main() { + c := client.NewClient(client.Wrap(Wrapper)) + + } + + +*/ +package client + +// Wrapper wraps a client and returns a client +type Wrapper func(Client) Client diff --git a/examples/client/wrapper.go b/examples/client/wrapper.go new file mode 100644 index 00000000..3d61432d --- /dev/null +++ b/examples/client/wrapper.go @@ -0,0 +1,87 @@ +/* + An example of how to use client.Wrapper as middleware +*/ +package main + +import ( + "fmt" + "time" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/cmd" + c "github.com/micro/go-micro/context" + example "github.com/micro/go-micro/examples/server/proto/example" + "golang.org/x/net/context" +) + +// log wrapper logs every time a request is made +type logWrapper struct { + client.Client +} + +func (l *logWrapper) Call(ctx context.Context, req client.Request, rsp interface{}) error { + md, _ := c.GetMetadata(ctx) + fmt.Printf("[Log Wrapper] ctx: %v service: %s method: %s\n", md, req.Service(), req.Method()) + return l.Client.Call(ctx, req, rsp) +} + +// trace wrapper attaches a unique trace ID - timestamp +type traceWrapper struct { + client.Client +} + +func (t *traceWrapper) Call(ctx context.Context, req client.Request, rsp interface{}) error { + ctx = c.WithMetadata(ctx, map[string]string{ + "X-Trace-Id": fmt.Sprintf("%d", time.Now().Unix()), + }) + return t.Client.Call(ctx, req, rsp) +} + +// Implements client.Wrapper as logWrapper +func logWrap(c client.Client) client.Client { + return &logWrapper{c} +} + +// Implements client.Wrapper as traceWrapper +func traceWrap(c client.Client) client.Client { + return &traceWrapper{c} +} + +// Calls the example service +func call(i int) { + // Create new request to service go.micro.srv.example, method Example.Call + req := client.NewRequest("go.micro.srv.example", "Example.Call", &example.Request{ + Name: "John", + }) + + // create context with metadata + ctx := c.WithMetadata(context.Background(), map[string]string{ + "X-User-Id": "john", + "X-From-Id": "script", + }) + + rsp := &example.Response{} + + // Call service + if err := client.Call(ctx, req, rsp); err != nil { + fmt.Println("call err: ", err, rsp) + return + } +} + +func main() { + cmd.Init() + + // Wrap the default client + client.DefaultClient = logWrap(client.DefaultClient) + + call(0) + + // Wrap using client.Wrap option + client.DefaultClient = client.NewClient( + client.Wrap(traceWrap), + client.Wrap(logWrap), + ) + + call(1) +}