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  }