Compare commits
3 commits
main
...
perf/optim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99672ea740 | ||
|
|
0fb32b0b8d | ||
|
|
c930ab151a |
4 changed files with 152 additions and 22 deletions
|
|
@ -9,6 +9,7 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -57,16 +58,34 @@ func (s *MediaStore) Clear() {
|
|||
s.media = make(map[string]*MediaItem)
|
||||
}
|
||||
|
||||
func init() {
|
||||
mime.AddExtensionType(".wasm", "application/wasm")
|
||||
mime.AddExtensionType(".js", "application/javascript")
|
||||
mime.AddExtensionType(".css", "text/css")
|
||||
mime.AddExtensionType(".html", "text/html; charset=utf-8")
|
||||
}
|
||||
|
||||
// AssetHandler serves both static assets and decrypted media
|
||||
type AssetHandler struct {
|
||||
assets fs.FS
|
||||
assets fs.FS
|
||||
fileServer http.Handler
|
||||
}
|
||||
|
||||
// NewAssetHandler creates a new AssetHandler
|
||||
func NewAssetHandler(assets fs.FS) *AssetHandler {
|
||||
sub, err := fs.Sub(assets, "frontend")
|
||||
if err != nil {
|
||||
// Fallback to assets if sub fails (e.g. test mock)
|
||||
sub = assets
|
||||
}
|
||||
return &AssetHandler{
|
||||
assets: assets,
|
||||
fileServer: http.FileServer(http.FS(sub)),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if path == "/" {
|
||||
path = "/index.html"
|
||||
}
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
|
||||
// Check if this is a media request
|
||||
|
|
@ -112,25 +131,12 @@ func (h *AssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Serve static assets
|
||||
data, err := fs.ReadFile(h.assets, "frontend/"+path)
|
||||
if err != nil {
|
||||
if h.fileServer == nil {
|
||||
// Fallback if not initialized via NewAssetHandler (should not happen in prod)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Set content type
|
||||
switch {
|
||||
case strings.HasSuffix(path, ".html"):
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
case strings.HasSuffix(path, ".js"):
|
||||
w.Header().Set("Content-Type", "application/javascript")
|
||||
case strings.HasSuffix(path, ".css"):
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
case strings.HasSuffix(path, ".wasm"):
|
||||
w.Header().Set("Content-Type", "application/wasm")
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
h.fileServer.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// App wraps player functionality
|
||||
|
|
@ -307,7 +313,7 @@ func main() {
|
|||
MinWidth: 800,
|
||||
MinHeight: 600,
|
||||
AssetServer: &assetserver.Options{
|
||||
Handler: &AssetHandler{assets: frontendAssets},
|
||||
Handler: NewAssetHandler(frontendAssets),
|
||||
},
|
||||
BackgroundColour: &options.RGBA{R: 18, G: 18, B: 18, A: 1},
|
||||
OnStartup: app.Startup,
|
||||
|
|
|
|||
125
cmd/dapp-fm-app/main_test.go
Normal file
125
cmd/dapp-fm-app/main_test.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
func TestAssetHandler_ServeHTTP_Static(t *testing.T) {
|
||||
mockFS := fstest.MapFS{
|
||||
"frontend/index.html": {Data: []byte("<html><body>Hello</body></html>")},
|
||||
"frontend/style.css": {Data: []byte("body { color: red; }")},
|
||||
"frontend/app.js": {Data: []byte("console.log('hi')")},
|
||||
"frontend/test.wasm": {Data: []byte{0x00, 0x61, 0x73, 0x6d}},
|
||||
}
|
||||
|
||||
handler := NewAssetHandler(mockFS)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
wantCode int
|
||||
wantType string
|
||||
wantContent string
|
||||
}{
|
||||
{
|
||||
name: "Root",
|
||||
path: "/",
|
||||
wantCode: http.StatusOK,
|
||||
wantType: "text/html; charset=utf-8",
|
||||
wantContent: "<html><body>Hello</body></html>",
|
||||
},
|
||||
{
|
||||
name: "Index",
|
||||
path: "/index.html",
|
||||
wantCode: http.StatusMovedPermanently, // http.FileServer redirects index.html to /
|
||||
},
|
||||
{
|
||||
name: "CSS",
|
||||
path: "/style.css",
|
||||
wantCode: http.StatusOK,
|
||||
wantType: "text/css",
|
||||
wantContent: "body { color: red; }",
|
||||
},
|
||||
{
|
||||
name: "JS",
|
||||
path: "/app.js",
|
||||
wantCode: http.StatusOK,
|
||||
wantType: "application/javascript",
|
||||
wantContent: "console.log('hi')",
|
||||
},
|
||||
{
|
||||
name: "WASM",
|
||||
path: "/test.wasm",
|
||||
wantCode: http.StatusOK,
|
||||
wantType: "application/wasm",
|
||||
wantContent: "\x00\x61\x73\x6d",
|
||||
},
|
||||
{
|
||||
name: "NotFound",
|
||||
path: "/missing.html",
|
||||
wantCode: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", tt.path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != tt.wantCode {
|
||||
t.Errorf("path %s: status code = %d, want %d", tt.path, resp.StatusCode, tt.wantCode)
|
||||
}
|
||||
|
||||
if tt.wantCode == http.StatusOK {
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if !strings.Contains(ct, tt.wantType) {
|
||||
t.Errorf("path %s: content type = %q, want %q", tt.path, ct, tt.wantType)
|
||||
}
|
||||
|
||||
// Read body
|
||||
if tt.wantContent != "" {
|
||||
body := w.Body.String()
|
||||
if body != tt.wantContent {
|
||||
t.Errorf("path %s: body = %q, want %q", tt.path, body, tt.wantContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetHandler_ServeHTTP_Media(t *testing.T) {
|
||||
// Setup test data
|
||||
globalStore.Set("123", &MediaItem{
|
||||
Data: []byte("mediadata"),
|
||||
MimeType: "audio/mp3",
|
||||
Name: "song.mp3",
|
||||
})
|
||||
defer globalStore.Clear()
|
||||
|
||||
mockFS := fstest.MapFS{}
|
||||
handler := NewAssetHandler(mockFS)
|
||||
|
||||
req := httptest.NewRequest("GET", "/media/123", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("status code = %d, want %d", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
if ct := resp.Header.Get("Content-Type"); ct != "audio/mp3" {
|
||||
t.Errorf("content type = %s, want audio/mp3", ct)
|
||||
}
|
||||
if body := w.Body.String(); body != "mediadata" {
|
||||
t.Errorf("body = %s, want mediadata", body)
|
||||
}
|
||||
}
|
||||
BIN
dapp-fm-app
Executable file
BIN
dapp-fm-app
Executable file
Binary file not shown.
|
|
@ -11,7 +11,6 @@ import (
|
|||
//go:embed frontend/index.html
|
||||
//go:embed frontend/wasm_exec.js
|
||||
//go:embed frontend/stmf.wasm
|
||||
//go:embed frontend/demo-track.smsg
|
||||
var assets embed.FS
|
||||
|
||||
// Assets returns the embedded filesystem with frontend/ prefix stripped
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue