From 7dce12f43f08246af5c92e50dc58d10c6cc1f082 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sat, 14 Mar 2026 23:06:25 -0400 Subject: [PATCH 01/10] feat: Add HTTPs server support. Signed-off-by: Shuchu Han --- go/main.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/go/main.go b/go/main.go index f49a27efa46..eb801c5b943 100644 --- a/go/main.go +++ b/go/main.go @@ -47,6 +47,10 @@ func (s *RealServerStarter) StartHttpServer(fs *feast.FeatureStore, host string, return StartHttpServer(fs, host, port, metricsPort, writeLoggedFeaturesCallback, loggingOpts) } +func (s *RealServerStarter) StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { + return StartHttpsServer(fs, host, port, metricsPort, certFile, keyFile, writeLoggedFeaturesCallback, loggingOpts) +} + func (s *RealServerStarter) StartGrpcServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { return StartGrpcServer(fs, host, port, metricsPort, writeLoggedFeaturesCallback, loggingOpts) } @@ -58,6 +62,8 @@ func main() { port := 8080 metricsPort := 9090 server := RealServerStarter{} + certFile := "" + keyFile := "" // Current Directory repoPath, err := os.Getwd() if err != nil { @@ -70,6 +76,8 @@ func main() { flag.StringVar(&host, "host", host, "Specify a host for the server") flag.IntVar(&port, "port", port, "Specify a port for the server") flag.IntVar(&metricsPort, "metrics-port", metricsPort, "Specify a port for the metrics server") + flag.StringVar(&certFile, "tls-cert-file", "", "Path to the TLS certificate file") + flag.StringVar(&keyFile, "tls-key-file", "", "Path to the TLS key file" ) flag.Parse() // Initialize tracer @@ -119,8 +127,10 @@ func main() { err = server.StartHttpServer(fs, host, port, metricsPort, nil, loggingOptions) } else if serverType == "grpc" { err = server.StartGrpcServer(fs, host, port, metricsPort, nil, loggingOptions) + } else if serverType == "https" { + err = server.StartHttpsServer(fs, host, port, metricsPort, certFile, keyFile, nil, loggingOptions) } else { - fmt.Println("Unknown server type. Please specify 'http' or 'grpc'.") + fmt.Println("Unknown server type. Please specify 'http' or 'grpc' or 'https'.") } if err != nil { @@ -285,6 +295,95 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort return err } +// StartHttpsServer starts HTTP server with TLS. Requires TLS_CERT_FILE and TLS_KEY_FILE env vars. +func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { + if certFile == "" || keyFile == "" { + return fmt.Errorf("TLS_CERT_FILE and TLS_KEY_FILE must be set") + } + + loggingService, err := constructLoggingService(fs, writeLoggedFeaturesCallback, loggingOpts) + if err != nil { + return err + } + + // Try to obtain an http.Handler from the concrete server if possible. + ser := server.NewHttpServer(fs, loggingService) + + // Start metrics server (same as HTTP) + metricsServer := &http.Server{Addr: fmt.Sprintf(":%d", metricsPort)} + go func() { + log.Info().Msgf("Starting metrics server on port %d", metricsPort) + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + metricsServer.Handler = mux + if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error().Err(err).Msg("Failed to start metrics server") + } + }() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + + var wg sync.WaitGroup + wg.Add(1) + serverExited := make(chan struct{}) + go func() { + defer wg.Done() + select { + case <-stop: + log.Info().Msg("Stopping the HTTPS server...") + // Try to stop underlying server if it exposes Stop() + if stopper, ok := interface{}(ser).(interface{ Stop() error }); ok { + if err := stopper.Stop(); err != nil { + log.Error().Err(err).Msg("Error when stopping the HTTPS server") + } + } + if err := metricsServer.Shutdown(context.Background()); err != nil { + log.Error().Err(err).Msg("Error stopping metrics server") + } + if loggingService != nil { + loggingService.Stop() + } + log.Info().Msg("HTTPS server terminated") + case <-serverExited: + metricsServer.Shutdown(context.Background()) + if loggingService != nil { + loggingService.Stop() + } + } + }() + + // If the concrete server exposes a Handler, use it with a tls-enabled http.Server. + if hProvider, ok := interface{}(ser).(interface{ Handler() http.Handler }); ok { + handler := hProvider.Handler() + srv := &http.Server{Addr: fmt.Sprintf("%s:%d", host, port), Handler: handler} + go func() { + log.Info().Msgf("Starting HTTPS server on host %s, port %d", host, port) + if err := srv.ListenAndServeTLS(certFile, keyFile); err != nil && err != http.ErrServerClosed { + log.Error().Err(err).Msg("HTTPS server failed") + } + }() + close(serverExited) + wg.Wait() + return nil + } + + // If concrete server supports ServeTLS(host,port,cert,key), call it. + if tlsServ, ok := interface{}(ser).(interface { + ServeTLS(string, int, string, string) error + }); ok { + err := tlsServ.ServeTLS(host, port, certFile, keyFile) + close(serverExited) + wg.Wait() + return err + } + + // Fallback: cannot enable TLS for this server implementation. + close(serverExited) + wg.Wait() + return fmt.Errorf("HTTPS not supported by underlying HTTP server implementation") +} + func OTELTracingEnabled() bool { return strings.ToLower(os.Getenv("ENABLE_OTEL_TRACING")) == "true" } From 064f6128e79d034737eb41d125ad38ece5e45097 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sun, 5 Apr 2026 19:50:30 -0400 Subject: [PATCH 02/10] feat: Add serverTSL() method. Signed-off-by: Shuchu Han --- go/internal/feast/server/http_server.go | 14 +++ go/main.go | 161 +++++++++++------------- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/go/internal/feast/server/http_server.go b/go/internal/feast/server/http_server.go index adfd40110e7..a96a23a4c62 100644 --- a/go/internal/feast/server/http_server.go +++ b/go/internal/feast/server/http_server.go @@ -396,6 +396,20 @@ func (s *httpServer) Serve(host string, port int) error { return err } +func (s *httpServer) ServeTLS(host string, port int, certFile string, keyFile string) error { + mux := http.NewServeMux() + mux.Handle("/get-online-features", metricsMiddleware(recoverMiddleware(http.HandlerFunc(s.getOnlineFeatures)))) + mux.Handle("/health", metricsMiddleware(http.HandlerFunc(healthCheckHandler))) + s.server = &http.Server{Addr: fmt.Sprintf("%s:%d", host, port), Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 15 * time.Second} + err := s.server.ListenAndServeTLS(certFile, keyFile) + // Don't return the error if it's caused by graceful shutdown using Stop() + if err == http.ErrServerClosed { + return nil + } + log.Fatal().Stack().Err(err).Msg("Failed to start HTTPS server") + return err +} + func healthCheckHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Healthy") diff --git a/go/main.go b/go/main.go index eb801c5b943..b86eccc3e21 100644 --- a/go/main.go +++ b/go/main.go @@ -11,6 +11,7 @@ import ( "strings" "sync" "syscall" + "time" "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/internal/feast/registry" @@ -70,14 +71,14 @@ func main() { log.Error().Stack().Err(err).Msg("Failed to get current directory") } - flag.StringVar(&serverType, "type", serverType, "Specify the server type (http or grpc)") + flag.StringVar(&serverType, "type", serverType, "Specify the server type (http, https or grpc)") flag.StringVar(&repoPath, "chdir", repoPath, "Repository path where feature store yaml file is stored") flag.StringVar(&host, "host", host, "Specify a host for the server") flag.IntVar(&port, "port", port, "Specify a port for the server") flag.IntVar(&metricsPort, "metrics-port", metricsPort, "Specify a port for the metrics server") flag.StringVar(&certFile, "tls-cert-file", "", "Path to the TLS certificate file") - flag.StringVar(&keyFile, "tls-key-file", "", "Path to the TLS key file" ) + flag.StringVar(&keyFile, "tls-key-file", "", "Path to the TLS key file") flag.Parse() // Initialize tracer @@ -244,13 +245,16 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort } ser := server.NewHttpServer(fs, loggingService) log.Info().Msgf("Starting a HTTP server on host %s, port %d", host, port) + // Start metrics server - metricsServer := &http.Server{Addr: fmt.Sprintf(":%d", metricsPort)} + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + metricsServer := &http.Server{ + Addr: fmt.Sprintf(":%d", metricsPort), + Handler: mux, + } go func() { log.Info().Msgf("Starting metrics server on port %d", metricsPort) - mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.Handler()) - metricsServer.Handler = mux if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error().Err(err).Msg("Failed to start metrics server") } @@ -258,6 +262,7 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(stop) var wg sync.WaitGroup wg.Add(1) @@ -273,7 +278,9 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort log.Error().Err(err).Msg("Error when stopping the HTTP server") } log.Info().Msg("Stopping metrics server...") - if err := metricsServer.Shutdown(context.Background()); err != nil { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := metricsServer.Shutdown(ctx); err != nil { log.Error().Err(err).Msg("Error stopping metrics server") } if loggingService != nil { @@ -295,6 +302,44 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort return err } +func OTELTracingEnabled() bool { + return strings.ToLower(os.Getenv("ENABLE_OTEL_TRACING")) == "true" +} + +func newExporter(ctx context.Context) (*otlptrace.Exporter, error) { + exp, err := otlptracehttp.New(ctx, + otlptracehttp.WithInsecure()) + if err != nil { + return nil, err + } + return exp, nil +} + +func newTracerProvider(exp sdktrace.SpanExporter) (*sdktrace.TracerProvider, error) { + serviceName := os.Getenv("OTEL_SERVICE_NAME") + if serviceName == "" { + serviceName = "FeastGoFeatureServer" + } + r, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(serviceName), + ), + ) + + if err != nil { + return nil, err + } + + return sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithResource(r), + ), nil +} + + + // StartHttpsServer starts HTTP server with TLS. Requires TLS_CERT_FILE and TLS_KEY_FILE env vars. func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { if certFile == "" || keyFile == "" { @@ -305,17 +350,18 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort if err != nil { return err } - - // Try to obtain an http.Handler from the concrete server if possible. ser := server.NewHttpServer(fs, loggingService) + log.Info().Msgf("Starting a HTTPS server on host %s, port %d", host, port) - // Start metrics server (same as HTTP) - metricsServer := &http.Server{Addr: fmt.Sprintf(":%d", metricsPort)} + // Start metrics server + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + metricsServer := &http.Server{ + Addr: fmt.Sprintf(":%d", metricsPort), + Handler: mux, + } go func() { log.Info().Msgf("Starting metrics server on port %d", metricsPort) - mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.Handler()) - metricsServer.Handler = mux if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error().Err(err).Msg("Failed to start metrics server") } @@ -323,6 +369,7 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(stop) var wg sync.WaitGroup wg.Add(1) @@ -331,21 +378,24 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort defer wg.Done() select { case <-stop: - log.Info().Msg("Stopping the HTTPS server...") - // Try to stop underlying server if it exposes Stop() - if stopper, ok := interface{}(ser).(interface{ Stop() error }); ok { - if err := stopper.Stop(); err != nil { - log.Error().Err(err).Msg("Error when stopping the HTTPS server") - } + // Received SIGINT/SIGTERM. Perform graceful shutdown. + log.Info().Msg("Stopping the HTTP server...") + err := ser.Stop() + if err != nil { + log.Error().Err(err).Msg("Error when stopping the HTTP server") } - if err := metricsServer.Shutdown(context.Background()); err != nil { + log.Info().Msg("Stopping metrics server...") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := metricsServer.Shutdown(ctx); err != nil { log.Error().Err(err).Msg("Error stopping metrics server") } if loggingService != nil { loggingService.Stop() } - log.Info().Msg("HTTPS server terminated") + log.Info().Msg("HTTP server terminated") case <-serverExited: + // Server exited (e.g. startup error), ensure metrics server is stopped metricsServer.Shutdown(context.Background()) if loggingService != nil { loggingService.Stop() @@ -353,69 +403,8 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort } }() - // If the concrete server exposes a Handler, use it with a tls-enabled http.Server. - if hProvider, ok := interface{}(ser).(interface{ Handler() http.Handler }); ok { - handler := hProvider.Handler() - srv := &http.Server{Addr: fmt.Sprintf("%s:%d", host, port), Handler: handler} - go func() { - log.Info().Msgf("Starting HTTPS server on host %s, port %d", host, port) - if err := srv.ListenAndServeTLS(certFile, keyFile); err != nil && err != http.ErrServerClosed { - log.Error().Err(err).Msg("HTTPS server failed") - } - }() - close(serverExited) - wg.Wait() - return nil - } - - // If concrete server supports ServeTLS(host,port,cert,key), call it. - if tlsServ, ok := interface{}(ser).(interface { - ServeTLS(string, int, string, string) error - }); ok { - err := tlsServ.ServeTLS(host, port, certFile, keyFile) - close(serverExited) - wg.Wait() - return err - } - - // Fallback: cannot enable TLS for this server implementation. + err = ser.ServeTLS(host, port, certFile, keyFile) close(serverExited) wg.Wait() - return fmt.Errorf("HTTPS not supported by underlying HTTP server implementation") -} - -func OTELTracingEnabled() bool { - return strings.ToLower(os.Getenv("ENABLE_OTEL_TRACING")) == "true" -} - -func newExporter(ctx context.Context) (*otlptrace.Exporter, error) { - exp, err := otlptracehttp.New(ctx, - otlptracehttp.WithInsecure()) - if err != nil { - return nil, err - } - return exp, nil -} - -func newTracerProvider(exp sdktrace.SpanExporter) (*sdktrace.TracerProvider, error) { - serviceName := os.Getenv("OTEL_SERVICE_NAME") - if serviceName == "" { - serviceName = "FeastGoFeatureServer" - } - r, err := resource.Merge( - resource.Default(), - resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceName(serviceName), - ), - ) - - if err != nil { - return nil, err - } - - return sdktrace.NewTracerProvider( - sdktrace.WithBatcher(exp), - sdktrace.WithResource(r), - ), nil -} + return err +} \ No newline at end of file From 5d64fcbfaf7e5689372b6c25ed4bbdca9b7ce963 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sun, 5 Apr 2026 20:02:01 -0400 Subject: [PATCH 03/10] fix: Lint the Go code. Signed-off-by: Shuchu Han --- go/internal/feast/metrics/metrics.go | 1 - go/internal/feast/onlinestore/postgresonlinestore.go | 2 +- go/main.go | 8 +++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/go/internal/feast/metrics/metrics.go b/go/internal/feast/metrics/metrics.go index 804eef6fa1b..d4783f257b7 100644 --- a/go/internal/feast/metrics/metrics.go +++ b/go/internal/feast/metrics/metrics.go @@ -30,7 +30,6 @@ var ( TimeHistogramType = reflect.TypeOf((*TimeHistogram)(nil)).Elem() ) - func RegisterTimeHistogram(name, help, namespace string, labelNames []string, tag reflect.StructTag) (func(prometheus.Labels) interface{}, prometheus.Collector, error) { f, collector, err := prometheusvanilla.BuildHistogram(name, help, namespace, labelNames, tag) if err != nil { diff --git a/go/internal/feast/onlinestore/postgresonlinestore.go b/go/internal/feast/onlinestore/postgresonlinestore.go index 4813f341db7..4077a9e06fa 100644 --- a/go/internal/feast/onlinestore/postgresonlinestore.go +++ b/go/internal/feast/onlinestore/postgresonlinestore.go @@ -194,4 +194,4 @@ func buildPostgresConnString(config map[string]interface{}) string { } return connURL.String() -} \ No newline at end of file +} diff --git a/go/main.go b/go/main.go index b86eccc3e21..a0e72720332 100644 --- a/go/main.go +++ b/go/main.go @@ -251,7 +251,7 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort mux.Handle("/metrics", promhttp.Handler()) metricsServer := &http.Server{ Addr: fmt.Sprintf(":%d", metricsPort), - Handler: mux, + Handler: mux, } go func() { log.Info().Msgf("Starting metrics server on port %d", metricsPort) @@ -338,8 +338,6 @@ func newTracerProvider(exp sdktrace.SpanExporter) (*sdktrace.TracerProvider, err ), nil } - - // StartHttpsServer starts HTTP server with TLS. Requires TLS_CERT_FILE and TLS_KEY_FILE env vars. func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { if certFile == "" || keyFile == "" { @@ -358,7 +356,7 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort mux.Handle("/metrics", promhttp.Handler()) metricsServer := &http.Server{ Addr: fmt.Sprintf(":%d", metricsPort), - Handler: mux, + Handler: mux, } go func() { log.Info().Msgf("Starting metrics server on port %d", metricsPort) @@ -407,4 +405,4 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort close(serverExited) wg.Wait() return err -} \ No newline at end of file +} From ed195739894c56c6d3446d84a4da9676247b9607 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sun, 5 Apr 2026 20:36:40 -0400 Subject: [PATCH 04/10] feat: Add TLS configuration. Signed-off-by: Shuchu Han --- go/internal/feast/server/http_server.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/go/internal/feast/server/http_server.go b/go/internal/feast/server/http_server.go index a96a23a4c62..2f60444c849 100644 --- a/go/internal/feast/server/http_server.go +++ b/go/internal/feast/server/http_server.go @@ -8,6 +8,7 @@ import ( "runtime" "strconv" "time" + "crypto/tls" "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/internal/feast/model" @@ -400,7 +401,24 @@ func (s *httpServer) ServeTLS(host string, port int, certFile string, keyFile st mux := http.NewServeMux() mux.Handle("/get-online-features", metricsMiddleware(recoverMiddleware(http.HandlerFunc(s.getOnlineFeatures)))) mux.Handle("/health", metricsMiddleware(http.HandlerFunc(healthCheckHandler))) - s.server = &http.Server{Addr: fmt.Sprintf("%s:%d", host, port), Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 15 * time.Second} + s.server = &http.Server{ + Addr: fmt.Sprintf("%s:%d", host, port), + Handler: mux, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 15 * time.Second, + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + // For production, use proper certificates + // Prefer server's cipher suites + PreferServerCipherSuites: true, + CurvePreferences: []tls.CurveID{ + tls.CurveP256, + tls.X25519MLKEM768, + tls.SecP256r1MLKEM768, + }, + }, + } err := s.server.ListenAndServeTLS(certFile, keyFile) // Don't return the error if it's caused by graceful shutdown using Stop() if err == http.ErrServerClosed { From 2ca0bbacd70a77b918d9dc5b4f1469f6bf3e6e5a Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Thu, 9 Apr 2026 23:42:22 -0400 Subject: [PATCH 05/10] fix: Add unit tests for HTTPS server. Signed-off-by: Shuchu Han --- go/internal/feast/server/http_server_test.go | 2 + go/main.go | 18 ++- go/main_test.go | 143 +++++++++++++++++++ pyproject.toml | 6 +- 4 files changed, 162 insertions(+), 7 deletions(-) diff --git a/go/internal/feast/server/http_server_test.go b/go/internal/feast/server/http_server_test.go index e0d474a9f34..7efecce5d5d 100644 --- a/go/internal/feast/server/http_server_test.go +++ b/go/internal/feast/server/http_server_test.go @@ -76,3 +76,5 @@ func TestMarshalInt64JSON(t *testing.T) { assert.Equal(t, expectedJSON, string(jsonData), "JSON output does not match expected") assert.IsType(t, &array.Int64{}, arrowArray, "arrowArray is not of type *array.Int64") } + + diff --git a/go/main.go b/go/main.go index a0e72720332..4f86d6ecdd4 100644 --- a/go/main.go +++ b/go/main.go @@ -37,6 +37,14 @@ import ( var tracer trace.Tracer +var newSignalStopChannel = func() (chan os.Signal, func()) { + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + return stop, func() { + signal.Stop(stop) + } +} + type ServerStarter interface { StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error StartGrpcServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error @@ -260,9 +268,8 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort } }() - stop := make(chan os.Signal, 1) - signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) - defer signal.Stop(stop) + stop, stopCleanup := newSignalStopChannel() + defer stopCleanup() var wg sync.WaitGroup wg.Add(1) @@ -365,9 +372,8 @@ func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort } }() - stop := make(chan os.Signal, 1) - signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) - defer signal.Stop(stop) + stop, stopCleanup := newSignalStopChannel() + defer stopCleanup() var wg sync.WaitGroup wg.Add(1) diff --git a/go/main_test.go b/go/main_test.go index f1f2ae98698..dade214c652 100644 --- a/go/main_test.go +++ b/go/main_test.go @@ -1,12 +1,28 @@ package main import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io" + "math/big" + "net" + "net/http" + "os" + "strings" + "syscall" "testing" + "time" "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/internal/feast/server/logging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) // MockServerStarter is a mock of ServerStarter interface for testing @@ -67,3 +83,130 @@ func TestConstructLoggingService(t *testing.T) { assert.NoError(t, err) // Further assertions can be added here based on the expected behavior of constructLoggingService } + +func TestStartHttpsServerHealthEndpoint(t *testing.T) { + certPath, keyPath := createSelfSignedTLSFiles(t) + host := "127.0.0.1" + port := getFreePort(t) + metricsPort := getFreePort(t) + + stop := make(chan os.Signal, 1) + prevNewSignalStopChannel := newSignalStopChannel + newSignalStopChannel = func() (chan os.Signal, func()) { + return stop, func() {} + } + t.Cleanup(func() { + newSignalStopChannel = prevNewSignalStopChannel + }) + + errCh := make(chan error, 1) + go func() { + errCh <- StartHttpsServer(&feast.FeatureStore{}, host, port, metricsPort, certPath, keyPath, nil, &logging.LoggingOptions{}) + }() + + httpsClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec + }, + } + t.Cleanup(httpsClient.CloseIdleConnections) + + url := fmt.Sprintf("https://%s:%d/health", host, port) + + var ( + resp *http.Response + err error + ) + require.Eventually(t, func() bool { + resp, err = httpsClient.Get(url) + if err != nil { + return false + } + return true + }, 5*time.Second, 100*time.Millisecond) + require.NoError(t, err) + t.Cleanup(func() { + if resp != nil && resp.Body != nil { + _ = resp.Body.Close() + } + }) + + body, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "Healthy", strings.TrimSpace(string(body))) + + stop <- syscall.SIGTERM + + select { + case startErr := <-errCh: + require.NoError(t, startErr) + case <-time.After(5 * time.Second): + t.Fatal("StartHttpsServer did not shutdown within timeout") + } +} + +func TestStartHttpsServerTLSFilesRequired(t *testing.T) { + err := StartHttpsServer(&feast.FeatureStore{}, "127.0.0.1", 0, 0, "", "", nil, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "TLS_CERT_FILE and TLS_KEY_FILE must be set") +} + +func getFreePort(t *testing.T) int { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer func() { + _ = listener.Close() + }() + + addr, ok := listener.Addr().(*net.TCPAddr) + require.True(t, ok) + return addr.Port +} + +func createSelfSignedTLSFiles(t *testing.T) (string, string) { + t.Helper() + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + tmpl := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "localhost", + }, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + + der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv) + require.NoError(t, err) + + certFile, err := os.CreateTemp(t.TempDir(), "feast-test-cert-*.pem") + require.NoError(t, err) + defer func() { + _ = certFile.Close() + }() + + keyFile, err := os.CreateTemp(t.TempDir(), "feast-test-key-*.pem") + require.NoError(t, err) + defer func() { + _ = keyFile.Close() + }() + + err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: der}) + require.NoError(t, err) + + err = pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + require.NoError(t, err) + + return certFile.Name(), keyFile.Name() +} diff --git a/pyproject.toml b/pyproject.toml index 2e45d1820e7..b8d86736bdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,11 +73,15 @@ gcp = [ "fsspec<=2024.9.0", ] ge = ["great_expectations>=0.15.41,<1"] -go = ["cffi>=1.15.0"] +go = [ + "cffi>=1.15.0", + "mypy-protobuf>=3.1", + ] grpcio = [ "grpcio>=1.56.2,<=1.62.3", "grpcio-reflection>=1.56.2,<=1.62.3", "grpcio-health-checking>=1.56.2,<=1.62.3", + "grpcio-tools>=1.56.2,<=1.62.3", ] hazelcast = ["hazelcast-python-client>=5.1"] hbase = ["happybase>=1.2.0,<3"] From 531042a14e9eb85b4f5f79ebc3bcd14373cc25b9 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Thu, 9 Apr 2026 23:54:18 -0400 Subject: [PATCH 06/10] fix: Format code. Fixed bad comments. Signed-off-by: Shuchu Han --- go/internal/feast/server/http_server.go | 28 ++++++++++---------- go/internal/feast/server/http_server_test.go | 2 -- go/main.go | 4 +-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/go/internal/feast/server/http_server.go b/go/internal/feast/server/http_server.go index 2f60444c849..42e787682bd 100644 --- a/go/internal/feast/server/http_server.go +++ b/go/internal/feast/server/http_server.go @@ -2,13 +2,13 @@ package server import ( "context" + "crypto/tls" "encoding/json" "fmt" "net/http" "runtime" "strconv" "time" - "crypto/tls" "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/internal/feast/model" @@ -402,22 +402,22 @@ func (s *httpServer) ServeTLS(host string, port int, certFile string, keyFile st mux.Handle("/get-online-features", metricsMiddleware(recoverMiddleware(http.HandlerFunc(s.getOnlineFeatures)))) mux.Handle("/health", metricsMiddleware(http.HandlerFunc(healthCheckHandler))) s.server = &http.Server{ - Addr: fmt.Sprintf("%s:%d", host, port), - Handler: mux, - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 15 * time.Second, + Addr: fmt.Sprintf("%s:%d", host, port), + Handler: mux, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 15 * time.Second, TLSConfig: &tls.Config{ - MinVersion: tls.VersionTLS12, - // For production, use proper certificates - // Prefer server's cipher suites - PreferServerCipherSuites: true, - CurvePreferences: []tls.CurveID{ - tls.CurveP256, + MinVersion: tls.VersionTLS12, + // For production, use proper certificates + // Prefer server's cipher suites + PreferServerCipherSuites: true, + CurvePreferences: []tls.CurveID{ + tls.CurveP256, tls.X25519MLKEM768, tls.SecP256r1MLKEM768, - }, - }, + }, + }, } err := s.server.ListenAndServeTLS(certFile, keyFile) // Don't return the error if it's caused by graceful shutdown using Stop() diff --git a/go/internal/feast/server/http_server_test.go b/go/internal/feast/server/http_server_test.go index 7efecce5d5d..e0d474a9f34 100644 --- a/go/internal/feast/server/http_server_test.go +++ b/go/internal/feast/server/http_server_test.go @@ -76,5 +76,3 @@ func TestMarshalInt64JSON(t *testing.T) { assert.Equal(t, expectedJSON, string(jsonData), "JSON output does not match expected") assert.IsType(t, &array.Int64{}, arrowArray, "arrowArray is not of type *array.Int64") } - - diff --git a/go/main.go b/go/main.go index 4f86d6ecdd4..3ae33cf9b07 100644 --- a/go/main.go +++ b/go/main.go @@ -345,10 +345,10 @@ func newTracerProvider(exp sdktrace.SpanExporter) (*sdktrace.TracerProvider, err ), nil } -// StartHttpsServer starts HTTP server with TLS. Requires TLS_CERT_FILE and TLS_KEY_FILE env vars. +// StartHttpsServer starts HTTP server with TLS. Requires --tls-cert-file and --tls-key-file flags func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { if certFile == "" || keyFile == "" { - return fmt.Errorf("TLS_CERT_FILE and TLS_KEY_FILE must be set") + return fmt.Errorf("--tls-cert-file and --tls-key-file must be provided") } loggingService, err := constructLoggingService(fs, writeLoggedFeaturesCallback, loggingOpts) From d87d8957aa7cadd0ea867b79735403ad2263bee0 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Fri, 10 Apr 2026 00:13:02 -0400 Subject: [PATCH 07/10] fix: Fix unit test error. Signed-off-by: Shuchu Han --- go/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/main_test.go b/go/main_test.go index dade214c652..3a7f3001ec3 100644 --- a/go/main_test.go +++ b/go/main_test.go @@ -150,7 +150,7 @@ func TestStartHttpsServerHealthEndpoint(t *testing.T) { func TestStartHttpsServerTLSFilesRequired(t *testing.T) { err := StartHttpsServer(&feast.FeatureStore{}, "127.0.0.1", 0, 0, "", "", nil, nil) require.Error(t, err) - assert.Contains(t, err.Error(), "TLS_CERT_FILE and TLS_KEY_FILE must be set") + assert.Contains(t, err.Error(), "--tls-cert-file and --tls-key-file must be provided") } func getFreePort(t *testing.T) int { From c6f5d69b7175ee2924c61626f8967c152a56a4bb Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sat, 11 Apr 2026 23:01:16 -0400 Subject: [PATCH 08/10] fix: use single StartHttpServer to cover HTTP and HTTPs cases. Signed-off-by: Shuchu Han --- go/internal/feast/server/http_server.go | 5 +- go/main.go | 89 +++++-------------------- go/main_test.go | 4 +- pyproject.toml | 2 +- 4 files changed, 19 insertions(+), 81 deletions(-) diff --git a/go/internal/feast/server/http_server.go b/go/internal/feast/server/http_server.go index 42e787682bd..876f42f846b 100644 --- a/go/internal/feast/server/http_server.go +++ b/go/internal/feast/server/http_server.go @@ -409,13 +409,10 @@ func (s *httpServer) ServeTLS(host string, port int, certFile string, keyFile st IdleTimeout: 15 * time.Second, TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS12, - // For production, use proper certificates - // Prefer server's cipher suites - PreferServerCipherSuites: true, CurvePreferences: []tls.CurveID{ tls.CurveP256, tls.X25519MLKEM768, - tls.SecP256r1MLKEM768, + //tls.SecP256r1MLKEM768, // Only available in Go 1.26 }, }, } diff --git a/go/main.go b/go/main.go index 3ae33cf9b07..0ff632115cf 100644 --- a/go/main.go +++ b/go/main.go @@ -48,16 +48,17 @@ var newSignalStopChannel = func() (chan os.Signal, func()) { type ServerStarter interface { StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error StartGrpcServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error + StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions, certFile string, keyFile string) error } type RealServerStarter struct{} func (s *RealServerStarter) StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { - return StartHttpServer(fs, host, port, metricsPort, writeLoggedFeaturesCallback, loggingOpts) + return StartHttpServer(fs, host, port, metricsPort, writeLoggedFeaturesCallback, loggingOpts, false, "", "") } -func (s *RealServerStarter) StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { - return StartHttpsServer(fs, host, port, metricsPort, certFile, keyFile, writeLoggedFeaturesCallback, loggingOpts) +func (s *RealServerStarter) StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions, certFile string, keyFile string) error { + return StartHttpServer(fs, host, port, metricsPort, writeLoggedFeaturesCallback, loggingOpts, true, certFile, keyFile) } func (s *RealServerStarter) StartGrpcServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { @@ -137,7 +138,7 @@ func main() { } else if serverType == "grpc" { err = server.StartGrpcServer(fs, host, port, metricsPort, nil, loggingOptions) } else if serverType == "https" { - err = server.StartHttpsServer(fs, host, port, metricsPort, certFile, keyFile, nil, loggingOptions) + err = server.StartHttpsServer(fs, host, port, metricsPort, nil, loggingOptions, certFile, keyFile) } else { fmt.Println("Unknown server type. Please specify 'http' or 'grpc' or 'https'.") } @@ -246,7 +247,11 @@ func StartGrpcServer(fs *feast.FeatureStore, host string, port int, metricsPort // StartHttpServerWithLogging starts HTTP server with enabled feature logging // Go does not allow direct assignment to package-level functions as a way to // mock them for tests -func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { +func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions, https_enable bool, certFile string, keyFile string) error { + if https_enable && (certFile == "" || keyFile == "") { + return fmt.Errorf("--tls-cert-file and --tls-key-file must be provided for HTTPS server.") + } + loggingService, err := constructLoggingService(fs, writeLoggedFeaturesCallback, loggingOpts) if err != nil { return err @@ -303,7 +308,11 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort } }() - err = ser.Serve(host, port) + if https_enable { + err = ser.ServeTLS(host, port, certFile, keyFile) + } else { + err = ser.Serve(host, port) + } close(serverExited) wg.Wait() return err @@ -344,71 +353,3 @@ func newTracerProvider(exp sdktrace.SpanExporter) (*sdktrace.TracerProvider, err sdktrace.WithResource(r), ), nil } - -// StartHttpsServer starts HTTP server with TLS. Requires --tls-cert-file and --tls-key-file flags -func StartHttpsServer(fs *feast.FeatureStore, host string, port int, metricsPort int, certFile string, keyFile string, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions) error { - if certFile == "" || keyFile == "" { - return fmt.Errorf("--tls-cert-file and --tls-key-file must be provided") - } - - loggingService, err := constructLoggingService(fs, writeLoggedFeaturesCallback, loggingOpts) - if err != nil { - return err - } - ser := server.NewHttpServer(fs, loggingService) - log.Info().Msgf("Starting a HTTPS server on host %s, port %d", host, port) - - // Start metrics server - mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.Handler()) - metricsServer := &http.Server{ - Addr: fmt.Sprintf(":%d", metricsPort), - Handler: mux, - } - go func() { - log.Info().Msgf("Starting metrics server on port %d", metricsPort) - if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Error().Err(err).Msg("Failed to start metrics server") - } - }() - - stop, stopCleanup := newSignalStopChannel() - defer stopCleanup() - - var wg sync.WaitGroup - wg.Add(1) - serverExited := make(chan struct{}) - go func() { - defer wg.Done() - select { - case <-stop: - // Received SIGINT/SIGTERM. Perform graceful shutdown. - log.Info().Msg("Stopping the HTTP server...") - err := ser.Stop() - if err != nil { - log.Error().Err(err).Msg("Error when stopping the HTTP server") - } - log.Info().Msg("Stopping metrics server...") - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := metricsServer.Shutdown(ctx); err != nil { - log.Error().Err(err).Msg("Error stopping metrics server") - } - if loggingService != nil { - loggingService.Stop() - } - log.Info().Msg("HTTP server terminated") - case <-serverExited: - // Server exited (e.g. startup error), ensure metrics server is stopped - metricsServer.Shutdown(context.Background()) - if loggingService != nil { - loggingService.Stop() - } - } - }() - - err = ser.ServeTLS(host, port, certFile, keyFile) - close(serverExited) - wg.Wait() - return err -} diff --git a/go/main_test.go b/go/main_test.go index 3a7f3001ec3..7eb0e0a6676 100644 --- a/go/main_test.go +++ b/go/main_test.go @@ -101,7 +101,7 @@ func TestStartHttpsServerHealthEndpoint(t *testing.T) { errCh := make(chan error, 1) go func() { - errCh <- StartHttpsServer(&feast.FeatureStore{}, host, port, metricsPort, certPath, keyPath, nil, &logging.LoggingOptions{}) + errCh <- StartHttpServer(&feast.FeatureStore{}, host, port, metricsPort, nil, &logging.LoggingOptions{}, true, certPath, keyPath) }() httpsClient := &http.Client{ @@ -148,7 +148,7 @@ func TestStartHttpsServerHealthEndpoint(t *testing.T) { } func TestStartHttpsServerTLSFilesRequired(t *testing.T) { - err := StartHttpsServer(&feast.FeatureStore{}, "127.0.0.1", 0, 0, "", "", nil, nil) + err := StartHttpServer(&feast.FeatureStore{}, "127.0.0.1", 0, 0, nil, &logging.LoggingOptions{}, true, "", "") require.Error(t, err) assert.Contains(t, err.Error(), "--tls-cert-file and --tls-key-file must be provided") } diff --git a/pyproject.toml b/pyproject.toml index b8d86736bdf..463ad007da5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,12 +76,12 @@ ge = ["great_expectations>=0.15.41,<1"] go = [ "cffi>=1.15.0", "mypy-protobuf>=3.1", + "grpcio-tools>=1.56.2,<=1.62.3", ] grpcio = [ "grpcio>=1.56.2,<=1.62.3", "grpcio-reflection>=1.56.2,<=1.62.3", "grpcio-health-checking>=1.56.2,<=1.62.3", - "grpcio-tools>=1.56.2,<=1.62.3", ] hazelcast = ["hazelcast-python-client>=5.1"] hbase = ["happybase>=1.2.0,<3"] From f3d93c5edca0638d28c87cad1188b39b8fa386bf Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sat, 11 Apr 2026 23:02:26 -0400 Subject: [PATCH 09/10] fix: Change back the pyproject.toml file. Signed-off-by: Shuchu Han --- pyproject.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 463ad007da5..2e45d1820e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,11 +73,7 @@ gcp = [ "fsspec<=2024.9.0", ] ge = ["great_expectations>=0.15.41,<1"] -go = [ - "cffi>=1.15.0", - "mypy-protobuf>=3.1", - "grpcio-tools>=1.56.2,<=1.62.3", - ] +go = ["cffi>=1.15.0"] grpcio = [ "grpcio>=1.56.2,<=1.62.3", "grpcio-reflection>=1.56.2,<=1.62.3", From 03d71569dde94cd018b52d5bf46c1b7288b03cd0 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sun, 12 Apr 2026 21:08:08 -0400 Subject: [PATCH 10/10] fix: Fix name convertion of https_enable. Signed-off-by: Shuchu Han --- go/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go/main.go b/go/main.go index 0ff632115cf..7f89fe66c3b 100644 --- a/go/main.go +++ b/go/main.go @@ -247,8 +247,8 @@ func StartGrpcServer(fs *feast.FeatureStore, host string, port int, metricsPort // StartHttpServerWithLogging starts HTTP server with enabled feature logging // Go does not allow direct assignment to package-level functions as a way to // mock them for tests -func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions, https_enable bool, certFile string, keyFile string) error { - if https_enable && (certFile == "" || keyFile == "") { +func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort int, writeLoggedFeaturesCallback logging.OfflineStoreWriteCallback, loggingOpts *logging.LoggingOptions, httpsEnable bool, certFile string, keyFile string) error { + if httpsEnable && (certFile == "" || keyFile == "") { return fmt.Errorf("--tls-cert-file and --tls-key-file must be provided for HTTPS server.") } @@ -308,7 +308,7 @@ func StartHttpServer(fs *feast.FeatureStore, host string, port int, metricsPort } }() - if https_enable { + if httpsEnable { err = ser.ServeTLS(host, port, certFile, keyFile) } else { err = ser.Serve(host, port)