@@ -8,48 +8,60 @@ description = "Timeout middleware for Echo"
88
99Timeout middleware is used to timeout at a long running operation within a predefined period.
1010
11+ > Note: 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 not a magic wand to hide slow handlers from clients. Consider designing/implementing asynchronous
14+ > request/response API if (extremely) fast responses are to be expected and actual work can be done in background
15+ > Prefer handling timeouts in handler functions explicitly
16+
1117* Usage*
1218
13- ` e.Use( middleware.Timeout()) `
19+ ` e.GET("/", handlerFunc, middleware.Timeout()) `
1420
1521## Custom Configuration
1622
1723* Usage*
1824
1925``` go
26+ // Echo instance
2027e := 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- }))
28+
29+ handlerFunc := func (c echo.Context ) error {
30+ time.Sleep (10 * time.Second )
31+ return c.String (http.StatusOK , " Hello, World!\n " )
32+ }
33+ middlewareFunc := middleware.TimeoutWithConfig (middleware.TimeoutConfig {
34+ Timeout : 30 * time.Second ,
35+ ErrorMessage : " my custom error message" ,
36+ })
37+ // Handler with timeout middleware
38+ e.GET (" /" , handlerFunc, middlewareFunc)
3039```
3140
3241## Configuration
3342
3443``` go
3544// 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- ```
45+ type TimeoutConfig struct {
46+ // Skipper defines a function to skip middleware.
47+ Skipper Skipper
4648
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
49+ // ErrorMessage is written to response on timeout in addition to http.StatusServiceUnavailable (503) status code
50+ // It can be used to define a custom timeout error message
51+ ErrorMessage string
52+
53+ // OnTimeoutRouteErrorHandler is an error handler that is executed for error that was returned from wrapped route after
54+ // request timeouted and we already had sent the error code (503) and message response to the client.
55+ // NB: do not write headers/body inside this handler. The response has already been sent to the client and response writer
56+ // will not accept anything no more. If you want to know what actual route middleware timeouted use `c.Path()`
57+ OnTimeoutRouteErrorHandler func (err error , c echo.Context )
58+
59+ // Timeout configures a timeout for the middleware, defaults to 0 for no timeout
60+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
61+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
62+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
63+ Timeout time.Duration
64+ }
5365```
5466
5567* Default Configuration*
@@ -58,6 +70,59 @@ TimeoutErrorHandlerWithContext func(error, echo.Context) error
5870DefaultTimeoutConfig = TimeoutConfig {
5971 Skipper : DefaultSkipper ,
6072 Timeout : 0 ,
61- ErrorHandler : nil ,
73+ ErrorMessage : " " ,
74+ }
75+ ```
76+
77+ ## Alternatively handle timeouts in handlers
78+
79+ ``` go
80+ func main () {
81+ e := echo.New ()
82+
83+ doBusinessLogic := func (ctx context.Context , UID string ) error {
84+ // NB: Do not use echo.JSON() or any other method that writes data/headers to client here. This function is executed
85+ // in different coroutine that should not access echo.Context and response writer
86+
87+ log.Printf (" uid: %v \n " , UID)
88+ // res, err := slowDatabaseCon.ExecContext(ctx, query, args)
89+ time.Sleep (10 * time.Second ) // simulate slow execution
90+ log.Print (" doBusinessLogic done\n " )
91+ return nil
92+ }
93+
94+ handlerFunc := func (c echo.Context ) error {
95+ defer log.Print (" handlerFunc done\n " )
96+
97+ // extract and validate needed data from request and pass it to business function
98+ UID := c.QueryParam (" uid" )
99+
100+ ctx , cancel := context.WithTimeout (c.Request ().Context (), 5 * time.Second )
101+ defer cancel ()
102+ result := make (chan error )
103+ go func () { // run actual business logic in separate coroutine
104+ result <- doBusinessLogic (ctx, UID)
105+ }()
106+
107+ select { // wait until doBusinessLogic finishes or we timeout while waiting for the result
108+ case <- ctx.Done ():
109+ err := ctx.Err ()
110+ if err == context.DeadlineExceeded {
111+ return echo.NewHTTPError (http.StatusServiceUnavailable , " doBusinessLogic timeout" )
112+ }
113+ return err // probably client closed the connection
114+ case err := <- result: // doBusinessLogic finishes
115+ if err != nil {
116+ return err
117+ }
118+ }
119+ return c.NoContent (http.StatusAccepted )
120+ }
121+ e.GET (" /" , handlerFunc)
122+
123+ s := http.Server {Addr: " :8080" , Handler: e}
124+ if err := s.ListenAndServe (); err != http.ErrServerClosed {
125+ log.Fatal (err)
126+ }
62127}
63128```
0 commit comments