WASM reactive UI framework for Go: - reactive/ - Signal[T], Effect, Runtime - ui/ - Button, Text, Input, View, Canvas, SVG components - navigation/ - Router, guards, history management - auth/ - OIDC client for WASM applications - host/ - Static file server Extracted from arcadia as open-source component. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
150
host/server.go
Normal file
150
host/server.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server provides a high-performance static file server with gzip compression
|
||||
// optimized for serving WASM applications built with the Iris framework.
|
||||
type Server struct {
|
||||
publicDir string
|
||||
indexFile string
|
||||
}
|
||||
|
||||
// New creates a new Server instance
|
||||
func New(publicDir, indexFile string) *Server {
|
||||
return &Server{
|
||||
publicDir: publicDir,
|
||||
indexFile: indexFile,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface
|
||||
// Falls back to index file when:
|
||||
// (1) Request path is not found
|
||||
// (2) Request path is a directory
|
||||
// Otherwise serves the requested file with gzip compression if supported.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
p := filepath.Join(s.publicDir, filepath.Clean(r.URL.Path))
|
||||
|
||||
if info, err := os.Stat(p); err != nil {
|
||||
s.serveFileWithCompression(w, r, filepath.Join(s.publicDir, s.indexFile))
|
||||
return
|
||||
} else if info.IsDir() {
|
||||
s.serveFileWithCompression(w, r, filepath.Join(s.publicDir, s.indexFile))
|
||||
return
|
||||
}
|
||||
|
||||
s.serveFileWithCompression(w, r, p)
|
||||
}
|
||||
|
||||
// serveFileWithCompression serves a file with gzip compression if supported by client
|
||||
func (s *Server) serveFileWithCompression(w http.ResponseWriter, r *http.Request, filePath string) {
|
||||
// Check if client accepts gzip
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
http.ServeFile(w, r, filePath)
|
||||
return
|
||||
}
|
||||
|
||||
// Open and read file
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "File not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Get file info for headers
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
http.Error(w, "Error reading file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set content type based on file extension
|
||||
contentType := getContentType(filepath.Ext(filePath))
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
// Check if we should compress this file type
|
||||
if shouldCompress(contentType) {
|
||||
// Set gzip headers
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Set("Vary", "Accept-Encoding")
|
||||
|
||||
// Create gzip writer
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
|
||||
// Copy file content through gzip
|
||||
_, err = io.Copy(gz, file)
|
||||
if err != nil {
|
||||
http.Error(w, "Error compressing file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Serve without compression
|
||||
http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file)
|
||||
}
|
||||
}
|
||||
|
||||
// shouldCompress determines if the content should be compressed based on Content-Type
|
||||
func shouldCompress(contentType string) bool {
|
||||
compressibleTypes := []string{
|
||||
"text/html",
|
||||
"text/css",
|
||||
"text/javascript",
|
||||
"application/javascript",
|
||||
"application/json",
|
||||
"application/wasm",
|
||||
"text/plain",
|
||||
"image/svg+xml",
|
||||
}
|
||||
|
||||
for _, t := range compressibleTypes {
|
||||
if strings.Contains(contentType, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getContentType returns the appropriate content type for a file extension
|
||||
func getContentType(ext string) string {
|
||||
switch ext {
|
||||
case ".html":
|
||||
return "text/html; charset=utf-8"
|
||||
case ".css":
|
||||
return "text/css"
|
||||
case ".js":
|
||||
return "application/javascript"
|
||||
case ".wasm":
|
||||
return "application/wasm"
|
||||
case ".json":
|
||||
return "application/json"
|
||||
case ".ico":
|
||||
return "image/x-icon"
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".jpg", ".jpeg":
|
||||
return "image/jpeg"
|
||||
case ".gif":
|
||||
return "image/gif"
|
||||
case ".svg":
|
||||
return "image/svg+xml"
|
||||
case ".woff":
|
||||
return "font/woff"
|
||||
case ".woff2":
|
||||
return "font/woff2"
|
||||
case ".ttf":
|
||||
return "font/ttf"
|
||||
case ".eot":
|
||||
return "application/vnd.ms-fontobject"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user