@@ -8,48 +8,62 @@ 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
+ 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
+
11
19
* Usage*
12
20
13
- ` e.Use( middleware.Timeout()) `
21
+ ` e.GET("/", handlerFunc, middleware.Timeout()) `
14
22
15
23
## Custom Configuration
16
24
17
25
* Usage*
18
26
19
27
``` go
28
+ // Echo instance
20
29
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
- }))
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)
30
41
```
31
42
32
43
## Configuration
33
44
34
45
``` go
35
46
// 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
46
50
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
+ }
53
67
```
54
68
55
69
* Default Configuration*
@@ -58,6 +72,64 @@ TimeoutErrorHandlerWithContext func(error, echo.Context) error
58
72
DefaultTimeoutConfig = TimeoutConfig {
59
73
Skipper : DefaultSkipper ,
60
74
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
+ }
62
134
}
63
135
```
0 commit comments