@@ -26,6 +26,7 @@ import (
2626 "net/url"
2727 "os"
2828 "path/filepath"
29+ "slices"
2930 "strings"
3031 "sync"
3132
5657type LocalWebsiteService struct {
5758 websiteRegLock sync.RWMutex
5859 state State
59- port int
6060 getApiAddress GetApiAddress
6161 isStartCmd bool
6262
@@ -75,12 +75,12 @@ func (l *LocalWebsiteService) SubscribeToState(fn func(State)) {
7575}
7676
7777// register - Register a new website
78- func (l * LocalWebsiteService ) register (website Website ) {
78+ func (l * LocalWebsiteService ) register (website Website , port int ) {
7979 l .websiteRegLock .Lock ()
8080 defer l .websiteRegLock .Unlock ()
8181
8282 // Emulates the CDN URL used in a deployed environment
83- publicUrl := fmt .Sprintf ("http://localhost:%d/%s" , l . port , strings .TrimPrefix (website .BasePath , "/" ))
83+ publicUrl := fmt .Sprintf ("http://localhost:%d/%s" , port , strings .TrimPrefix (website .BasePath , "/" ))
8484
8585 l .state [website .Name ] = Website {
8686 WebsitePb : website .WebsitePb ,
@@ -95,9 +95,9 @@ func (l *LocalWebsiteService) register(website Website) {
9595
9696type staticSiteHandler struct {
9797 website * Website
98- port int
9998 devURL string
10099 isStartCmd bool
100+ server * http.Server
101101}
102102
103103func (h staticSiteHandler ) serveProxy (res http.ResponseWriter , req * http.Request ) {
@@ -117,6 +117,17 @@ func (h staticSiteHandler) serveProxy(res http.ResponseWriter, req *http.Request
117117 return
118118 }
119119
120+ // Strip the base path from the request path before proxying
121+ if h .website .BasePath != "/" {
122+ // redirect to base if path is / and there is no query string
123+ if req .RequestURI == "/" {
124+ http .Redirect (res , req , h .website .BasePath , http .StatusFound )
125+ return
126+ }
127+
128+ req .URL .Path = strings .TrimPrefix (req .URL .Path , h .website .BasePath )
129+ }
130+
120131 // Reverse proxy request
121132 proxy := httputil .NewSingleHostReverseProxy (targetUrl )
122133
@@ -152,7 +163,7 @@ func (h staticSiteHandler) serveStatic(res http.ResponseWriter, req *http.Reques
152163 }
153164
154165 if fi .IsDir () {
155- http .ServeFile (res , req , filepath .Join (h . website . OutputDirectory , h .website .IndexDocument ))
166+ http .ServeFile (res , req , filepath .Join (path , h .website .IndexDocument ))
156167
157168 return
158169 }
@@ -171,21 +182,9 @@ func (h staticSiteHandler) ServeHTTP(res http.ResponseWriter, req *http.Request)
171182 h .serveStatic (res , req )
172183}
173184
174- // Start - Start the local website service
175- func (l * LocalWebsiteService ) Start (websites []Website ) error {
176- newLis , err := netx .GetNextListener (netx .MinPort (5000 ))
177- if err != nil {
178- return err
179- }
180-
181- l .port = newLis .Addr ().(* net.TCPAddr ).Port
182-
183- _ = newLis .Close ()
184-
185- mux := http .NewServeMux ()
186-
187- // Register the API proxy handler
188- mux .HandleFunc ("/api/{name}/" , func (res http.ResponseWriter , req * http.Request ) {
185+ // createAPIPathHandler creates a handler for API proxy requests
186+ func (l * LocalWebsiteService ) createAPIPathHandler () http.HandlerFunc {
187+ return func (res http.ResponseWriter , req * http.Request ) {
189188 apiName := req .PathValue ("name" )
190189
191190 apiAddress := l .getApiAddress (apiName )
@@ -201,31 +200,126 @@ func (l *LocalWebsiteService) Start(websites []Website) error {
201200 req .URL .Path = targetPath
202201
203202 proxy .ServeHTTP (res , req )
203+ }
204+ }
205+
206+ // createServer creates and configures an HTTP server with the given mux
207+ func (l * LocalWebsiteService ) createServer (mux * http.ServeMux , port int ) * http.Server {
208+ return & http.Server {
209+ Addr : fmt .Sprintf (":%d" , port ),
210+ Handler : mux ,
211+ }
212+ }
213+
214+ // startServer starts the given server in a goroutine and handles errors
215+ func (l * LocalWebsiteService ) startServer (server * http.Server , errChan chan error , errMsg string ) {
216+ go func () {
217+ if err := server .ListenAndServe (); err != nil && err != http .ErrServerClosed {
218+ select {
219+ case errChan <- fmt .Errorf (errMsg , err ):
220+ default :
221+ }
222+ }
223+ }()
224+ }
225+
226+ // Start - Start the local website service
227+ func (l * LocalWebsiteService ) Start (websites []Website ) error {
228+ errChan := make (chan error , 1 )
229+
230+ startPort := 5000
231+
232+ slices .SortFunc (websites , func (a , b Website ) int {
233+ return strings .Compare (a .BasePath , b .BasePath )
204234 })
205235
206- // Register the SPA handler for each website
207- for i := range websites {
208- website := & websites [ i ]
209- spa := staticSiteHandler { website : website , port : l . port , devURL : website . DevURL , isStartCmd : l . isStartCmd }
236+ if l . isStartCmd {
237+ // In start mode, create individual servers for each website
238+ for i := range websites {
239+ website := & websites [ i ]
210240
211- if website .BasePath == "/" {
241+ // Get a new listener for each website, incrementing the port each time
242+ newLis , err := netx .GetNextListener (netx .MinPort (startPort + i ))
243+ if err != nil {
244+ return err
245+ }
246+
247+ port := newLis .Addr ().(* net.TCPAddr ).Port
248+ _ = newLis .Close ()
249+
250+ mux := http .NewServeMux ()
251+
252+ // Register the API proxy handler for this website
253+ mux .HandleFunc ("/api/{name}/" , l .createAPIPathHandler ())
254+
255+ // Create the SPA handler for this website
256+ spa := staticSiteHandler {
257+ website : website ,
258+ devURL : website .DevURL ,
259+ isStartCmd : l .isStartCmd ,
260+ }
261+
262+ // Register the SPA handler
212263 mux .Handle ("/" , spa )
213- } else {
214- mux .Handle (website .BasePath + "/" , http .StripPrefix (website .BasePath + "/" , spa ))
264+
265+ // Create and start the server
266+ server := l .createServer (mux , port )
267+
268+ // Store the server in the handler for potential cleanup
269+ spa .server = server
270+
271+ // Register the website with its port
272+ l .register (* website , port )
273+
274+ // Start the server in a goroutine
275+ l .startServer (server , errChan , "failed to start server for website %s: %w" )
276+ }
277+ } else {
278+ // For static serving, use a single server
279+ newLis , err := netx .GetNextListener (netx .MinPort (startPort ))
280+ if err != nil {
281+ return err
215282 }
216- }
217283
218- // Start the server with the multiplexer
219- go func () {
220- addr := fmt .Sprintf (":%d" , l .port )
221- if err := http .ListenAndServe (addr , mux ); err != nil {
222- fmt .Printf ("Failed to start server: %s\n " , err )
284+ port := newLis .Addr ().(* net.TCPAddr ).Port
285+ _ = newLis .Close ()
286+
287+ mux := http .NewServeMux ()
288+
289+ // Register the API proxy handler
290+ mux .HandleFunc ("/api/{name}/" , l .createAPIPathHandler ())
291+
292+ // Register the SPA handler for each website
293+ for i := range websites {
294+ website := & websites [i ]
295+ spa := staticSiteHandler {
296+ website : website ,
297+ devURL : website .DevURL ,
298+ isStartCmd : l .isStartCmd ,
299+ }
300+
301+ if website .BasePath == "/" {
302+ mux .Handle ("/" , spa )
303+ } else {
304+ mux .Handle (website .BasePath + "/" , http .StripPrefix (website .BasePath + "/" , spa ))
305+ }
223306 }
224- }()
225307
226- // Register the websites
227- for _ , website := range websites {
228- l .register (website )
308+ // Register all websites with the same port
309+ for _ , website := range websites {
310+ l .register (website , port )
311+ }
312+
313+ // Create and start the server
314+ server := l .createServer (mux , port )
315+
316+ // Start the server in a goroutine
317+ l .startServer (server , errChan , "failed to start static server: %w" )
318+ }
319+
320+ // Return the first error that occurred, if any
321+ if err := <- errChan ; err != nil {
322+ return err
229323 }
230324
231325 return nil
0 commit comments