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  }