knative.dev/func-go@v0.21.3/cloudevents/service.go (about) 1 // Package ce implements a Functions CloudEvent middleware for use by 2 // scaffolding which exposes a function as a network service which handles 3 // Cloud Events. 4 package cloudevents 5 6 import ( 7 "bufio" 8 "context" 9 "fmt" 10 "net" 11 "net/http" 12 "os" 13 "os/signal" 14 "runtime" 15 "strings" 16 "syscall" 17 "time" 18 19 cloudevents "github.com/cloudevents/sdk-go/v2" 20 "github.com/rs/zerolog/log" 21 ) 22 23 const ( 24 DefaultLogLevel = LogDebug 25 DefaultListenAddress = "127.0.0.1:8080" 26 ServerShutdownTimeout = 30 * time.Second 27 InstanceStopTimeout = 30 * time.Second 28 ) 29 30 // Start an intance using a new Service 31 // Note that for CloudEvent Handlers this effectively accepts ANY because 32 // the actual type of the handler function is determined later. 33 func Start(f any) error { 34 log.Debug().Msg("func runtime creating function instance") 35 return New(f).Start(context.Background()) 36 } 37 38 // Service exposes a Function Instance as a an HTTP service. 39 type Service struct { 40 http.Server 41 listener net.Listener 42 f any 43 stop chan error 44 } 45 46 // New Service which service the given instance. 47 func New(f any) *Service { 48 svc := &Service{ 49 f: f, 50 stop: make(chan error), 51 Server: http.Server{ 52 ReadTimeout: 30 * time.Second, 53 WriteTimeout: 30 * time.Second, 54 IdleTimeout: 30 * time.Second, 55 MaxHeaderBytes: 1 << 20, 56 ReadHeaderTimeout: 2 * time.Second, 57 }, 58 } 59 mux := http.NewServeMux() 60 mux.HandleFunc("/health/readiness", svc.Ready) 61 mux.HandleFunc("/health/liveness", svc.Alive) 62 mux.Handle("/", newCloudeventHandler(f)) // See implementation note 63 svc.Handler = mux 64 return svc 65 } 66 67 // Start serving 68 func (s *Service) Start(ctx context.Context) (err error) { 69 // Get the listen address 70 // TODO: Currently this is an env var for legacy reasons. Logic should 71 // be moved into the generated mainfiles, and this setting be an optional 72 // functional option WithListenAddress(os.Getenv("LISTEN_ADDRESS")) 73 addr := listenAddress() 74 log.Debug().Str("address", addr).Msg("function starting") 75 76 // Listen 77 if s.listener, err = net.Listen("tcp", addr); err != nil { 78 return 79 } 80 81 // Start 82 // Starts the function instance in a separate routine, sending any 83 // runtime errors on s.stop. 84 if err = s.startInstance(ctx); err != nil { 85 return 86 } 87 88 // Wait for signals 89 // Interrupts and Kill signals 90 // sending a message on the s.stop channel if either are received. 91 s.handleSignals() 92 93 go func() { 94 if err := s.Serve(s.listener); err != http.ErrServerClosed { 95 log.Error().Err(err).Msg("http server exited with unexpected error") 96 s.stop <- err 97 } 98 }() 99 100 log.Debug().Msg("waiting for stop signals or errors") 101 // Wait for either a context cancellation or a signal on the stop channel. 102 select { 103 case err = <-s.stop: 104 if err != nil { 105 log.Error().Err(err).Msg("function error") 106 } 107 case <-ctx.Done(): 108 log.Debug().Msg("function canceled") 109 } 110 return s.shutdown(err) 111 } 112 113 func listenAddress() string { 114 // If they are using the corret LISTEN_ADRESS, use this immediately 115 listenAddress := os.Getenv("LISTEN_ADDRESS") 116 if listenAddress != "" { 117 return listenAddress 118 } 119 120 // Legacy logic if ADDRESS or PORT provided 121 address := os.Getenv("ADDRESS") 122 port := os.Getenv("PORT") 123 if address != "" || port != "" { 124 if address != "" { 125 log.Warn().Msg("Environment variable ADDRESS is deprecated and support will be removed in future versions. Try rebuilding your Function with the latest version of func to use LISTEN_ADDRESS instead.") 126 } else { 127 address = "127.0.0.1" 128 } 129 if port != "" { 130 log.Warn().Msg("Environment variable PORT is deprecated and support will be removed in future version.s Try rebuilding your Function with the latest version of func to use LISTEN_ADDRESS instead.") 131 } else { 132 port = "8080" 133 } 134 return address + ":" + port 135 } 136 137 return DefaultListenAddress 138 } 139 140 // Addr returns the address upon which the service is listening if started; 141 // nil otherwise. 142 func (s *Service) Addr() net.Addr { 143 if s.listener == nil { 144 return nil 145 } 146 return s.listener.Addr() 147 } 148 149 // NOTE: no Handle on service because of the need to decorate the handler 150 // at runtime to adapt to the cloudevents sdk's expectation of a polymorphic 151 // handle method. So instead of a 'func (s *Service) Handle..' we have: 152 // 153 // TODO: test when f is not a pointer 154 // TODO: test when f.Handle does not have a pointer receiver 155 // TODO: test when f is an interface type 156 func newCloudeventHandler(f any) http.Handler { 157 var h any 158 if dh, ok := f.(DefaultHandler); ok { 159 // Static Functions use a struct to curry the reference 160 h = dh.Handler 161 } else { 162 // Instanced Functions implement one of the defined interfaces. 163 h = getReceiverFn(f) 164 } 165 166 protocol, err := cloudevents.NewHTTP() 167 panicOn(err) 168 ctx := context.Background() // ctx is not used by NewHTTPReceiveHandler 169 cloudeventReceiver, err := cloudevents.NewHTTPReceiveHandler(ctx, protocol, h) 170 panicOn(err) 171 return cloudeventReceiver 172 } 173 174 // Ready handles readiness checks. 175 func (s *Service) Ready(w http.ResponseWriter, r *http.Request) { 176 if i, ok := s.f.(ReadinessReporter); ok { 177 ready, err := i.Ready(r.Context()) 178 if err != nil { 179 message := "error checking readiness" 180 log.Debug().Err(err).Msg(message) 181 w.WriteHeader(500) 182 _, _ = w.Write([]byte(message + ". " + err.Error())) 183 return 184 } 185 if !ready { 186 message := "function not yet available" 187 log.Debug().Msg(message) 188 w.WriteHeader(503) 189 fmt.Fprintln(w, message) 190 return 191 } 192 } 193 fmt.Fprintf(w, "READY") 194 } 195 196 // Alive handles liveness checks. 197 func (s *Service) Alive(w http.ResponseWriter, r *http.Request) { 198 if i, ok := s.f.(LivenessReporter); ok { 199 alive, err := i.Alive(r.Context()) 200 if err != nil { 201 message := "error checking liveness" 202 log.Err(err).Msg(message) 203 w.WriteHeader(500) 204 _, _ = w.Write([]byte(message + ". " + err.Error())) 205 return 206 } 207 if !alive { 208 message := "function not ready" 209 log.Debug().Msg(message) 210 w.WriteHeader(503) 211 _, _ = w.Write([]byte(message)) 212 return 213 } 214 } 215 fmt.Fprintf(w, "ALIVE") 216 } 217 218 func (s *Service) startInstance(ctx context.Context) error { 219 if i, ok := s.f.(Starter); ok { 220 cfg, err := newCfg() 221 if err != nil { 222 return err 223 } 224 go func() { 225 if err := i.Start(ctx, cfg); err != nil { 226 s.stop <- err 227 } 228 }() 229 } else { 230 log.Debug().Msg("function does not implement Start. Skipping") 231 } 232 return nil 233 } 234 235 func (s *Service) handleSignals() { 236 sigs := make(chan os.Signal, 2) 237 signal.Notify(sigs) 238 go func() { 239 for { 240 sig := <-sigs 241 if sig == syscall.SIGINT || sig == syscall.SIGTERM { 242 log.Debug().Any("signal", sig).Msg("signal received") 243 s.stop <- nil 244 } else if runtime.GOOS == "linux" && sig == syscall.Signal(0x17) { 245 // Ignore SIGURG; signal 23 (0x17) 246 // See https://go.googlesource.com/proposal/+/master/design/24543-non-cooperative-preemption.md 247 } 248 } 249 }() 250 } 251 252 // readCfg returns a map representation of ./cfg 253 // Empty map is returned if ./cfg does not exist. 254 // Error is returned for invalid entries. 255 // keys and values are space-trimmed. 256 // Quotes are removed from values. 257 func readCfg() (map[string]string, error) { 258 cfg := map[string]string{} 259 260 f, err := os.Open("cfg") 261 if err != nil { 262 log.Debug().Msg("no static config") 263 return cfg, nil 264 } 265 defer f.Close() 266 267 scanner := bufio.NewScanner(f) 268 i := 0 269 for scanner.Scan() { 270 i++ 271 line := scanner.Text() 272 parts := strings.SplitN(line, "=", 2) 273 if len(parts) != 2 { 274 return cfg, fmt.Errorf("config line %v invalid: %v", i, line) 275 } 276 cfg[strings.TrimSpace(parts[0])] = strings.Trim(strings.TrimSpace(parts[1]), "\"") 277 } 278 return cfg, scanner.Err() 279 } 280 281 // newCfg creates a final map of config values built from the static 282 // values in `cfg` and all environment variables. 283 func newCfg() (cfg map[string]string, err error) { 284 if cfg, err = readCfg(); err != nil { 285 return 286 } 287 288 for _, e := range os.Environ() { 289 pair := strings.SplitN(e, "=", 2) 290 cfg[pair[0]] = pair[1] 291 } 292 return 293 } 294 295 // shutdown is invoked when the stop channel receives a message and attempts to 296 // gracefully cease execution. 297 // Passed in is the message received on the stop channel, wich is either an 298 // error in the case of a runtime error, or nil in the case of a context 299 // cancellation or sigint/sigkill. 300 func (s *Service) shutdown(sourceErr error) (err error) { 301 log.Debug().Msg("function stopping") 302 var runtimeErr, instanceErr error 303 304 // Start a graceful shutdown of the HTTP server 305 ctx, cancel := context.WithTimeout(context.Background(), ServerShutdownTimeout) 306 defer cancel() 307 runtimeErr = s.Shutdown(ctx) 308 309 // Start a graceful shutdown of the Function instance 310 if i, ok := s.f.(Stopper); ok { 311 ctx, cancel = context.WithTimeout(context.Background(), InstanceStopTimeout) 312 defer cancel() 313 instanceErr = i.Stop(ctx) 314 } 315 316 return collapseErrors("shutdown error", sourceErr, instanceErr, runtimeErr) 317 } 318 319 // collapseErrors returns the first non-nil error which it is passed, 320 // printing the rest to log with the given prefix. 321 func collapseErrors(msg string, ee ...error) (err error) { 322 for _, e := range ee { 323 if e != nil { 324 if err == nil { 325 err = e 326 } else { 327 log.Error().Err(e).Msg(msg) 328 } 329 } 330 } 331 return 332 } 333 334 // CE-specific helpers 335 func panicOn(err error) { 336 if err != nil { 337 panic(err) 338 } 339 }