smesh2.go raw

   1  package app
   2  
   3  import (
   4  	"context"
   5  	"embed"
   6  	"fmt"
   7  	"io/fs"
   8  	"net"
   9  	"net/http"
  10  	"strings"
  11  	"time"
  12  
  13  	"git.smesh.lol/orly/pkg/lol/log"
  14  )
  15  
  16  //go:embed smesh2
  17  var smesh2FS embed.FS
  18  
  19  // Smesh2Server serves the embedded smesh2 web client on a dedicated port.
  20  type Smesh2Server struct {
  21  	server   *http.Server
  22  	listener net.Listener
  23  	port     int
  24  }
  25  
  26  // NewSmesh2Server creates a new smesh2 HTTP server on the given port.
  27  func NewSmesh2Server(port int) *Smesh2Server {
  28  	return &Smesh2Server{port: port}
  29  }
  30  
  31  // Start begins serving the embedded smesh2 client.
  32  func (s *Smesh2Server) Start(ctx context.Context) error {
  33  	webDist, err := fs.Sub(smesh2FS, "smesh2")
  34  	if err != nil {
  35  		return fmt.Errorf("failed to load embedded smesh2 app: %w", err)
  36  	}
  37  
  38  	fileServer := http.FileServer(http.FS(webDist))
  39  
  40  	mux := http.NewServeMux()
  41  	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  42  		path := r.URL.Path
  43  		if path == "/" {
  44  			fileServer.ServeHTTP(w, r)
  45  			return
  46  		}
  47  
  48  		cleanPath := strings.TrimPrefix(path, "/")
  49  		if f, err := webDist.Open(cleanPath); err == nil {
  50  			f.Close()
  51  			fileServer.ServeHTTP(w, r)
  52  			return
  53  		}
  54  
  55  		// SPA fallback
  56  		r.URL.Path = "/"
  57  		fileServer.ServeHTTP(w, r)
  58  	})
  59  
  60  	addr := fmt.Sprintf("127.0.0.1:%d", s.port)
  61  	s.server = &http.Server{
  62  		Addr:         addr,
  63  		Handler:      mux,
  64  		ReadTimeout:  15 * time.Second,
  65  		WriteTimeout: 15 * time.Second,
  66  		IdleTimeout:  60 * time.Second,
  67  	}
  68  
  69  	s.listener, err = net.Listen("tcp", addr)
  70  	if err != nil {
  71  		return fmt.Errorf("smesh2: failed to listen on %s: %w", addr, err)
  72  	}
  73  
  74  	go func() {
  75  		<-ctx.Done()
  76  		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  77  		defer cancel()
  78  		if err := s.server.Shutdown(shutdownCtx); err != nil {
  79  			log.W.F("smesh2 server shutdown error: %v", err)
  80  		}
  81  	}()
  82  
  83  	log.I.F("smesh2 web client serving on http://%s", addr)
  84  
  85  	go func() {
  86  		if err := s.server.Serve(s.listener); err != nil && err != http.ErrServerClosed {
  87  			log.E.F("smesh2 server error: %v", err)
  88  		}
  89  	}()
  90  
  91  	return nil
  92  }
  93  
  94  // Stop shuts down the smesh2 server.
  95  func (s *Smesh2Server) Stop() {
  96  	if s.server != nil {
  97  		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  98  		defer cancel()
  99  		s.server.Shutdown(ctx)
 100  	}
 101  }
 102