From c381c7821f2aa72917a87ef4d8f60aa87e18b06a Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Thu, 12 Nov 2026 00:47:58 +0000 Subject: [PATCH 1/2] fix: set HTTP server connection timeouts --- README.md | 5 +++++ main.go | 6 ++++++ timeouts_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 timeouts_test.go diff --git a/README.md b/README.md index 41f73c1..ff7ec11 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ The OpenAPI spec is in [`publiccode-validator-api.oas.yaml`](publiccode-validato go run main.go ``` +Requests must be fully received within 30 seconds (up to the default 4 MiB +body limit). Response writes have a 30 second timeout after validation +finishes, including any external checks. Idle keepalive connections close +after 60 seconds. These connection timeouts do not limit validation execution. + ### Validate a publiccode.yml file ```console diff --git a/main.go b/main.go index 1bb3b6d..828d044 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/cors" @@ -28,6 +29,11 @@ func Setup() *fiber.App { app := fiber.New(fiber.Config{ ErrorHandler: common.CustomErrorHandler, + // Allow 30 seconds to upload the default maximum body of 4 MiB. + ReadTimeout: 30 * time.Second, + // This bounds response writes after the handler, including external checks, returns. + WriteTimeout: 30 * time.Second, + IdleTimeout: 60 * time.Second, // Fiber doesn't set DisallowUnknownFields by default // (https://github.com/gofiber/fiber/issues/2601) JSONDecoder: jsondecoder.UnmarshalDisallowUnknownFields, diff --git a/timeouts_test.go b/timeouts_test.go new file mode 100644 index 0000000..cdec9ea --- /dev/null +++ b/timeouts_test.go @@ -0,0 +1,52 @@ +package main + +import ( + "io" + "net" + "testing" + "time" + + "github.com/gofiber/fiber/v3" + "github.com/stretchr/testify/require" +) + +func TestStalledRequests(t *testing.T) { + // Setup writes package globals in libpubliccode, so the subtests share + // one server instead of each building their own in parallel. + server := Setup() + config := server.Config() + require.Equal(t, 30*time.Second, config.ReadTimeout) + require.Equal(t, 30*time.Second, config.WriteTimeout) + require.Equal(t, 60*time.Second, config.IdleTimeout) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + done := make(chan error, 1) + go func() { + done <- server.Listener(listener, fiber.ListenConfig{DisableStartupMessage: true}) + }() + t.Cleanup(func() { + require.NoError(t, server.Shutdown()) + require.NoError(t, <-done) + }) + + for name, request := range map[string]string{ + "headers": "GET /v1/status HTTP/1.1\r\nHost: localhost\r\n", + "body": "POST /v1/validate HTTP/1.1\r\nHost: localhost\r\nContent-Length: 100\r\n\r\npubliccodeYmlVersion:", + } { + t.Run(name, func(t *testing.T) { + t.Parallel() + + conn, err := net.DialTimeout("tcp", listener.Addr().String(), time.Second) + require.NoError(t, err) + defer conn.Close() + require.NoError(t, conn.SetDeadline(time.Now().Add(config.ReadTimeout+5*time.Second))) + _, err = io.WriteString(conn, request) + require.NoError(t, err) + + // The server may send a timeout response before closing the connection. + _, err = io.Copy(io.Discard, conn) + require.NoError(t, err, "server must close the connection before the client deadline") + }) + } +} From 67e61f808ad2593adbc372953a358b3ec0be2e21 Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Thu, 12 Nov 2026 16:28:17 +0000 Subject: [PATCH 2/2] doc: remove connection timeout details from README --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index ff7ec11..41f73c1 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,6 @@ The OpenAPI spec is in [`publiccode-validator-api.oas.yaml`](publiccode-validato go run main.go ``` -Requests must be fully received within 30 seconds (up to the default 4 MiB -body limit). Response writes have a 30 second timeout after validation -finishes, including any external checks. Idle keepalive connections close -after 60 seconds. These connection timeouts do not limit validation execution. - ### Validate a publiccode.yml file ```console