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 }