github.com/ergo-services/ergo@v1.999.224/gen/web.go (about)

     1  package gen
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"reflect"
     9  	"strconv"
    10  
    11  	"github.com/ergo-services/ergo/etf"
    12  	"github.com/ergo-services/ergo/lib"
    13  )
    14  
    15  type WebBehavior interface {
    16  	ServerBehavior
    17  	// mandatory method
    18  	InitWeb(process *WebProcess, args ...etf.Term) (WebOptions, error)
    19  
    20  	// optional methods
    21  	HandleWebCall(process *WebProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus)
    22  	HandleWebCast(process *WebProcess, message etf.Term) ServerStatus
    23  	HandleWebInfo(process *WebProcess, message etf.Term) ServerStatus
    24  }
    25  
    26  type WebStatus error
    27  
    28  var (
    29  	WebStatusOK   WebStatus // nil
    30  	WebStatusStop WebStatus = fmt.Errorf("stop")
    31  
    32  	// internals
    33  	defaultWebPort    = uint16(8080)
    34  	defaultWebTLSPort = uint16(8443)
    35  )
    36  
    37  type Web struct {
    38  	Server
    39  }
    40  
    41  type WebOptions struct {
    42  	Host    string
    43  	Port    uint16 // default port 8080, for TLS - 8443
    44  	TLS     *tls.Config
    45  	Handler http.Handler
    46  }
    47  
    48  type WebProcess struct {
    49  	ServerProcess
    50  	options  WebOptions
    51  	behavior WebBehavior
    52  	listener net.Listener
    53  }
    54  
    55  type defaultHandler struct{}
    56  
    57  func (dh *defaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    58  	w.WriteHeader(http.StatusNotFound)
    59  	fmt.Fprintf(w, "Handler is not initialized\n")
    60  }
    61  
    62  //
    63  // WebProcess API
    64  //
    65  
    66  func (wp *WebProcess) StartWebHandler(web WebHandlerBehavior, options WebHandlerOptions) http.Handler {
    67  	handler, err := web.initHandler(wp, web, options)
    68  	if err != nil {
    69  		name := reflect.ValueOf(web).Elem().Type().Name()
    70  		lib.Warning("[%s] can not initialaze WebHandler (%s): %s", wp.Self(), name, err)
    71  
    72  		return &defaultHandler{}
    73  	}
    74  	return handler
    75  }
    76  
    77  //
    78  // Server callbacks
    79  //
    80  
    81  func (web *Web) Init(process *ServerProcess, args ...etf.Term) error {
    82  
    83  	behavior, ok := process.Behavior().(WebBehavior)
    84  	if !ok {
    85  		return fmt.Errorf("Web: not a WebBehavior")
    86  	}
    87  
    88  	webProcess := &WebProcess{
    89  		ServerProcess: *process,
    90  		behavior:      behavior,
    91  	}
    92  	// do not inherit parent State
    93  	webProcess.State = nil
    94  
    95  	options, err := behavior.InitWeb(webProcess, args...)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	tlsEnabled := false
   101  	if options.TLS != nil {
   102  		if options.TLS.Certificates == nil && options.TLS.GetCertificate == nil {
   103  			return fmt.Errorf("TLS config has no certificates")
   104  		}
   105  		tlsEnabled = true
   106  	}
   107  
   108  	if options.Port == 0 {
   109  		if tlsEnabled {
   110  			options.Port = defaultWebTLSPort
   111  		} else {
   112  			options.Port = defaultWebPort
   113  		}
   114  	}
   115  
   116  	lc := net.ListenConfig{}
   117  	ctx := process.Context()
   118  	hostPort := net.JoinHostPort(options.Host, strconv.Itoa(int(options.Port)))
   119  	listener, err := lc.Listen(ctx, "tcp", hostPort)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	if tlsEnabled {
   125  		listener = tls.NewListener(listener, options.TLS)
   126  	}
   127  
   128  	httpServer := http.Server{
   129  		Handler: options.Handler,
   130  	}
   131  
   132  	// start acceptor
   133  	go func() {
   134  		err := httpServer.Serve(listener)
   135  		process.Exit(err.Error())
   136  	}()
   137  
   138  	// Golang's listener is weird. It takes the context in the Listen method
   139  	// but doesn't use it at all. HTTP server has the same issue.
   140  	// So making a little workaround to handle process context cancelation.
   141  	// Maybe one day they fix it.
   142  	go func() {
   143  		// this goroutine will be alive until the process context is canceled.
   144  		select {
   145  		case <-ctx.Done():
   146  			httpServer.Close()
   147  		}
   148  	}()
   149  
   150  	webProcess.options = options
   151  	webProcess.listener = listener
   152  	process.State = webProcess
   153  
   154  	return nil
   155  }
   156  
   157  // HandleCall
   158  func (web *Web) HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) {
   159  	webp := process.State.(*WebProcess)
   160  	return webp.behavior.HandleWebCall(webp, from, message)
   161  }
   162  
   163  // HandleDirect
   164  func (web *Web) HandleDirect(process *ServerProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) {
   165  	return nil, DirectStatusOK
   166  }
   167  
   168  // HandleCast
   169  func (web *Web) HandleCast(process *ServerProcess, message etf.Term) ServerStatus {
   170  	webp := process.State.(*WebProcess)
   171  	status := webp.behavior.HandleWebCast(webp, message)
   172  
   173  	switch status {
   174  	case WebStatusOK:
   175  		return ServerStatusOK
   176  	case WebStatusStop:
   177  		return ServerStatusStop
   178  	default:
   179  		return ServerStatus(status)
   180  	}
   181  }
   182  
   183  // HandleInfo
   184  func (web *Web) HandleInfo(process *ServerProcess, message etf.Term) ServerStatus {
   185  	webp := process.State.(*WebProcess)
   186  	status := webp.behavior.HandleWebInfo(webp, message)
   187  
   188  	switch status {
   189  	case WebStatusOK:
   190  		return ServerStatusOK
   191  	case WebStatusStop:
   192  		return ServerStatusStop
   193  	default:
   194  		return ServerStatus(status)
   195  	}
   196  }
   197  
   198  func (web *Web) Terminate(process *ServerProcess, reason string) {
   199  	webp := process.State.(*WebProcess)
   200  	webp.listener.Close()
   201  }
   202  
   203  //
   204  // default Web callbacks
   205  //
   206  
   207  // HandleWebCall
   208  func (web *Web) HandleWebCall(process *WebProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) {
   209  	lib.Warning("HandleWebCall: unhandled message (from %#v) %#v", from, message)
   210  	return etf.Atom("ok"), ServerStatusOK
   211  }
   212  
   213  // HandleWebCast
   214  func (web *Web) HandleWebCast(process *WebProcess, message etf.Term) ServerStatus {
   215  	lib.Warning("HandleWebCast: unhandled message %#v", message)
   216  	return ServerStatusOK
   217  }
   218  
   219  // HandleWebInfo
   220  func (web *Web) HandleWebInfo(process *WebProcess, message etf.Term) ServerStatus {
   221  	lib.Warning("HandleWebInfo: unhandled message %#v", message)
   222  	return ServerStatusOK
   223  }