feat(web,server): embed статики SPA + fallback, монтирование в cmd/server
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// Package web embeds the built React SPA (web/dist, copied to
|
||||
// internal/web/dist by `make web` before go build/test) and serves it
|
||||
// with SPA-fallback: any non-file path resolves to index.html so
|
||||
// client-side routing works.
|
||||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed all:dist
|
||||
var distFS embed.FS
|
||||
|
||||
// Handler serves the embedded SPA with fallback to index.html for
|
||||
// client-side routes (any non-file path that isn't an API route).
|
||||
func Handler() (http.Handler, error) {
|
||||
sub, err := fs.Sub(distFS, "dist")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileServer := http.FileServer(http.FS(sub))
|
||||
index, err := fs.ReadFile(sub, "index.html")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// существующий файл (ассет) — отдать как есть
|
||||
p := strings.TrimPrefix(r.URL.Path, "/")
|
||||
if p != "" {
|
||||
if f, err := sub.Open(p); err == nil {
|
||||
_ = f.Close()
|
||||
fileServer.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
// иначе — SPA index
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(index)
|
||||
}), nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandlerServesIndexAndSPAFallback(t *testing.T) {
|
||||
h, err := Handler()
|
||||
if err != nil {
|
||||
t.Fatalf("handler: %v", err)
|
||||
}
|
||||
// корень → 200 и HTML
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("root status %d", rec.Code)
|
||||
}
|
||||
// неизвестный клиентский путь → SPA fallback (index.html), не 404
|
||||
rec2 := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec2, httptest.NewRequest(http.MethodGet, "/domains/xyz", nil))
|
||||
if rec2.Code != http.StatusOK {
|
||||
t.Fatalf("SPA fallback status %d", rec2.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user