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 }