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  }