github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/services/request.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package services 19 20 import ( 21 "encoding/json" 22 "fmt" 23 "github.com/aacfactory/errors" 24 "github.com/aacfactory/fns/commons/bytex" 25 "github.com/aacfactory/fns/commons/objects" 26 "github.com/aacfactory/fns/commons/uid" 27 "github.com/aacfactory/fns/commons/versions" 28 "github.com/aacfactory/fns/context" 29 "github.com/cespare/xxhash/v2" 30 "github.com/valyala/bytebufferpool" 31 "strconv" 32 "sync" 33 ) 34 35 type Param interface { 36 objects.Object 37 } 38 39 func NewParam(src interface{}) Param { 40 return objects.New(src) 41 } 42 43 // ValueOfParam 44 // type of T must be struct value or slice, can not be ptr 45 func ValueOfParam[T any](param Param) (v T, err error) { 46 v, err = objects.Value[T](param) 47 if err != nil { 48 err = errors.Warning("fns: get value of param failed").WithCause(err) 49 return 50 } 51 return 52 } 53 54 // +-------------------------------------------------------------------------------------------------------------------+ 55 56 type RequestOption func(*RequestOptions) 57 58 func WithRequestId(id []byte) RequestOption { 59 return func(options *RequestOptions) { 60 options.header.requestId = id 61 } 62 } 63 64 func WithProcessId(id []byte) RequestOption { 65 return func(options *RequestOptions) { 66 options.header.processId = id 67 } 68 } 69 70 func WithEndpointId(id []byte) RequestOption { 71 return func(options *RequestOptions) { 72 options.header.endpointId = id 73 } 74 } 75 76 func WithToken(token []byte) RequestOption { 77 return func(options *RequestOptions) { 78 options.header.token = token 79 } 80 } 81 82 func WithDeviceId(id []byte) RequestOption { 83 return func(options *RequestOptions) { 84 options.header.deviceId = id 85 } 86 } 87 88 func WithDeviceIp(ip []byte) RequestOption { 89 return func(options *RequestOptions) { 90 options.header.deviceIp = ip 91 } 92 } 93 94 func WithInternalRequest() RequestOption { 95 return func(options *RequestOptions) { 96 options.header.internal = true 97 } 98 } 99 100 func WithRequestVersions(acceptedVersions versions.Intervals) RequestOption { 101 return func(options *RequestOptions) { 102 options.header.acceptedVersions = acceptedVersions 103 } 104 } 105 106 type RequestOptions struct { 107 header Header 108 } 109 110 // +-------------------------------------------------------------------------------------------------------------------+ 111 112 var ( 113 requestPool = sync.Pool{} 114 ) 115 116 type Request interface { 117 context.Context 118 Fn() (endpoint []byte, fn []byte) 119 Header() (header Header) 120 Param() (param Param) 121 } 122 123 func NewRequest(ctx context.Context, service []byte, fn []byte, param interface{}, options ...RequestOption) (v Request) { 124 opt := &RequestOptions{ 125 header: Header{}, 126 } 127 if options != nil && len(options) > 0 { 128 for _, option := range options { 129 option(opt) 130 } 131 } 132 if len(opt.header.processId) == 0 { 133 opt.header.processId = uid.Bytes() 134 } 135 parent, hasParent := TryLoadRequest(ctx) 136 if hasParent { 137 header := parent.Header() 138 if len(opt.header.requestId) == 0 && len(header.requestId) > 0 { 139 opt.header.requestId = header.requestId 140 } 141 if len(opt.header.deviceId) == 0 && len(header.deviceId) > 0 { 142 opt.header.deviceId = header.deviceId 143 } 144 if len(opt.header.deviceIp) == 0 && len(header.deviceIp) > 0 { 145 opt.header.deviceIp = header.deviceIp 146 } 147 if len(opt.header.token) == 0 && len(header.token) > 0 { 148 opt.header.token = header.token 149 } 150 if len(opt.header.acceptedVersions) == 0 && len(header.acceptedVersions) > 0 { 151 opt.header.acceptedVersions = header.acceptedVersions 152 } 153 opt.header.internal = true 154 } 155 r := new(request) 156 r.Context = context.Fork(ctx) 157 r.header = opt.header 158 r.service = service 159 r.fn = fn 160 r.param = NewParam(param) 161 v = r 162 return 163 } 164 165 func AcquireRequest(ctx context.Context, service []byte, fn []byte, param interface{}, options ...RequestOption) (v Request) { 166 opt := &RequestOptions{ 167 header: Header{}, 168 } 169 if options != nil && len(options) > 0 { 170 for _, option := range options { 171 option(opt) 172 } 173 } 174 if len(opt.header.processId) == 0 { 175 opt.header.processId = uid.Bytes() 176 } 177 parent, hasParent := TryLoadRequest(ctx) 178 if hasParent { 179 header := parent.Header() 180 if len(opt.header.requestId) == 0 && len(header.requestId) > 0 { 181 opt.header.requestId = header.requestId 182 } 183 if len(opt.header.deviceId) == 0 && len(header.deviceId) > 0 { 184 opt.header.deviceId = header.deviceId 185 } 186 if len(opt.header.deviceIp) == 0 && len(header.deviceIp) > 0 { 187 opt.header.deviceIp = header.deviceIp 188 } 189 if len(opt.header.token) == 0 && len(header.token) > 0 { 190 opt.header.token = header.token 191 } 192 if len(opt.header.acceptedVersions) == 0 && len(header.acceptedVersions) > 0 { 193 opt.header.acceptedVersions = header.acceptedVersions 194 } 195 opt.header.internal = true 196 } 197 var r *request 198 cached := requestPool.Get() 199 if cached == nil { 200 r = new(request) 201 } else { 202 r = cached.(*request) 203 } 204 r.Context = context.Acquire(ctx) 205 r.header = opt.header 206 r.service = service 207 r.fn = fn 208 r.param = NewParam(param) 209 v = r 210 return 211 } 212 213 func ReleaseRequest(r Request) { 214 req, ok := r.(*request) 215 if !ok { 216 return 217 } 218 context.Release(req.Context) 219 req.Context = nil 220 requestPool.Put(req) 221 } 222 223 type request struct { 224 context.Context 225 header Header 226 service []byte 227 fn []byte 228 param Param 229 } 230 231 func (r *request) Fn() (service []byte, fn []byte) { 232 service, fn = r.service, r.fn 233 return 234 } 235 236 func (r *request) Header() (header Header) { 237 header = r.header 238 return 239 } 240 241 func (r *request) Param() (param Param) { 242 param = r.param 243 return 244 } 245 246 // +-------------------------------------------------------------------------------------------------------------------+ 247 248 func TryLoadRequest(ctx context.Context) (r Request, ok bool) { 249 r, ok = ctx.(Request) 250 return 251 } 252 253 func LoadRequest(ctx context.Context) Request { 254 r, ok := ctx.(Request) 255 if ok { 256 return r 257 } 258 panic(fmt.Sprintf("%+v", errors.Warning("fns: can not convert context to request"))) 259 return r 260 } 261 262 // +-------------------------------------------------------------------------------------------------------------------+ 263 264 type HashRequestOptions struct { 265 withToken bool 266 withDeviceId bool 267 sumFn func(p []byte) uint64 268 } 269 270 type HashRequestOption func(options *HashRequestOptions) 271 272 func HashRequestWithToken() HashRequestOption { 273 return func(options *HashRequestOptions) { 274 options.withToken = true 275 } 276 } 277 278 func HashRequestWithDeviceId() HashRequestOption { 279 return func(options *HashRequestOptions) { 280 options.withDeviceId = true 281 } 282 } 283 284 func HashRequestBySumFn(fn func(p []byte) uint64) HashRequestOption { 285 return func(options *HashRequestOptions) { 286 options.sumFn = fn 287 } 288 } 289 290 var ( 291 requestHashContextKeyPrefix = []byte("@fns:context:services:requestHash:") 292 ) 293 294 func HashRequest(r Request, options ...HashRequestOption) (p []byte, err error) { 295 opt := HashRequestOptions{ 296 withToken: false, 297 withDeviceId: false, 298 sumFn: xxhash.Sum64, 299 } 300 for _, option := range options { 301 option(&opt) 302 } 303 service, fn := r.Fn() 304 var pp []byte 305 if r.Param().Valid() { 306 pp, err = json.Marshal(r.Param()) 307 if err != nil { 308 err = errors.Warning("fns: hash request failed").WithCause(err) 309 return 310 } 311 } 312 buf := bytebufferpool.Get() 313 _, _ = buf.Write(service) 314 _, _ = buf.Write(fn) 315 _, _ = buf.Write(r.Header().AcceptedVersions().Bytes()) 316 if opt.withToken { 317 token := r.Header().Token() 318 if len(token) == 0 { 319 bytebufferpool.Put(buf) 320 err = errors.Unauthorized("unauthorized").WithCause(errors.Warning("fns: hash request failed").WithCause(fmt.Errorf("fns: token is required"))) 321 return 322 } 323 _, _ = buf.Write(token) 324 } 325 if opt.withDeviceId { 326 deviceId := r.Header().DeviceId() 327 if len(deviceId) == 0 { 328 bytebufferpool.Put(buf) 329 err = errors.Warning("fns: hash request failed").WithCause(fmt.Errorf("fns: device id is required")) 330 return 331 } 332 _, _ = buf.Write(deviceId) 333 } 334 if len(pp) > 0 { 335 _, _ = buf.Write(pp) 336 } 337 b := buf.Bytes() 338 p = bytex.FromString(strconv.FormatUint(opt.sumFn(b), 16)) 339 bytebufferpool.Put(buf) 340 r.SetLocalValue(append(requestHashContextKeyPrefix, r.Header().RequestId()...), p) 341 return 342 } 343 344 func TryLoadRequestHash(ctx context.Context) (p []byte, has bool) { 345 r, ok := TryLoadRequest(ctx) 346 if !ok { 347 return 348 } 349 p, has = context.LocalValue[[]byte](ctx, append(requestHashContextKeyPrefix, r.Header().RequestId()...)) 350 return 351 }