github.com/clubpay/ronykit/kit@v0.14.4-0.20240515065620-d0dace45cbc7/ctx.go (about)

     1  package kit
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"net/http"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/clubpay/ronykit/kit/utils"
    11  )
    12  
    13  const (
    14  	abortIndex = math.MaxInt >> 1
    15  )
    16  
    17  type (
    18  	// ErrHandlerFunc is called when an error happens in internal layers.
    19  	// NOTICE: ctx could be nil, make sure you do nil-check before calling its methods.
    20  	ErrHandlerFunc = func(ctx *Context, err error)
    21  	// HandlerFunc is a function that will execute code in its context. If there is another handler
    22  	// set in the path, by calling ctx.Next you can move forward and then run the rest of the code in
    23  	// your handler.
    24  	HandlerFunc        = func(ctx *Context)
    25  	HandlerFuncChain   []HandlerFunc
    26  	LimitedHandlerFunc = func(ctx *LimitedContext)
    27  )
    28  
    29  type Context struct {
    30  	utils.SpinLock
    31  	ctx       context.Context //nolint:containedctx
    32  	sb        *southBridge
    33  	ls        *localStore
    34  	forwarded bool
    35  	rxt       time.Duration // remote execution timeout
    36  
    37  	serviceName []byte
    38  	contractID  []byte
    39  	route       []byte
    40  	rawData     []byte
    41  
    42  	kv         map[string]any
    43  	hdr        map[string]string
    44  	conn       Conn
    45  	in         *Envelope
    46  	modifiers  []ModifierFunc
    47  	err        error
    48  	statusCode int
    49  
    50  	handlers     HandlerFuncChain
    51  	handlerIndex int
    52  }
    53  
    54  func newContext(ls *localStore) *Context {
    55  	return &Context{
    56  		ls:         ls,
    57  		kv:         make(map[string]any, 4),
    58  		hdr:        make(map[string]string, 4),
    59  		statusCode: http.StatusOK,
    60  		ctx:        context.Background(),
    61  	}
    62  }
    63  
    64  // ExecuteArg is used by bundle developers, if they want to build a
    65  // Gateway or Cluster to be used with EdgeServer. If you are the user of
    66  // the framework, you don't need this.
    67  // In general ExecuteArg carrys information about the Context that is running,
    68  // for example it identifies that which Contract from which Service is this Context for.
    69  // Route identifies which RouteSelector was this request comes from.
    70  type ExecuteArg struct {
    71  	ServiceName string
    72  	ContractID  string
    73  	Route       string
    74  }
    75  
    76  // execute the Context with the provided ExecuteArg.
    77  func (ctx *Context) execute(arg ExecuteArg, c Contract) {
    78  	ctx.
    79  		setRoute(arg.Route).
    80  		setServiceName(arg.ServiceName).
    81  		setContractID(arg.ContractID).
    82  		AddModifier(c.Modifiers()...)
    83  
    84  	ctx.handlers = append(ctx.handlers, c.Handlers()...)
    85  	ctx.Next()
    86  }
    87  
    88  type executeRemoteArg struct {
    89  	Target      string
    90  	In          *envelopeCarrier
    91  	OutCallback func(carrier *envelopeCarrier)
    92  }
    93  
    94  func (ctx *Context) executeRemote(arg executeRemoteArg) error {
    95  	if ctx.sb == nil {
    96  		return ErrSouthBridgeDisabled
    97  	}
    98  
    99  	ch, err := ctx.sb.sendMessage(
   100  		arg.In.SessionID,
   101  		arg.Target,
   102  		arg.In.ToJSON(),
   103  	)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	var (
   109  		cancelFn context.CancelFunc
   110  		rxCtx    = context.Background()
   111  	)
   112  
   113  	if ctx.rxt > 0 {
   114  		rxCtx, cancelFn = context.WithTimeout(rxCtx, ctx.rxt)
   115  		defer cancelFn()
   116  	}
   117  LOOP:
   118  	for {
   119  		select {
   120  		case <-rxCtx.Done():
   121  			return rxCtx.Err()
   122  		case <-ctx.ctx.Done():
   123  			return ctx.ctx.Err()
   124  		case c, ok := <-ch:
   125  			if !ok {
   126  				break LOOP
   127  			}
   128  			arg.OutCallback(c)
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // Next sets the next handler which will be called after the current handler.
   136  /*
   137  	Here's a brief explanation of the Next() method:
   138  
   139  	1. It increments the handlerIndex of the Context by 1.
   140  	2. It enters a for loop that runs as long as the handlerIndex is less than or equal to the number of
   141  			registered handlers in the Context.
   142  	3. Inside the loop, it calls the next registered handler with the current instance of Context.
   143  	4. After the execution of the handler, it increments the handlerIndex by 1, and the loop continues until all remaining
   144  			handlers are called.
   145  
   146  	This method is useful in situations where you may have a set of middlewares to be executed in sequence,
   147  	and you want to control the order in which they're called. By calling Next() in a middleware function,
   148  	you allow the processing flow to continue and pass control to the subsequent middleware functions
   149  	in the chain.
   150  */
   151  
   152  func (ctx *Context) Next() {
   153  	ctx.handlerIndex++
   154  	for ctx.handlerIndex <= len(ctx.handlers) {
   155  		ctx.handlers[ctx.handlerIndex-1](ctx)
   156  		ctx.handlerIndex++
   157  	}
   158  }
   159  
   160  // StopExecution stops the execution of the next handlers.
   161  // When you call this in your handler, any other middleware that is not executed yet
   162  // will be skipped over.
   163  func (ctx *Context) StopExecution() {
   164  	ctx.handlerIndex = abortIndex
   165  }
   166  
   167  // AddModifier adds one or more modifiers to the context which will be executed on each outgoing
   168  // Envelope before writing it to the wire.
   169  func (ctx *Context) AddModifier(modifiers ...ModifierFunc) {
   170  	ctx.modifiers = append(ctx.modifiers, modifiers...)
   171  }
   172  
   173  func (ctx *Context) SetUserContext(userCtx context.Context) {
   174  	ctx.ctx = userCtx
   175  }
   176  
   177  // Context returns a context.Background which can be used a reference context for
   178  // other context-aware function calls.
   179  func (ctx *Context) Context() context.Context {
   180  	return ctx.ctx
   181  }
   182  
   183  // SetStatusCode set the connection status. It **ONLY** works if the underlying connection
   184  // is a RESTConn connection.
   185  func (ctx *Context) SetStatusCode(code int) {
   186  	ctx.statusCode = code
   187  
   188  	rc, ok := ctx.Conn().(RESTConn)
   189  	if !ok {
   190  		return
   191  	}
   192  
   193  	rc.SetStatusCode(code)
   194  }
   195  
   196  func (ctx *Context) GetStatusCode() int {
   197  	return ctx.statusCode
   198  }
   199  
   200  func (ctx *Context) GetStatusText() string {
   201  	return http.StatusText(ctx.statusCode)
   202  }
   203  
   204  // Conn returns the underlying connection
   205  func (ctx *Context) Conn() Conn {
   206  	return ctx.conn
   207  }
   208  
   209  // RESTConn returns the underlying REST connection. It panics if the underlying connection
   210  // does not implement RESTConn interface. If you want to be safe when calling this method
   211  // you can use IsREST method:
   212  // Example:
   213  //
   214  //	 if ctx.IsREST() {
   215  //			conn := ctx.RESTConn()
   216  //	 }
   217  func (ctx *Context) RESTConn() RESTConn {
   218  	return ctx.conn.(RESTConn) //nolint:forcetypeassert
   219  }
   220  
   221  func (ctx *Context) IsREST() bool {
   222  	_, ok := ctx.conn.(RESTConn)
   223  
   224  	return ok
   225  }
   226  
   227  // PresetHdr sets the common header key-value pairs, so in Out method we do not need to
   228  // repeatedly set those. This method is useful for some cases if we need to update the
   229  // header in some middleware before the actual response is prepared.
   230  // If you only want to set the header for an envelope, you can use Envelope.SetHdr method instead.
   231  func (ctx *Context) PresetHdr(k, v string) {
   232  	ctx.hdr[k] = v
   233  }
   234  
   235  // PresetHdrMap sets the common header key-value pairs so in Out method we do not need to
   236  // repeatedly set those. Please refer to PresetHdr for more details
   237  func (ctx *Context) PresetHdrMap(hdr map[string]string) {
   238  	for k, v := range hdr {
   239  		ctx.hdr[k] = v
   240  	}
   241  }
   242  
   243  // In returns the incoming Envelope which received from the connection.
   244  // You **MUST NOT** call Send method of this Envelope.
   245  // If you want to return a message/envelope to connection, use Out or OutTo methods
   246  // of the Context
   247  func (ctx *Context) In() *Envelope {
   248  	return ctx.in
   249  }
   250  
   251  // InputRawData returns the raw input data from the connection. This slice is not valid
   252  // after this Context lifetime. If you need to use it after the Context lifetime,
   253  // you need to copy it.
   254  // You should not use this method in your code, ONLY if you need it for debugging.
   255  func (ctx *Context) InputRawData() RawMessage {
   256  	return ctx.rawData
   257  }
   258  
   259  // Out generate a new Envelope which could be used to send data to the connection.
   260  func (ctx *Context) Out() *Envelope {
   261  	return ctx.OutTo(ctx.conn)
   262  }
   263  
   264  // OutTo is similar to Out except that it lets you send your envelope to another connection.
   265  // This is useful for scenarios where you want to send a cross-client message. For example,
   266  // in a fictional chat server, you want to pass a message from client A to client B.
   267  func (ctx *Context) OutTo(c Conn) *Envelope {
   268  	return newEnvelope(ctx, c, true)
   269  }
   270  
   271  // Error is useful for some kind of errors that you are not going to return it to the connection,
   272  // or you want to use its side effect for logging, monitoring, etc. This will call your ErrHandlerFunc.
   273  // The boolean result indicates if 'err' was an actual error.
   274  func (ctx *Context) Error(err error) bool {
   275  	if err != nil {
   276  		ctx.err = err
   277  
   278  		return true
   279  	}
   280  
   281  	return false
   282  }
   283  
   284  // HasError returns true if there is an error set by calling Error method.
   285  func (ctx *Context) HasError() bool {
   286  	return ctx.err != nil
   287  }
   288  
   289  // Limited returns a LimitedContext. This is useful when you do not want to give all
   290  // capabilities of the Context to some other function/method.
   291  func (ctx *Context) Limited() *LimitedContext {
   292  	return newLimitedContext(ctx)
   293  }
   294  
   295  func (ctx *Context) reset() {
   296  	for k := range ctx.kv {
   297  		delete(ctx.kv, k)
   298  	}
   299  	for k := range ctx.hdr {
   300  		delete(ctx.hdr, k)
   301  	}
   302  
   303  	ctx.forwarded = false
   304  	ctx.serviceName = ctx.serviceName[:0]
   305  	ctx.contractID = ctx.contractID[:0]
   306  	ctx.route = ctx.route[:0]
   307  
   308  	ctx.in.release()
   309  	ctx.statusCode = http.StatusOK
   310  	ctx.handlerIndex = 0
   311  	ctx.handlers = ctx.handlers[:0]
   312  	ctx.modifiers = ctx.modifiers[:0]
   313  	ctx.ctx = context.Background()
   314  }
   315  
   316  type ctxPool struct {
   317  	sync.Pool
   318  	ls *localStore
   319  	th HandlerFunc // trace handler
   320  }
   321  
   322  func (p *ctxPool) acquireCtx(c Conn) *Context {
   323  	ctx, ok := p.Pool.Get().(*Context)
   324  	if !ok {
   325  		ctx = newContext(p.ls)
   326  	}
   327  
   328  	ctx.conn = c
   329  	ctx.in = newEnvelope(ctx, c, false)
   330  	if p.th != nil {
   331  		ctx.handlers = append(ctx.handlers, p.th)
   332  	}
   333  
   334  	return ctx
   335  }
   336  
   337  func (p *ctxPool) releaseCtx(ctx *Context) {
   338  	ctx.reset()
   339  	p.Pool.Put(ctx)
   340  }