mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-10 17:32:15 +01:00
fix: rework api and webhook module timeout properties (fixes #396)
This commit is contained in:
@@ -30,8 +30,8 @@ func init() {
|
||||
type API struct {
|
||||
port int
|
||||
readTimeout time.Duration
|
||||
processTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
timeout time.Duration
|
||||
rootPath string
|
||||
traceHeader string
|
||||
disableHealthCheckLogging bool
|
||||
@@ -166,10 +166,20 @@ func (API) Descriptor() gotenberg.ModuleDescriptor {
|
||||
fs.Duration("api-read-timeout", time.Duration(30)*time.Second, "Set the maximum duration allowed to read a complete request, including the body")
|
||||
fs.Duration("api-process-timeout", time.Duration(30)*time.Second, "Set the maximum duration allowed to process a request")
|
||||
fs.Duration("api-write-timeout", time.Duration(30)*time.Second, "Set the maximum duration before timing out writes of the response")
|
||||
fs.Duration("api-timeout", time.Duration(30)*time.Second, "Set the time limit for requests")
|
||||
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
|
||||
fs.String("api-trace-header", "Gotenberg-Trace", "Set the header name to use for identifying requests")
|
||||
fs.Bool("api-disable-health-check-logging", false, "Disable health check logging")
|
||||
|
||||
var err error
|
||||
err = multierr.Append(err, fs.MarkDeprecated("api-read-timeout", "use api-timeout instead"))
|
||||
err = multierr.Append(err, fs.MarkDeprecated("api-process-timeout", "use api-timeout instead"))
|
||||
err = multierr.Append(err, fs.MarkDeprecated("api-write-timeout", "use api-timeout instead"))
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("create deprecated flags for the api module: %v", err))
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
New: func() gotenberg.Module { return new(API) },
|
||||
@@ -180,9 +190,9 @@ func (API) Descriptor() gotenberg.ModuleDescriptor {
|
||||
func (a *API) Provision(ctx *gotenberg.Context) error {
|
||||
flags := ctx.ParsedFlags()
|
||||
a.port = flags.MustInt("api-port")
|
||||
a.readTimeout = flags.MustDuration("api-read-timeout")
|
||||
a.processTimeout = flags.MustDuration("api-process-timeout")
|
||||
a.writeTimeout = flags.MustDuration("api-write-timeout")
|
||||
a.readTimeout = flags.MustDeprecatedDuration("api-read-timeout", "api-timeout")
|
||||
a.writeTimeout = flags.MustDeprecatedDuration("api-write-timeout", "api-timeout")
|
||||
a.timeout = flags.MustDeprecatedDuration("api-process-timeout", "api-timeout")
|
||||
a.rootPath = flags.MustString("api-root-path")
|
||||
a.traceHeader = flags.MustString("api-trace-header")
|
||||
a.disableHealthCheckLogging = flags.MustBool("api-disable-health-check-logging")
|
||||
@@ -274,7 +284,7 @@ func (a *API) Provision(ctx *gotenberg.Context) error {
|
||||
}
|
||||
|
||||
// Grace duration.
|
||||
a.gcGraceDuration = a.readTimeout + a.processTimeout + a.writeTimeout
|
||||
a.gcGraceDuration = a.timeout
|
||||
|
||||
mods, err = ctx.Modules(new(GarbageCollectorGraceDurationIncrementer))
|
||||
if err != nil {
|
||||
@@ -378,7 +388,9 @@ func (a *API) Start() error {
|
||||
a.srv.HideBanner = true
|
||||
a.srv.HidePort = true
|
||||
a.srv.Server.ReadTimeout = a.readTimeout
|
||||
a.srv.Server.WriteTimeout = a.writeTimeout
|
||||
a.srv.Server.IdleTimeout = a.timeout
|
||||
// See https://github.com/gotenberg/gotenberg/issues/396.
|
||||
a.srv.Server.WriteTimeout = a.writeTimeout + a.writeTimeout
|
||||
a.srv.HTTPErrorHandler = httpErrorHandler()
|
||||
|
||||
// Let's prepare the modules' routes.
|
||||
@@ -402,7 +414,6 @@ func (a *API) Start() error {
|
||||
latencyMiddleware(),
|
||||
rootPathMiddleware(a.rootPath),
|
||||
traceMiddleware(a.traceHeader),
|
||||
timeoutsMiddleware(a.readTimeout, a.processTimeout, a.writeTimeout),
|
||||
loggerMiddleware(a.logger, disableLoggingForPaths),
|
||||
)
|
||||
|
||||
@@ -419,14 +430,14 @@ func (a *API) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
hardTimeout := a.processTimeout + (time.Duration(5) * time.Second)
|
||||
hardTimeout := a.timeout + (time.Duration(5) * time.Second)
|
||||
|
||||
// Add the modules' routes and their specific middlewares.
|
||||
for _, route := range a.routes {
|
||||
var middlewares []echo.MiddlewareFunc
|
||||
|
||||
if route.IsMultipart {
|
||||
middlewares = append(middlewares, contextMiddleware(a.processTimeout))
|
||||
middlewares = append(middlewares, contextMiddleware(a.timeout))
|
||||
|
||||
for _, externalMultipartMiddleware := range externalMultipartMiddlewares {
|
||||
middlewares = append(middlewares, externalMultipartMiddleware.Handler)
|
||||
@@ -447,7 +458,7 @@ func (a *API) Start() error {
|
||||
a.srv.GET(
|
||||
fmt.Sprintf("%s%s", a.rootPath, "health"),
|
||||
func() echo.HandlerFunc {
|
||||
checks := append(a.healthChecks, health.WithTimeout(a.processTimeout))
|
||||
checks := append(a.healthChecks, health.WithTimeout(a.timeout))
|
||||
checker := health.NewChecker(checks...)
|
||||
|
||||
return echo.WrapHandler(health.NewHandler(checker))
|
||||
|
||||
@@ -386,7 +386,7 @@ func TestAPI_Provision(t *testing.T) {
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectGraceDuration: time.Duration(93) * time.Second,
|
||||
expectGraceDuration: time.Duration(33) * time.Second,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -120,25 +120,6 @@ func traceMiddleware(header string) echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// timeoutsMiddleware sets the read, process and write timeouts in the
|
||||
// echo.Context under "readTimeout", "processTimeout" and "writeTimeout".
|
||||
//
|
||||
// readTimeout := c.Get("readTimeout").(time.Duration)
|
||||
// processTimeout := c.Get("processTimeout").(time.Duration)
|
||||
// writeTimeout := c.Get("writeTimeout").(time.Duration)
|
||||
func timeoutsMiddleware(readTimeout, processTimeout, writeTimeout time.Duration) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
c.Set("readTimeout", readTimeout)
|
||||
c.Set("processTimeout", processTimeout)
|
||||
c.Set("writeTimeout", writeTimeout)
|
||||
|
||||
// Call the next middleware in the chain.
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loggerMiddleware sets the logger in the echo.Context under "logger" and logs
|
||||
// a synchronous request result.
|
||||
//
|
||||
@@ -218,14 +199,14 @@ func loggerMiddleware(logger *zap.Logger, disableLoggingForPaths []string) echo.
|
||||
//
|
||||
// ctx := c.Get("context").(*api.Context)
|
||||
// cancel := c.Get("cancel").(context.CancelFunc)
|
||||
func contextMiddleware(processTimeout time.Duration) echo.MiddlewareFunc {
|
||||
func contextMiddleware(timeout time.Duration) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
logger := c.Get("logger").(*zap.Logger)
|
||||
|
||||
// We create a context with a timeout so that underlying processes are
|
||||
// able to stop early and handle correctly a timeout scenario.
|
||||
ctx, cancel, err := newContext(c, logger, processTimeout)
|
||||
ctx, cancel, err := newContext(c, logger, timeout)
|
||||
if err != nil {
|
||||
cancel()
|
||||
|
||||
|
||||
@@ -222,47 +222,6 @@ func TestTraceMiddleware(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutsMiddleware(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(request, recorder)
|
||||
|
||||
expectReadTimeout := time.Duration(1) * time.Second
|
||||
expectProcessTimeout := time.Duration(2) * time.Second
|
||||
expectWriteTimeout := time.Duration(3) * time.Second
|
||||
|
||||
err := timeoutsMiddleware(expectReadTimeout, expectProcessTimeout, expectWriteTimeout)(
|
||||
func(c echo.Context) error {
|
||||
return nil
|
||||
},
|
||||
)(c)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
actualReadTimeout := c.Get("readTimeout").(time.Duration)
|
||||
actualProcessTimeout := c.Get("processTimeout").(time.Duration)
|
||||
actualWriteTimeout := c.Get("writeTimeout").(time.Duration)
|
||||
|
||||
if actualReadTimeout != expectReadTimeout {
|
||||
t.Errorf("expected '%s' but got '%s", expectReadTimeout, actualReadTimeout)
|
||||
}
|
||||
|
||||
if actualProcessTimeout != expectProcessTimeout {
|
||||
t.Errorf("expected '%s' but got '%s", expectProcessTimeout, actualProcessTimeout)
|
||||
}
|
||||
|
||||
if actualWriteTimeout != expectWriteTimeout {
|
||||
t.Errorf("expected '%s' but got '%s", actualWriteTimeout, expectWriteTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerMiddleware(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
request *http.Request
|
||||
|
||||
Reference in New Issue
Block a user