Skip to content

Commit 433ac9a

Browse files
authored
Add HTTP middleware for retry handling (#4)
* feat(http): add HTTP middleware for retry handling * feat(examples): add router integration examples for chi and routegroup * fix(http): address golangci-lint issues and nil pointer dereference - Fix bodyclose linter warning by properly closing response body - Handle error return values to satisfy errcheck linter - Prevent nil pointer dereference by checking result.Body before Close() - Fix formatting issues in examples
1 parent 02d75c4 commit 433ac9a

21 files changed

Lines changed: 1156 additions & 202 deletions

File tree

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,60 @@ req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
143143
resp, err := ebo.HTTPDo(req, http.DefaultClient, ebo.API())
144144
```
145145

146+
### HTTP Middleware
147+
148+
EBO provides HTTP middleware that automatically retries requests based on response codes.
149+
150+
```go
151+
// Create a handler
152+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
153+
// Your API logic here
154+
})
155+
156+
// Wrap with retry middleware (retries on 5xx and 429 by default)
157+
retryHandler := ebo.NewRetryMiddleware(handler, ebo.DefaultResponseChecker,
158+
ebo.Initial(500*time.Millisecond),
159+
ebo.Tries(5),
160+
ebo.Jitter(0.3),
161+
)
162+
163+
// Use with standard HTTP server
164+
http.ListenAndServe(":8080", retryHandler)
165+
166+
// Or use the middleware function for router compatibility
167+
middleware := ebo.Middleware(ebo.DefaultResponseChecker, ebo.API())
168+
http.Handle("/api/", middleware(handler))
169+
170+
// Custom response checker
171+
customChecker := func(resp *http.Response) bool {
172+
return resp.StatusCode >= 500 || resp.StatusCode == 404
173+
}
174+
customMiddleware := ebo.Middleware(customChecker, ebo.Quick())
175+
```
176+
177+
### Router Integration
178+
179+
EBO's middleware works seamlessly with popular Go routers:
180+
181+
```go
182+
// Chi router
183+
import "github.com/go-chi/chi/v5"
184+
185+
r := chi.NewRouter()
186+
r.Use(ebo.Middleware(ebo.DefaultResponseChecker, ebo.API()))
187+
r.Get("/api/users", usersHandler)
188+
189+
// RouteGroup
190+
import "github.com/go-pkgz/routegroup"
191+
192+
router := routegroup.New(http.NewServeMux())
193+
apiGroup := router.Group()
194+
apiGroup.Use(ebo.Middleware(ebo.DefaultResponseChecker, ebo.Quick()))
195+
apiGroup.HandleFunc("GET /api/data", dataHandler)
196+
```
197+
198+
See [examples/router-integration](examples/router-integration) for complete examples with chi and routegroup.
199+
146200
## Iterator Pattern (Go 1.23+)
147201

148202
EBO now supports the new Go iterator pattern for more flexible and elegant retry loops.

doc.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727
//
2828
// // Quick retries for fast operations
2929
// err := ebo.Retry(checkCache, ebo.Quick())
30-
//
30+
//
3131
// // API calls with moderate retry
3232
// err := ebo.Retry(callAPI, ebo.API())
33-
//
33+
//
3434
// // Database operations with longer timeouts
3535
// err := ebo.Retry(connectDB, ebo.Database())
36-
//
36+
//
3737
// // HTTP requests with status code awareness
3838
// client := ebo.NewHTTPClient(ebo.HTTPStatus())
3939
//
@@ -46,4 +46,4 @@
4646
// - Iterator pattern for stateful retries (using Go 1.23+ iter package)
4747
//
4848
// For more examples and documentation, visit https://github.com/flaticols/ebo
49-
package ebo
49+
package ebo

example_doc_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func ExampleRetry() {
1515
fmt.Println("Attempting operation...")
1616
return errors.New("temporary failure")
1717
}, ebo.Tries(1))
18-
18+
1919
if err != nil {
2020
fmt.Printf("Operation failed: %v\n", err)
2121
}
@@ -33,7 +33,7 @@ func ExampleQuickRetry() {
3333
}
3434
return nil
3535
})
36-
36+
3737
if err == nil {
3838
fmt.Println("Success after", attempts, "attempts")
3939
}
@@ -44,12 +44,12 @@ func ExampleQuickRetry() {
4444
func ExampleRetryWithContext() {
4545
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
4646
defer cancel()
47-
47+
4848
err := ebo.RetryWithContext(ctx, func() error {
4949
fmt.Println("Trying with context...")
5050
return nil
5151
}, ebo.Initial(100*time.Millisecond))
52-
52+
5353
if err == nil {
5454
fmt.Println("Success!")
5555
}
@@ -84,12 +84,12 @@ func ExampleInitial() {
8484
// Show configuration with Initial option
8585
err := ebo.Retry(func() error {
8686
return nil
87-
},
87+
},
8888
ebo.Initial(1*time.Second),
8989
ebo.Tries(3),
9090
ebo.NoJitter(),
9191
)
92-
92+
9393
if err == nil {
9494
fmt.Println("Configured successfully")
9595
}
@@ -103,7 +103,7 @@ func ExampleAPI() {
103103
fmt.Println("Using API preset")
104104
return nil
105105
}, ebo.API())
106-
106+
107107
if err == nil {
108108
fmt.Println("API preset works")
109109
}
@@ -114,7 +114,7 @@ func ExampleAPI() {
114114

115115
func ExampleRetryWithLogging() {
116116
logger := log.New(log.Writer(), "[RETRY] ", 0)
117-
117+
118118
attempts := 0
119119
err := ebo.RetryWithLogging(func() error {
120120
attempts++
@@ -123,10 +123,10 @@ func ExampleRetryWithLogging() {
123123
}
124124
return nil
125125
}, logger, ebo.Tries(3), ebo.Initial(10*time.Millisecond))
126-
126+
127127
if err == nil {
128128
fmt.Println("Success with logging")
129129
}
130130
// Output:
131131
// Success with logging
132-
}
132+
}

example_test.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package ebo
22

33
import (
4-
"fmt"
54
"errors"
5+
"fmt"
66
"net/http"
77
"time"
88
)
99

1010
func ExampleRetry() {
1111
// Example: Retry an HTTP request with custom options
1212
var response *http.Response
13-
13+
1414
err := Retry(func() error {
1515
resp, err := http.Get("https://api.example.com/data")
1616
if err != nil {
@@ -33,7 +33,7 @@ func ExampleRetry() {
3333
fmt.Printf("Failed after retries: %v\n", err)
3434
return
3535
}
36-
36+
3737
defer func() { _ = response.Body.Close() }()
3838
// Process response...
3939
}
@@ -48,7 +48,7 @@ func ExampleRetry_withTimeout() {
4848
MaxTime(5*time.Second),
4949
Tries(0), // No retry limit, only time limit
5050
)
51-
51+
5252
if err != nil {
5353
fmt.Printf("Operation failed within timeout: %v\n", err)
5454
}
@@ -63,9 +63,9 @@ func ExampleRetry_customBackoff() {
6363
Initial(100*time.Millisecond),
6464
Max(10*time.Second),
6565
Multiplier(3.0), // Triple the interval each time
66-
Jitter(0), // No jitter
66+
Jitter(0), // No jitter
6767
)
68-
68+
6969
if err != nil {
7070
fmt.Printf("Operation failed: %v\n", err)
7171
}
@@ -77,7 +77,7 @@ func ExampleQuickRetry() {
7777
// Your operation here
7878
return errors.New("temporary failure")
7979
})
80-
80+
8181
if err != nil {
8282
fmt.Printf("Operation failed: %v\n", err)
8383
}
@@ -89,7 +89,7 @@ func ExampleRetryWithBackoff() {
8989
// Your operation here
9090
return nil
9191
}, 3)
92-
92+
9393
if err != nil {
9494
fmt.Printf("Failed after 3 attempts: %v\n", err)
9595
}
@@ -98,38 +98,38 @@ func ExampleRetryWithBackoff() {
9898
func ExampleOption() {
9999
// Example: Creating reusable option sets
100100
fastRetryOptions := []Option{
101-
Initial(50*time.Millisecond),
102-
Max(500*time.Millisecond),
101+
Initial(50 * time.Millisecond),
102+
Max(500 * time.Millisecond),
103103
Tries(3),
104104
Multiplier(2.0),
105105
}
106-
106+
107107
robustRetryOptions := []Option{
108-
Initial(1*time.Second),
109-
Max(1*time.Minute),
108+
Initial(1 * time.Second),
109+
Max(1 * time.Minute),
110110
Tries(10),
111111
Multiplier(2.0),
112112
Jitter(0.5),
113-
MaxTime(10*time.Minute),
113+
MaxTime(10 * time.Minute),
114114
}
115-
115+
116116
// Use fast retry for quick operations
117117
err := Retry(func() error {
118118
// Quick operation
119119
return nil
120120
}, fastRetryOptions...)
121-
121+
122122
if err != nil {
123123
fmt.Printf("Fast retry failed: %v\n", err)
124124
}
125-
125+
126126
// Use robust retry for critical operations
127127
err = Retry(func() error {
128128
// Critical operation
129129
return nil
130130
}, robustRetryOptions...)
131-
131+
132132
if err != nil {
133133
fmt.Printf("Robust retry failed: %v\n", err)
134134
}
135-
}
135+
}

0 commit comments

Comments
 (0)