Add the four settings the README documented but pixa lacked (closes #61)
check / check (push) Successful in 15s

README.md documented access_control_allow_origin,
upstream_fetch_timeout, upstream_max_response_size and
downstream_timeout, but pixa did not know them, so a config following
the README aborted startup. Each is now a setting with its PIXA_
variable, defaulting to the value that was fixed in the code: *, 30s,
50 MiB and 60s. Durations are Go duration strings and must be positive;
the size is whole bytes, at most 1 GiB. The origin is * or one http or
https origin written exactly as a browser sends it; anything else
aborts startup. downstream_timeout sets both the server's write timeout
and the per-request timeout. The owner approved the edits to existing
tests.

Model: opus-5-5
This commit was merged in pull request #142.
This commit is contained in:
2026-09-29 08:49:18 +02:00
parent 46a55ec15d
commit 56217cbf4a
13 changed files with 571 additions and 18 deletions
+1 -2
View File
@@ -14,7 +14,6 @@ const (
// short, so a slowloris client dribbling headers is dropped well
// before it ties up a connection for the whole ReadTimeout window.
HTTPReadHeaderTimeout = 10 * time.Second
HTTPWriteTimeout = 60 * time.Second
// HTTPIdleTimeout bounds how long an idle keep-alive connection is
// held open, so idle connections cannot accumulate without limit on a
// service targeting high concurrency.
@@ -30,7 +29,7 @@ func (s *Server) newHTTPServer() *http.Server {
Addr: fmt.Sprintf(":%d", s.config.Port),
ReadTimeout: HTTPReadTimeout,
ReadHeaderTimeout: HTTPReadHeaderTimeout,
WriteTimeout: HTTPWriteTimeout,
WriteTimeout: s.config.DownstreamTimeout,
IdleTimeout: HTTPIdleTimeout,
MaxHeaderBytes: HTTPMaxHeaderBytes,
Handler: s,
+7 -3
View File
@@ -11,11 +11,15 @@ import (
// carries every hardening timeout wired onto it, including the slowloris
// defense (ReadHeaderTimeout) and the keep-alive bound (IdleTimeout). This
// guards against a field being defined but never set on the server, so
// each assertion compares the server field to its constant.
// each assertion compares the server field to its constant, or, for
// WriteTimeout, to downstream_timeout from the config.
func TestNewHTTPServerTimeouts(t *testing.T) {
t.Parallel()
s := &Server{config: &config.Config{Port: 8080}}
s := &Server{config: &config.Config{
Port: 8080,
DownstreamTimeout: 45 * time.Second,
}}
srv := s.newHTTPServer()
@@ -26,7 +30,7 @@ func TestNewHTTPServerTimeouts(t *testing.T) {
}{
{"ReadTimeout", srv.ReadTimeout, HTTPReadTimeout},
{"ReadHeaderTimeout", srv.ReadHeaderTimeout, HTTPReadHeaderTimeout},
{"WriteTimeout", srv.WriteTimeout, HTTPWriteTimeout},
{"WriteTimeout", srv.WriteTimeout, 45 * time.Second},
{"IdleTimeout", srv.IdleTimeout, HTTPIdleTimeout},
}
@@ -51,11 +51,12 @@ func newTestServer(t *testing.T) *Server {
stateDir := t.TempDir()
cfg := &config.Config{
Debug: true,
SigningKey: testSigningKey,
StateDir: stateDir,
DBURL: "file:" + filepath.Join(stateDir, "state.sqlite3"),
TrustedProxies: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")},
Debug: true,
SigningKey: testSigningKey,
StateDir: stateDir,
DBURL: "file:" + filepath.Join(stateDir, "state.sqlite3"),
TrustedProxies: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")},
DownstreamTimeout: config.DefaultDownstreamTimeout,
}
lc := fxtest.NewLifecycle(t)
+1 -1
View File
@@ -33,7 +33,7 @@ func (s *Server) SetupRoutes() {
}
s.router.Use(s.mw.CORS())
s.router.Use(middleware.Timeout(HTTPWriteTimeout))
s.router.Use(middleware.Timeout(s.config.DownstreamTimeout))
if s.sentryEnabled {
sentryHandler := sentryhttp.New(sentryhttp.Options{