github.com/ergo-services/ergo@v1.999.224/gen/web_handler.go (about) 1 package gen 2 3 import ( 4 "fmt" 5 "net/http" 6 "reflect" 7 "strconv" 8 "sync" 9 "sync/atomic" 10 "time" 11 "unsafe" 12 13 "github.com/ergo-services/ergo/etf" 14 "github.com/ergo-services/ergo/lib" 15 ) 16 17 var ( 18 WebHandlerStatusDone WebHandlerStatus = nil 19 WebHandlerStatusWait WebHandlerStatus = fmt.Errorf("wait") 20 21 defaultRequestQueueLength = 10 22 23 webMessageRequestPool = &sync.Pool{ 24 New: func() interface{} { 25 return &webMessageRequest{} 26 }, 27 } 28 ) 29 30 type WebHandlerStatus error 31 32 type WebHandlerBehavior interface { 33 ServerBehavior 34 35 // Mandatory callback 36 HandleRequest(process *WebHandlerProcess, request WebMessageRequest) WebHandlerStatus 37 38 // Optional callbacks 39 HandleWebHandlerCall(process *WebHandlerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) 40 HandleWebHandlerCast(process *WebHandlerProcess, message etf.Term) ServerStatus 41 HandleWebHandlerInfo(process *WebHandlerProcess, message etf.Term) ServerStatus 42 HandleWebHandlerTerminate(process *WebHandlerProcess, reason string, count int64) 43 44 // internal methods 45 initHandler(process Process, handler WebHandlerBehavior, options WebHandlerOptions) (http.Handler, error) 46 } 47 48 type WebHandler struct { 49 Server 50 51 parent Process 52 behavior WebHandlerBehavior 53 options WebHandlerOptions 54 pool []*Process 55 counter uint64 56 } 57 58 type poolItem struct { 59 process Process 60 } 61 62 type WebHandlerOptions struct { 63 // Timeout for web-requests. The default timeout is 5 seconds. It can also be 64 // overridden within HTTP requests using the header 'Request-Timeout' 65 RequestTimeout int 66 // RequestQueueLength defines how many parallel requests can be directed to this process. Default value is 10. 67 RequestQueueLength int 68 // NumHandlers defines how many handlers will be started. Default 1 69 NumHandlers int 70 // IdleTimeout defines how long (in seconds) keep the started handler alive with no requests. Zero value makes handler not stop. 71 IdleTimeout int 72 } 73 74 type WebHandlerProcess struct { 75 ServerProcess 76 behavior WebHandlerBehavior 77 lastRequest int64 78 counter int64 79 idleTimeout int 80 id int 81 } 82 83 type WebMessageRequest struct { 84 Ref etf.Ref 85 Request *http.Request 86 Response http.ResponseWriter 87 } 88 89 type webMessageRequest struct { 90 sync.Mutex 91 WebMessageRequest 92 requestState int // 0 - initial, 1 - canceled, 2 - handled 93 } 94 95 type optsWebHandler struct { 96 id int 97 idleTimeout int 98 } 99 100 type messageWebHandlerIdleCheck struct{} 101 102 func (wh *WebHandler) initHandler(parent Process, handler WebHandlerBehavior, options WebHandlerOptions) (http.Handler, error) { 103 if options.NumHandlers < 1 { 104 options.NumHandlers = 1 105 } 106 if options.RequestTimeout < 1 { 107 options.RequestTimeout = DefaultCallTimeout 108 } 109 110 if options.IdleTimeout < 0 { 111 options.IdleTimeout = 0 112 } 113 114 if options.RequestQueueLength < 1 { 115 options.RequestQueueLength = defaultRequestQueueLength 116 } 117 118 wh.parent = parent 119 wh.behavior = handler 120 wh.options = options 121 c := atomic.AddUint64(&wh.counter, 1) 122 if c > 1 { 123 return nil, fmt.Errorf("you can not use the same object more than once") 124 } 125 126 for i := 0; i < options.NumHandlers; i++ { 127 p := wh.startHandler(i, options.IdleTimeout) 128 if p == nil { 129 return nil, fmt.Errorf("can not initialize handlers") 130 } 131 wh.pool = append(wh.pool, &p) 132 } 133 return wh, nil 134 } 135 136 func (wh *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 137 var p Process 138 139 //w.WriteHeader(http.StatusOK) 140 //return 141 142 mr := webMessageRequestPool.Get().(*webMessageRequest) 143 mr.Request = r 144 mr.Response = w 145 mr.requestState = 0 146 147 timeout := wh.options.RequestTimeout 148 if t := r.Header.Get("Request-Timeout"); t != "" { 149 intT, err := strconv.Atoi(t) 150 if err == nil && intT > 0 { 151 timeout = intT 152 } 153 } 154 155 l := uint64(wh.options.NumHandlers) 156 // make round robin using the counter value 157 c := atomic.AddUint64(&wh.counter, 1) 158 159 // attempts 160 for a := uint64(0); a < l; a++ { 161 i := (c + a) % l 162 163 p = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&wh.pool[i])))) 164 165 respawned: 166 if r.Context().Err() != nil { 167 // canceled by the client 168 return 169 } 170 _, err := p.DirectWithTimeout(mr, timeout) 171 switch err { 172 case nil: 173 webMessageRequestPool.Put(mr) 174 return 175 176 case lib.ErrProcessTerminated: 177 mr.Lock() 178 if mr.requestState > 0 { 179 mr.Unlock() 180 return 181 } 182 mr.Unlock() 183 p = wh.startHandler(int(i), wh.options.IdleTimeout) 184 atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&wh.pool[i])), unsafe.Pointer(&p)) 185 goto respawned 186 187 case lib.ErrProcessBusy: 188 continue 189 190 case lib.ErrTimeout: 191 mr.Lock() 192 if mr.requestState == 2 { 193 // timeout happened during the handling request 194 mr.Unlock() 195 webMessageRequestPool.Put(mr) 196 return 197 } 198 mr.requestState = 1 // canceled 199 mr.Unlock() 200 w.WriteHeader(http.StatusGatewayTimeout) 201 return 202 203 default: 204 lib.Warning("WebHandler %s return error: %s", p.Self(), err) 205 mr.Lock() 206 if mr.requestState > 0 { 207 mr.Unlock() 208 return 209 } 210 mr.Unlock() 211 212 w.WriteHeader(http.StatusInternalServerError) // 500 213 return 214 } 215 } 216 217 // all handlers are busy 218 name := reflect.ValueOf(wh.behavior).Elem().Type().Name() 219 lib.Warning("too many requests for %s", name) 220 w.WriteHeader(http.StatusServiceUnavailable) // 503 221 webMessageRequestPool.Put(mr) 222 } 223 224 func (wh *WebHandler) startHandler(id int, idleTimeout int) Process { 225 opts := ProcessOptions{ 226 Context: wh.parent.Context(), 227 DirectboxSize: uint16(wh.options.RequestQueueLength), 228 } 229 230 optsHandler := optsWebHandler{id: id, idleTimeout: idleTimeout} 231 p, err := wh.parent.Spawn("", opts, wh.behavior, optsHandler) 232 if err != nil { 233 lib.Warning("can not start WebHandler: %s", err) 234 return nil 235 } 236 return p 237 } 238 239 func (wh *WebHandler) Init(process *ServerProcess, args ...etf.Term) error { 240 behavior, ok := process.Behavior().(WebHandlerBehavior) 241 if !ok { 242 return fmt.Errorf("Web: not a WebHandlerBehavior") 243 } 244 handlerProcess := &WebHandlerProcess{ 245 ServerProcess: *process, 246 behavior: behavior, 247 } 248 if len(args) == 0 { 249 return fmt.Errorf("Web: can not start with no args") 250 } 251 252 if a, ok := args[0].(optsWebHandler); ok { 253 handlerProcess.idleTimeout = a.idleTimeout 254 handlerProcess.id = a.id 255 } else { 256 return fmt.Errorf("Web: wrong args for the WebHandler") 257 } 258 259 // do not inherit parent State 260 handlerProcess.State = nil 261 process.State = handlerProcess 262 263 if handlerProcess.idleTimeout > 0 { 264 process.CastAfter(process.Self(), messageWebHandlerIdleCheck{}, 5*time.Second) 265 } 266 267 return nil 268 } 269 270 func (wh *WebHandler) HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) { 271 whp := process.State.(*WebHandlerProcess) 272 return whp.behavior.HandleWebHandlerCall(whp, from, message) 273 } 274 275 func (wh *WebHandler) HandleCast(process *ServerProcess, message etf.Term) ServerStatus { 276 whp := process.State.(*WebHandlerProcess) 277 switch message.(type) { 278 case messageWebHandlerIdleCheck: 279 if time.Now().Unix()-whp.lastRequest > int64(whp.idleTimeout) { 280 return ServerStatusStop 281 } 282 process.CastAfter(process.Self(), messageWebHandlerIdleCheck{}, 5*time.Second) 283 284 default: 285 return whp.behavior.HandleWebHandlerCast(whp, message) 286 } 287 return ServerStatusOK 288 } 289 290 func (wh *WebHandler) HandleInfo(process *ServerProcess, message etf.Term) ServerStatus { 291 whp := process.State.(*WebHandlerProcess) 292 return whp.behavior.HandleWebHandlerInfo(whp, message) 293 } 294 295 func (wh *WebHandler) HandleDirect(process *ServerProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) { 296 whp := process.State.(*WebHandlerProcess) 297 switch m := message.(type) { 298 case *webMessageRequest: 299 whp.lastRequest = time.Now().Unix() 300 whp.counter++ 301 m.Lock() 302 defer m.Unlock() 303 if m.requestState != 0 || m.Request.Context().Err() != nil { // canceled 304 return nil, DirectStatusOK 305 } 306 m.requestState = 2 // handled 307 m.Ref = ref 308 status := whp.behavior.HandleRequest(whp, m.WebMessageRequest) 309 switch status { 310 case WebHandlerStatusDone: 311 return nil, DirectStatusOK 312 313 case WebHandlerStatusWait: 314 return nil, DirectStatusIgnore 315 default: 316 return nil, status 317 } 318 } 319 return nil, DirectStatusOK 320 } 321 322 func (wh *WebHandler) Terminate(process *ServerProcess, reason string) { 323 whp := process.State.(*WebHandlerProcess) 324 whp.behavior.HandleWebHandlerTerminate(whp, reason, whp.counter) 325 } 326 327 // HandleWebHandlerCall 328 func (wh *WebHandler) HandleWebHandlerCall(process *WebHandlerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) { 329 lib.Warning("HandleWebHandlerCall: unhandled message (from %#v) %#v", from, message) 330 return etf.Atom("ok"), ServerStatusOK 331 } 332 333 // HandleWebHandlerCast 334 func (wh *WebHandler) HandleWebHandlerCast(process *WebHandlerProcess, message etf.Term) ServerStatus { 335 lib.Warning("HandleWebHandlerCast: unhandled message %#v", message) 336 return ServerStatusOK 337 } 338 339 // HandleWebHandlerInfo 340 func (wh *WebHandler) HandleWebHandlerInfo(process *WebHandlerProcess, message etf.Term) ServerStatus { 341 lib.Warning("HandleWebHandlerInfo: unhandled message %#v", message) 342 return ServerStatusOK 343 } 344 func (wh *WebHandler) HandleWebHandlerTerminate(process *WebHandlerProcess, reason string, count int64) { 345 return 346 } 347 348 // we should disable SetTrapExit for the WebHandlerProcess by overriding it. 349 func (whp *WebHandlerProcess) SetTrapExit(trap bool) { 350 lib.Warning("[%s] method 'SetTrapExit' is disabled for WebHandlerProcess", whp.Self()) 351 }