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