github.com/annwntech/go-micro/v2@v2.9.5/util/wrapper/wrapper.go (about) 1 package wrapper 2 3 import ( 4 "context" 5 "reflect" 6 "strings" 7 8 "github.com/annwntech/go-micro/v2/auth" 9 "github.com/annwntech/go-micro/v2/client" 10 "github.com/annwntech/go-micro/v2/debug/stats" 11 "github.com/annwntech/go-micro/v2/debug/trace" 12 "github.com/annwntech/go-micro/v2/errors" 13 "github.com/annwntech/go-micro/v2/metadata" 14 "github.com/annwntech/go-micro/v2/server" 15 ) 16 17 type fromServiceWrapper struct { 18 client.Client 19 20 // headers to inject 21 headers metadata.Metadata 22 } 23 24 var ( 25 HeaderPrefix = "Micro-" 26 ) 27 28 func (f *fromServiceWrapper) setHeaders(ctx context.Context) context.Context { 29 // don't overwrite keys 30 return metadata.MergeContext(ctx, f.headers, false) 31 } 32 33 func (f *fromServiceWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 34 ctx = f.setHeaders(ctx) 35 return f.Client.Call(ctx, req, rsp, opts...) 36 } 37 38 func (f *fromServiceWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { 39 ctx = f.setHeaders(ctx) 40 return f.Client.Stream(ctx, req, opts...) 41 } 42 43 func (f *fromServiceWrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { 44 ctx = f.setHeaders(ctx) 45 return f.Client.Publish(ctx, p, opts...) 46 } 47 48 // FromService wraps a client to inject service and auth metadata 49 func FromService(name string, c client.Client) client.Client { 50 return &fromServiceWrapper{ 51 c, 52 metadata.Metadata{ 53 HeaderPrefix + "From-Service": name, 54 }, 55 } 56 } 57 58 // HandlerStats wraps a server handler to generate request/error stats 59 func HandlerStats(stats stats.Stats) server.HandlerWrapper { 60 // return a handler wrapper 61 return func(h server.HandlerFunc) server.HandlerFunc { 62 // return a function that returns a function 63 return func(ctx context.Context, req server.Request, rsp interface{}) error { 64 // execute the handler 65 err := h(ctx, req, rsp) 66 // record the stats 67 stats.Record(err) 68 // return the error 69 return err 70 } 71 } 72 } 73 74 type traceWrapper struct { 75 client.Client 76 77 name string 78 trace trace.Tracer 79 } 80 81 func (c *traceWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 82 newCtx, s := c.trace.Start(ctx, req.Service()+"."+req.Endpoint()) 83 84 s.Type = trace.SpanTypeRequestOutbound 85 err := c.Client.Call(newCtx, req, rsp, opts...) 86 if err != nil { 87 s.Metadata["error"] = err.Error() 88 } 89 90 // finish the trace 91 c.trace.Finish(s) 92 93 return err 94 } 95 96 // TraceCall is a call tracing wrapper 97 func TraceCall(name string, t trace.Tracer, c client.Client) client.Client { 98 return &traceWrapper{ 99 name: name, 100 trace: t, 101 Client: c, 102 } 103 } 104 105 // TraceHandler wraps a server handler to perform tracing 106 func TraceHandler(t trace.Tracer) server.HandlerWrapper { 107 // return a handler wrapper 108 return func(h server.HandlerFunc) server.HandlerFunc { 109 // return a function that returns a function 110 return func(ctx context.Context, req server.Request, rsp interface{}) error { 111 // don't store traces for debug 112 if strings.HasPrefix(req.Endpoint(), "Debug.") { 113 return h(ctx, req, rsp) 114 } 115 116 // get the span 117 newCtx, s := t.Start(ctx, req.Service()+"."+req.Endpoint()) 118 s.Type = trace.SpanTypeRequestInbound 119 120 err := h(newCtx, req, rsp) 121 if err != nil { 122 s.Metadata["error"] = err.Error() 123 } 124 125 // finish 126 t.Finish(s) 127 128 return err 129 } 130 } 131 } 132 133 type authWrapper struct { 134 client.Client 135 auth func() auth.Auth 136 } 137 138 func (a *authWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 139 // parse the options 140 var options client.CallOptions 141 for _, o := range opts { 142 o(&options) 143 } 144 145 // check to see if the authorization header has already been set. 146 // We dont't override the header unless the ServiceToken option has 147 // been specified or the header wasn't provided 148 if _, ok := metadata.Get(ctx, "Authorization"); ok && !options.ServiceToken { 149 return a.Client.Call(ctx, req, rsp, opts...) 150 } 151 152 // if auth is nil we won't be able to get an access token, so we execute 153 // the request without one. 154 aa := a.auth() 155 if aa == nil { 156 return a.Client.Call(ctx, req, rsp, opts...) 157 } 158 159 // set the namespace header if it has not been set (e.g. on a service to service request) 160 if _, ok := metadata.Get(ctx, "Micro-Namespace"); !ok { 161 ctx = metadata.Set(ctx, "Micro-Namespace", aa.Options().Issuer) 162 } 163 164 // check to see if we have a valid access token 165 aaOpts := aa.Options() 166 if aaOpts.Token != nil && !aaOpts.Token.Expired() { 167 ctx = metadata.Set(ctx, "Authorization", auth.BearerScheme+aaOpts.Token.AccessToken) 168 return a.Client.Call(ctx, req, rsp, opts...) 169 } 170 171 // call without an auth token 172 return a.Client.Call(ctx, req, rsp, opts...) 173 } 174 175 // AuthClient wraps requests with the auth header 176 func AuthClient(auth func() auth.Auth, c client.Client) client.Client { 177 return &authWrapper{c, auth} 178 } 179 180 // AuthHandler wraps a server handler to perform auth 181 func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { 182 return func(h server.HandlerFunc) server.HandlerFunc { 183 return func(ctx context.Context, req server.Request, rsp interface{}) error { 184 // get the auth.Auth interface 185 a := fn() 186 187 // Check for debug endpoints which should be excluded from auth 188 if strings.HasPrefix(req.Endpoint(), "Debug.") { 189 return h(ctx, req, rsp) 190 } 191 192 // Extract the token if present. Note: if noop is being used 193 // then the token can be blank without erroring 194 var account *auth.Account 195 if header, ok := metadata.Get(ctx, "Authorization"); ok { 196 // Ensure the correct scheme is being used 197 if !strings.HasPrefix(header, auth.BearerScheme) { 198 return errors.Unauthorized(req.Service(), "invalid authorization header. expected Bearer schema") 199 } 200 201 // Strip the prefix and inspect the resulting token 202 account, _ = a.Inspect(strings.TrimPrefix(header, auth.BearerScheme)) 203 } 204 205 // Extract the namespace header 206 ns, ok := metadata.Get(ctx, "Micro-Namespace") 207 if !ok { 208 ns = a.Options().Issuer 209 ctx = metadata.Set(ctx, "Micro-Namespace", ns) 210 } 211 212 // Check the issuer matches the services namespace. TODO: Stop allowing go.micro to access 213 // any namespace and instead check for the server issuer. 214 if account != nil && account.Issuer != ns && account.Issuer != "go.micro" { 215 return errors.Forbidden(req.Service(), "Account was not issued by %v", ns) 216 } 217 218 // construct the resource 219 res := &auth.Resource{ 220 Type: "service", 221 Name: req.Service(), 222 Endpoint: req.Endpoint(), 223 } 224 225 // Verify the caller has access to the resource 226 err := a.Verify(account, res, auth.VerifyContext(ctx)) 227 if err != nil && account != nil { 228 return errors.Forbidden(req.Service(), "Forbidden call made to %v:%v by %v", req.Service(), req.Endpoint(), account.ID) 229 } else if err != nil { 230 return errors.Unauthorized(req.Service(), "Unauthorized call made to %v:%v", req.Service(), req.Endpoint()) 231 } 232 233 // There is an account, set it in the context 234 if account != nil { 235 ctx = auth.ContextWithAccount(ctx, account) 236 } 237 238 // The user is authorised, allow the call 239 return h(ctx, req, rsp) 240 } 241 } 242 } 243 244 type cacheWrapper struct { 245 cacheFn func() *client.Cache 246 client.Client 247 } 248 249 // Call executes the request. If the CacheExpiry option was set, the response will be cached using 250 // a hash of the metadata and request as the key. 251 func (c *cacheWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 252 // parse the options 253 var options client.CallOptions 254 for _, o := range opts { 255 o(&options) 256 } 257 258 // if the client doesn't have a cacbe setup don't continue 259 cache := c.cacheFn() 260 if cache == nil { 261 return c.Client.Call(ctx, req, rsp, opts...) 262 } 263 264 // if the cache expiry is not set, execute the call without the cache 265 if options.CacheExpiry == 0 { 266 return c.Client.Call(ctx, req, rsp, opts...) 267 } 268 269 // if the response is nil don't call the cache since we can't assign the response 270 if rsp == nil { 271 return c.Client.Call(ctx, req, rsp, opts...) 272 } 273 274 // check to see if there is a response cached, if there is assign it 275 if r, ok := cache.Get(ctx, &req); ok { 276 val := reflect.ValueOf(rsp).Elem() 277 val.Set(reflect.ValueOf(r).Elem()) 278 return nil 279 } 280 281 // don't cache the result if there was an error 282 if err := c.Client.Call(ctx, req, rsp, opts...); err != nil { 283 return err 284 } 285 286 // set the result in the cache 287 cache.Set(ctx, &req, rsp, options.CacheExpiry) 288 return nil 289 } 290 291 // CacheClient wraps requests with the cache wrapper 292 func CacheClient(cacheFn func() *client.Cache, c client.Client) client.Client { 293 return &cacheWrapper{cacheFn, c} 294 } 295 296 type staticClient struct { 297 address string 298 client.Client 299 } 300 301 func (s *staticClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 302 return s.Client.Call(ctx, req, rsp, append(opts, client.WithAddress(s.address))...) 303 } 304 305 func (s *staticClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { 306 return s.Client.Stream(ctx, req, append(opts, client.WithAddress(s.address))...) 307 } 308 309 // StaticClient sets an address on every call 310 func StaticClient(address string, c client.Client) client.Client { 311 return &staticClient{address, c} 312 }