@@ -8,48 +8,62 @@ description = "Timeout middleware for Echo"
88
99Timeout middleware is used to timeout at a long running operation within a predefined period.
1010
11+ When timeout occurs, and the client receives timeout response the handler keeps running its code and keeps using resources until it finishes and returns!
12+
13+ > Timeout middleware is a serious performance hit as it buffers all responses from wrapped handler. Do not set it in front of file downloads or responses you want to stream to the client.
14+
15+ Timeout middleware is not a magic wand to hide slow handlers from clients. Consider designing/implementing asynchronous
16+ request/response API if (extremely) fast responses are to be expected and actual work can be done in background
17+ Prefer handling timeouts in handler functions explicitly
18+
1119* Usage*
1220
13- ` e.Use( middleware.Timeout()) `
21+ ` e.GET("/", handlerFunc, middleware.Timeout()) `
1422
1523## Custom Configuration
1624
1725* Usage*
1826
1927``` go
28+ // Echo instance
2029e := echo.New ()
21- e.Use (middleware.TimeoutWithConfig (middleware.TimeoutConfig {
22- Skipper : Skipper ,
23- ErrorHandler : func (err error , e echo.Context ) error {
24- // you can handle your error here, the returning error will be
25- // passed down the middleware chain
26- return err
27- },
28- Timeout : 30 *time.Second ,
29- }))
30+
31+ handlerFunc := func (c echo.Context ) error {
32+ time.Sleep (10 * time.Second )
33+ return c.String (http.StatusOK , " Hello, World!\n " )
34+ }
35+ middlewareFunc := middleware.TimeoutWithConfig (middleware.TimeoutConfig {
36+ Timeout : 30 * time.Second ,
37+ ErrorMessage : " my custom error message" ,
38+ })
39+ // Handler with timeout middleware
40+ e.GET (" /" , handlerFunc, middlewareFunc)
3041```
3142
3243## Configuration
3344
3445``` go
3546// TimeoutConfig defines the config for Timeout middleware.
36- TimeoutConfig struct {
37- // Skipper defines a function to skip middleware.
38- Skipper Skipper
39- // ErrorHandler defines a function which is executed for a timeout
40- // It can be used to define a custom timeout error
41- ErrorHandler TimeoutErrorHandlerWithContext
42- // Timeout configures a timeout for the middleware, defaults to 0 for no timeout
43- Timeout time.Duration
44- }
45- ```
47+ type TimeoutConfig struct {
48+ // Skipper defines a function to skip middleware.
49+ Skipper Skipper
4650
47- * TimeoutErrorHandlerWithContext* is responsible for handling the errors when a timeout happens
48- ``` go
49- // TimeoutErrorHandlerWithContext is an error handler that is used
50- // with the timeout middleware so we can handle the error
51- // as we see fit
52- TimeoutErrorHandlerWithContext func (error , echo.Context ) error
51+ // ErrorMessage is written to response on timeout in addition to http.StatusServiceUnavailable (503) status code
52+ // It can be used to define a custom timeout error message
53+ ErrorMessage string
54+
55+ // OnTimeoutRouteErrorHandler is an error handler that is executed for error that was returned from wrapped route after
56+ // request timeouted and we already had sent the error code (503) and message response to the client.
57+ // NB: do not write headers/body inside this handler. The response has already been sent to the client and response writer
58+ // will not accept anything no more. If you want to know what actual route middleware timeouted use `c.Path()`
59+ OnTimeoutRouteErrorHandler func (err error , c echo.Context )
60+
61+ // Timeout configures a timeout for the middleware, defaults to 0 for no timeout
62+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
63+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
64+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
65+ Timeout time.Duration
66+ }
5367```
5468
5569* Default Configuration*
@@ -58,6 +72,64 @@ TimeoutErrorHandlerWithContext func(error, echo.Context) error
5872DefaultTimeoutConfig = TimeoutConfig {
5973 Skipper : DefaultSkipper ,
6074 Timeout : 0 ,
61- ErrorHandler : nil ,
75+ ErrorMessage : " " ,
76+ }
77+ ```
78+
79+ ## Alternatively handle timeouts in handlers
80+
81+ ``` go
82+ func main () {
83+ e := echo.New ()
84+
85+ doBusinessLogic := func (ctx context.Context , UID string ) error {
86+ // NB: Do not use echo.JSON() or any other method that writes data/headers to client here. This function is executed
87+ // in different coroutine that should not access echo.Context and response writer
88+
89+ log.Printf (" uid: %v \n " , UID)
90+ // res, err := slowDatabaseCon.ExecContext(ctx, query, args)
91+ time.Sleep (10 * time.Second ) // simulate slow execution
92+ log.Print (" doBusinessLogic done\n " )
93+ return nil
94+ }
95+
96+ handlerFunc := func (c echo.Context ) error {
97+ defer log.Print (" handlerFunc done\n " )
98+
99+ // extract and validate needed data from request and pass it to business function
100+ UID := c.QueryParam (" uid" )
101+
102+ ctx , cancel := context.WithTimeout (c.Request ().Context (), 5 * time.Second )
103+ defer cancel ()
104+ result := make (chan error )
105+ go func () { // run actual business logic in separate coroutine
106+ defer func () { // unhandled panic in coroutine will crash the whole application
107+ if err := recover (); err != nil {
108+ result <- fmt.Errorf (" panic: %v " , err)
109+ }
110+ }()
111+ result <- doBusinessLogic (ctx, UID)
112+ }()
113+
114+ select { // wait until doBusinessLogic finishes or we timeout while waiting for the result
115+ case <- ctx.Done ():
116+ err := ctx.Err ()
117+ if err == context.DeadlineExceeded {
118+ return echo.NewHTTPError (http.StatusServiceUnavailable , " doBusinessLogic timeout" )
119+ }
120+ return err // probably client closed the connection
121+ case err := <- result: // doBusinessLogic finishes
122+ if err != nil {
123+ return err
124+ }
125+ }
126+ return c.NoContent (http.StatusAccepted )
127+ }
128+ e.GET (" /" , handlerFunc)
129+
130+ s := http.Server {Addr: " :8080" , Handler: e}
131+ if err := s.ListenAndServe (); err != http.ErrServerClosed {
132+ log.Fatal (err)
133+ }
62134}
63135```
0 commit comments