|  | 
| 1 | 1 | package http | 
| 2 | 2 | 
 | 
| 3 | 3 | import ( | 
| 4 |  | -	"bufio" | 
| 5 |  | -	"net" | 
|  | 4 | +	"context" | 
|  | 5 | +	"errors" | 
| 6 | 6 | 	"net/http" | 
|  | 7 | +	"os" | 
|  | 8 | +	"os/signal" | 
|  | 9 | +	"syscall" | 
| 7 | 10 | 	"time" | 
| 8 | 11 | 
 | 
| 9 | 12 | 	"k8s.io/klog/v2" | 
| 10 |  | -) | 
| 11 | 13 | 
 | 
| 12 |  | -func RequestMiddleware(next http.Handler) http.Handler { | 
| 13 |  | -	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 
| 14 |  | -		start := time.Now() | 
|  | 14 | +	"github.com/manusa/kubernetes-mcp-server/pkg/mcp" | 
|  | 15 | +) | 
| 15 | 16 | 
 | 
| 16 |  | -		lrw := &loggingResponseWriter{ | 
| 17 |  | -			ResponseWriter: w, | 
| 18 |  | -			statusCode:     http.StatusOK, | 
| 19 |  | -		} | 
|  | 17 | +func Serve(ctx context.Context, mcpServer *mcp.Server, port, sseBaseUrl string) error { | 
|  | 18 | +	mux := http.NewServeMux() | 
|  | 19 | +	wrappedMux := RequestMiddleware(mux) | 
| 20 | 20 | 
 | 
| 21 |  | -		next.ServeHTTP(lrw, r) | 
|  | 21 | +	httpServer := &http.Server{ | 
|  | 22 | +		Addr:    ":" + port, | 
|  | 23 | +		Handler: wrappedMux, | 
|  | 24 | +	} | 
| 22 | 25 | 
 | 
| 23 |  | -		duration := time.Since(start) | 
| 24 |  | -		klog.V(5).Infof("%s %s %d %v", r.Method, r.URL.Path, lrw.statusCode, duration) | 
|  | 26 | +	sseServer := mcpServer.ServeSse(sseBaseUrl, httpServer) | 
|  | 27 | +	streamableHttpServer := mcpServer.ServeHTTP(httpServer) | 
|  | 28 | +	mux.Handle("/sse", sseServer) | 
|  | 29 | +	mux.Handle("/message", sseServer) | 
|  | 30 | +	mux.Handle("/mcp", streamableHttpServer) | 
|  | 31 | +	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { | 
|  | 32 | +		w.WriteHeader(http.StatusOK) | 
| 25 | 33 | 	}) | 
| 26 |  | -} | 
| 27 | 34 | 
 | 
| 28 |  | -type loggingResponseWriter struct { | 
| 29 |  | -	http.ResponseWriter | 
| 30 |  | -	statusCode    int | 
| 31 |  | -	headerWritten bool | 
| 32 |  | -} | 
|  | 35 | +	ctx, cancel := context.WithCancel(ctx) | 
|  | 36 | +	defer cancel() | 
| 33 | 37 | 
 | 
| 34 |  | -func (lrw *loggingResponseWriter) WriteHeader(code int) { | 
| 35 |  | -	if !lrw.headerWritten { | 
| 36 |  | -		lrw.statusCode = code | 
| 37 |  | -		lrw.headerWritten = true | 
| 38 |  | -		lrw.ResponseWriter.WriteHeader(code) | 
| 39 |  | -	} | 
| 40 |  | -} | 
|  | 38 | +	sigChan := make(chan os.Signal, 1) | 
|  | 39 | +	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) | 
| 41 | 40 | 
 | 
| 42 |  | -func (lrw *loggingResponseWriter) Write(b []byte) (int, error) { | 
| 43 |  | -	if !lrw.headerWritten { | 
| 44 |  | -		lrw.statusCode = http.StatusOK | 
| 45 |  | -		lrw.headerWritten = true | 
| 46 |  | -	} | 
| 47 |  | -	return lrw.ResponseWriter.Write(b) | 
| 48 |  | -} | 
|  | 41 | +	serverErr := make(chan error, 1) | 
|  | 42 | +	go func() { | 
|  | 43 | +		klog.V(0).Infof("Streaming and SSE HTTP servers starting on port %s and paths /mcp, /sse, /message", port) | 
|  | 44 | +		if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { | 
|  | 45 | +			serverErr <- err | 
|  | 46 | +		} | 
|  | 47 | +	}() | 
| 49 | 48 | 
 | 
| 50 |  | -func (lrw *loggingResponseWriter) Flush() { | 
| 51 |  | -	if flusher, ok := lrw.ResponseWriter.(http.Flusher); ok { | 
| 52 |  | -		flusher.Flush() | 
|  | 49 | +	select { | 
|  | 50 | +	case sig := <-sigChan: | 
|  | 51 | +		klog.V(0).Infof("Received signal %v, initiating graceful shutdown", sig) | 
|  | 52 | +		cancel() | 
|  | 53 | +	case <-ctx.Done(): | 
|  | 54 | +		klog.V(0).Infof("Context cancelled, initiating graceful shutdown") | 
|  | 55 | +	case err := <-serverErr: | 
|  | 56 | +		klog.Errorf("HTTP server error: %v", err) | 
|  | 57 | +		return err | 
| 53 | 58 | 	} | 
| 54 |  | -} | 
| 55 | 59 | 
 | 
| 56 |  | -func (lrw *loggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { | 
| 57 |  | -	if hijacker, ok := lrw.ResponseWriter.(http.Hijacker); ok { | 
| 58 |  | -		return hijacker.Hijack() | 
|  | 60 | +	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) | 
|  | 61 | +	defer shutdownCancel() | 
|  | 62 | + | 
|  | 63 | +	klog.V(0).Infof("Shutting down HTTP server gracefully...") | 
|  | 64 | +	if err := httpServer.Shutdown(shutdownCtx); err != nil { | 
|  | 65 | +		klog.Errorf("HTTP server shutdown error: %v", err) | 
|  | 66 | +		return err | 
| 59 | 67 | 	} | 
| 60 |  | -	return nil, nil, http.ErrNotSupported | 
|  | 68 | + | 
|  | 69 | +	klog.V(0).Infof("HTTP server shutdown complete") | 
|  | 70 | +	return nil | 
| 61 | 71 | } | 
0 commit comments