@@ -8,48 +8,60 @@ description = "Timeout middleware for Echo"
8
8
9
9
Timeout middleware is used to timeout at a long running operation within a predefined period.
10
10
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
+
11
17
* Usage*
12
18
13
- ` e.Use( middleware.Timeout()) `
19
+ ` e.GET("/", handlerFunc, middleware.Timeout()) `
14
20
15
21
## Custom Configuration
16
22
17
23
* Usage*
18
24
19
25
``` go
26
+ // Echo instance
20
27
e := 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)
30
39
```
31
40
32
41
## Configuration
33
42
34
43
``` go
35
44
// 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
46
48
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
+ }
53
65
```
54
66
55
67
* Default Configuration*
@@ -58,6 +70,59 @@ TimeoutErrorHandlerWithContext func(error, echo.Context) error
58
70
DefaultTimeoutConfig = TimeoutConfig {
59
71
Skipper : DefaultSkipper ,
60
72
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
+ }
62
127
}
63
128
```
0 commit comments