mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
* feat(v3): add server mode for headless HTTP deployment Server mode allows Wails applications to run as pure HTTP servers without native GUI dependencies. Enable with `-tags server` build tag. Features: - HTTP server with configurable host/port via ServerOptions - WAILS_SERVER_HOST and WAILS_SERVER_PORT env var overrides - WebSocket event broadcasting to connected browsers - Browser clients represented as BrowserWindow (Window interface) - Health check endpoint at /health - Graceful shutdown with configurable timeout - Docker support with Dockerfile.server template and tasks Build and run: wails3 task build:server wails3 task run:server wails3 task build:docker wails3 task run:docker Documentation at docs/guides/server-build.mdx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(v3): add server mode for headless HTTP deployment Server mode allows Wails applications to run as pure HTTP servers without native GUI dependencies. Enable with `-tags server` build tag. Features: - HTTP server with configurable host/port via ServerOptions - WAILS_SERVER_HOST and WAILS_SERVER_PORT env var overrides - WebSocket event broadcasting to connected browsers - Browser clients represented as BrowserWindow (Window interface) - Health check endpoint at /health - Graceful shutdown with configurable timeout - Docker support with Dockerfile.server template and tasks Build and run: wails3 task build:server wails3 task run:server wails3 task build:docker wails3 task run:docker Documentation at docs/guides/server-build.mdx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review comments - Fix corrupted test file with embedded terminal output - Fix module name mismatch in gin-routing (was gin-example) - Fix replace directive version mismatch in gin-service - Fix placeholder module name in ios example (was changeme) - Fix Dockerfile COPY path to work from both build contexts - Fix bare URL in README (MD034 compliance) - Fix comment accuracy in getScreens (returns error, not empty slice) - Remove deprecated docker-compose version field - Add port documentation in Taskfile template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review comments - Add note about healthcheck wget not being available in distroless images - Add !server build constraint to menu_windows.go and menu_darwin.go - Downgrade window-visibility-test go.mod from 1.25 to 1.24 to match CI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
168 lines
3.4 KiB
Go
168 lines
3.4 KiB
Go
//go:build server
|
|
|
|
package application
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// resetGlobalApp resets the global application state for testing
|
|
func resetGlobalApp() {
|
|
globalApplication = nil
|
|
}
|
|
|
|
func TestServerMode_HealthEndpoint(t *testing.T) {
|
|
resetGlobalApp()
|
|
|
|
// Create a server mode app (server mode is enabled via build tag)
|
|
app := New(Options{
|
|
Name: "Test Server",
|
|
Server: ServerOptions{
|
|
Host: "127.0.0.1",
|
|
Port: 18081, // Use specific port for this test
|
|
},
|
|
Assets: AssetOptions{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("OK"))
|
|
}),
|
|
},
|
|
})
|
|
|
|
// Start app in background
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
defer wg.Done()
|
|
errCh <- app.Run()
|
|
}()
|
|
|
|
// Wait for server to start
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Test health endpoint
|
|
resp, err := http.Get("http://127.0.0.1:18081/health")
|
|
if err != nil {
|
|
t.Fatalf("health check failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", resp.StatusCode)
|
|
}
|
|
|
|
// Shutdown
|
|
app.Quit()
|
|
|
|
// Wait for shutdown
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
t.Errorf("app.Run() returned error: %v", err)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Error("timeout waiting for app shutdown")
|
|
}
|
|
}
|
|
|
|
func TestServerMode_AssetServing(t *testing.T) {
|
|
resetGlobalApp()
|
|
|
|
testContent := "Hello from server mode!"
|
|
|
|
app := New(Options{
|
|
Name: "Test Assets",
|
|
Server: ServerOptions{
|
|
Host: "127.0.0.1",
|
|
Port: 18082, // Use specific port for this test
|
|
},
|
|
Assets: AssetOptions{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(testContent))
|
|
}),
|
|
},
|
|
})
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
errCh <- app.Run()
|
|
}()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Test asset serving
|
|
resp, err := http.Get("http://127.0.0.1:18082/")
|
|
if err != nil {
|
|
t.Fatalf("request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", resp.StatusCode)
|
|
}
|
|
|
|
app.Quit()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
select {
|
|
case <-errCh:
|
|
case <-ctx.Done():
|
|
t.Error("timeout waiting for app shutdown")
|
|
}
|
|
}
|
|
|
|
func TestServerMode_Defaults(t *testing.T) {
|
|
resetGlobalApp()
|
|
|
|
app := New(Options{
|
|
Name: "Test Defaults",
|
|
Server: ServerOptions{
|
|
Port: 18083, // Use specific port to avoid conflicts
|
|
},
|
|
Assets: AssetOptions{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
},
|
|
})
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
errCh <- app.Run()
|
|
}()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Should be listening on localhost:18083
|
|
resp, err := http.Get("http://localhost:18083/health")
|
|
if err != nil {
|
|
t.Fatalf("request to address failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", resp.StatusCode)
|
|
}
|
|
|
|
app.Quit()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
select {
|
|
case <-errCh:
|
|
case <-ctx.Done():
|
|
t.Error("timeout waiting for app shutdown")
|
|
}
|
|
}
|