Initial iris repository structure
Some checks failed
CI / build (push) Failing after 36s

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:
2026-01-08 19:23:49 +01:00
commit 00d98879d3
36 changed files with 4181 additions and 0 deletions

150
host/server.go Normal file
View 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"
}
}