api-client/packages/hoppscotch-selfhost-web/webapp-server/main.go
Rodrigo Kellermann 40ac84c115
feat(selfhost-web): make webapp-server timeouts configurable (#6147)
Signed-off-by: Rodrigo Kellermann <kellermann@gmail.com>
Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com>
2026-04-28 00:45:41 +05:30

99 lines
2.5 KiB
Go

// Hoppscotch Webapp Server
//
// Builds a signed bundle from frontend assets and serves it over HTTP.
// The bundle is zstd-compressed and signed with ed25519 so clients can verify integrity.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"hoppscotch-selfhost-web/webapp-server/internal/bundle"
"hoppscotch-selfhost-web/webapp-server/internal/config"
"hoppscotch-selfhost-web/webapp-server/internal/crypto"
"hoppscotch-selfhost-web/webapp-server/internal/server"
)
func main() {
log.Println("Initializing Hoppscotch Web Static Server")
cfg := config.Load()
// NOTE: key generation handles persistence internally
// it'll try env vars first, then disk, then generate new
keyPair, err := crypto.GenerateKeyPair()
if err != nil {
log.Fatalf("Failed to generate key pair: %v", err)
}
builder, err := bundle.NewBuilder()
if err != nil {
log.Fatalf("Failed to create bundle builder: %v", err)
}
// this walks the frontend dir and creates a zstd-compressed zip
content, files, err := builder.Build(cfg.FrontendPath)
if err != nil {
log.Fatalf("Failed to build bundle: %v", err)
}
// manager holds the bundle in memory and handles signing
bundleManager, err := bundle.NewManager(
content,
files,
keyPair.SigningKey,
keyPair.VerifyingKey,
bundle.DefaultMaxSize,
)
if err != nil {
log.Fatalf("Failed to create bundle manager: %v", err)
}
srv := server.New(bundleManager)
mux := http.NewServeMux()
srv.RegisterRoutes(mux)
addr := fmt.Sprintf(":%d", cfg.Port)
// NOTE: timeouts default to conservative values but can be overridden
// via WEBAPP_SERVER_READ_TIMEOUT, WEBAPP_SERVER_WRITE_TIMEOUT, WEBAPP_SERVER_IDLE_TIMEOUT
// (Go duration strings, e.g. "30s", "2m", "0" to disable)
httpServer := &http.Server{
Addr: addr,
Handler: mux,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
IdleTimeout: cfg.IdleTimeout,
}
go func() {
log.Printf("Server starting on %s", addr)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
// wait for shutdown signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Server shutting down...")
// give in-flight requests 30s to finish
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := httpServer.Shutdown(ctx); err != nil {
log.Printf("Server forced to shutdown: %v", err)
}
log.Println("Server stopped")
}