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>
This commit is contained in:
Rodrigo Kellermann 2026-04-27 16:15:41 -03:00 committed by GitHub
parent 9861ee84ad
commit 40ac84c115
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 14 deletions

View file

@ -16,15 +16,18 @@ docker build -t hoppscotch-webapp-server .
## Configuration ## Configuration
| Variable | Description | Default | | Variable | Description | Default |
|----------------------------------|------------------------------------|------------------------------------------------| |----------------------------------|------------------------------------------------------------|------------------------------------------------|
| `WEBAPP_SERVER_PORT` | Server port | `3200` | | `WEBAPP_SERVER_PORT` | Server port | `3200` |
| `FRONTEND_PATH` | Path to frontend assets | `/site/selfhost-web` (prod) or `../dist` (dev) | | `WEBAPP_SERVER_READ_TIMEOUT` | HTTP read timeout (Go duration, e.g. `30s`; `0` disables) | `15s` |
| `WEBAPP_SERVER_SIGNING_SECRET` | Secret string for key derivation | None | | `WEBAPP_SERVER_WRITE_TIMEOUT` | HTTP write timeout (Go duration, e.g. `30s`; `0` disables) | `15s` |
| `WEBAPP_SERVER_SIGNING_SEED` | Base64 encoded 32-byte seed | None | | `WEBAPP_SERVER_IDLE_TIMEOUT` | HTTP idle timeout (Go duration, e.g. `2m`; `0` disables) | `60s` |
| `WEBAPP_SERVER_SIGNING_KEY` | Base64 encoded 64-byte private key | None | | `FRONTEND_PATH` | Path to frontend assets | `/site/selfhost-web` (prod) or `../dist` (dev) |
| `WEBAPP_SERVER_SIGNING_KEY_FILE` | Custom path for key file | `/data/webapp-server/signing.key` | | `WEBAPP_SERVER_SIGNING_SECRET` | Secret string for key derivation | None |
| `GO_ENV` | Set to `development` for dev mode | None | | `WEBAPP_SERVER_SIGNING_SEED` | Base64 encoded 32-byte seed | None |
| `WEBAPP_SERVER_SIGNING_KEY` | Base64 encoded 64-byte private key | None |
| `WEBAPP_SERVER_SIGNING_KEY_FILE` | Custom path for key file | `/data/webapp-server/signing.key` |
| `GO_ENV` | Set to `development` for dev mode | None |
## Signing Key Persistence ## Signing Key Persistence

View file

@ -4,17 +4,43 @@ import (
"log" "log"
"os" "os"
"strconv" "strconv"
"time"
) )
const ( const (
DefaultPort = 3200 DefaultPort = 3200
DefaultFrontendPath = "/site/selfhost-web" DefaultFrontendPath = "/site/selfhost-web"
DevFrontendPath = "../dist" DevFrontendPath = "../dist"
DefaultReadTimeout = 15 * time.Second
DefaultWriteTimeout = 15 * time.Second
DefaultIdleTimeout = 60 * time.Second
) )
type Config struct { type Config struct {
Port int Port int
FrontendPath string FrontendPath string
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
// parseDuration reads a Go duration string from an env var (e.g. "30s", "2m", "0").
// A value of "0" is valid and disables the timeout (net/http semantics).
// If the variable is unset or invalid, it logs a warning and returns the fallback.
func parseDuration(envKey string, fallback time.Duration) time.Duration {
if s := os.Getenv(envKey); s != "" {
if d, err := time.ParseDuration(s); err == nil && d >= 0 {
if d == 0 {
log.Printf("Using %s from environment: %s (timeout disabled)", envKey, s)
} else {
log.Printf("Using %s from environment: %s", envKey, s)
}
return d
}
log.Printf("Warning: Invalid %s value '%s', using default %v", envKey, s, fallback)
}
return fallback
} }
// Load reads config from env vars with sensible defaults // Load reads config from env vars with sensible defaults
@ -46,5 +72,9 @@ func Load() *Config {
log.Println("Running in production mode, using frontend path: /site/selfhost-web") log.Println("Running in production mode, using frontend path: /site/selfhost-web")
} }
cfg.ReadTimeout = parseDuration("WEBAPP_SERVER_READ_TIMEOUT", DefaultReadTimeout)
cfg.WriteTimeout = parseDuration("WEBAPP_SERVER_WRITE_TIMEOUT", DefaultWriteTimeout)
cfg.IdleTimeout = parseDuration("WEBAPP_SERVER_IDLE_TIMEOUT", DefaultIdleTimeout)
return cfg return cfg
} }

View file

@ -62,14 +62,15 @@ func main() {
addr := fmt.Sprintf(":%d", cfg.Port) addr := fmt.Sprintf(":%d", cfg.Port)
// NOTE: these timeouts are pretty conservative // NOTE: timeouts default to conservative values but can be overridden
// bump them if you're serving huge bundles over slow connections // 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{ httpServer := &http.Server{
Addr: addr, Addr: addr,
Handler: mux, Handler: mux,
ReadTimeout: 15 * time.Second, ReadTimeout: cfg.ReadTimeout,
WriteTimeout: 15 * time.Second, WriteTimeout: cfg.WriteTimeout,
IdleTimeout: 60 * time.Second, IdleTimeout: cfg.IdleTimeout,
} }
go func() { go func() {