Skip to content

Commit a5a16ee

Browse files
committed
add http server middleware examples
1 parent e47c72c commit a5a16ee

File tree

1 file changed

+244
-0
lines changed

1 file changed

+244
-0
lines changed

expert/webMiddleware.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"compress/gzip"
6+
"crypto/sha1"
7+
"encoding/base64"
8+
"fmt"
9+
"io"
10+
"log"
11+
"net/http"
12+
"sync"
13+
"time"
14+
15+
"github.com/ulule/limiter"
16+
"github.com/ulule/limiter/drivers/store/memory"
17+
)
18+
19+
func main() {
20+
// Define route handlers
21+
homeHandler := http.HandlerFunc(home)
22+
profileHandler := http.HandlerFunc(profile)
23+
24+
// Apply the combined middleware to your route handlers
25+
http.Handle("/", applyMiddleware(homeHandler))
26+
http.Handle("/profile", applyMiddleware(profileHandler))
27+
28+
http.ListenAndServe(":8080", nil)
29+
}
30+
31+
// home and profile are our route handlers
32+
func home(w http.ResponseWriter, r *http.Request) {
33+
fmt.Fprintf(w, "Welcome to the home page!")
34+
}
35+
36+
func profile(w http.ResponseWriter, r *http.Request) {
37+
fmt.Fprintf(w, "Welcome to the profile page!")
38+
}
39+
40+
// Combine multiple middlewares into a single applyMiddleware function
41+
func applyMiddleware(handler http.Handler) http.Handler {
42+
return timingMiddleware(
43+
loggingMiddleware(
44+
corsMiddleware(
45+
gzipMiddleware(handler),
46+
),
47+
),
48+
)
49+
}
50+
51+
// various middleware implementations:
52+
53+
// timingMiddleware logs the time taken to process a request
54+
func timingMiddleware(next http.Handler) http.Handler {
55+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56+
start := time.Now()
57+
next.ServeHTTP(w, r)
58+
elapsed := time.Since(start)
59+
log.Printf("Elapsed time: %v\n", elapsed)
60+
})
61+
}
62+
63+
// loggingMiddleware logs the request method and URL
64+
func loggingMiddleware(next http.Handler) http.Handler {
65+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
log.Printf("Request received: %s %s\n", r.Method, r.URL)
67+
next.ServeHTTP(w, r)
68+
})
69+
}
70+
71+
// gzipMiddleware compresses the response body
72+
func gzipMiddleware(next http.Handler) http.Handler {
73+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74+
w.Header().Set("Content-Encoding", "gzip")
75+
gw := gzip.NewWriter(w)
76+
defer gw.Close()
77+
grw := gzipResponseWriter{ResponseWriter: w, Writer: gw}
78+
next.ServeHTTP(grw, r)
79+
})
80+
}
81+
82+
type gzipResponseWriter struct {
83+
http.ResponseWriter
84+
io.Writer
85+
}
86+
87+
func (grw gzipResponseWriter) Write(data []byte) (int, error) {
88+
return grw.Writer.Write(data)
89+
}
90+
91+
// corsMiddleware adds the Access-Control-Allow-Origin header
92+
func corsMiddleware(next http.Handler) http.Handler {
93+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
94+
w.Header().Set("Access-Control-Allow-Origin", "*")
95+
next.ServeHTTP(w, r)
96+
})
97+
}
98+
99+
// rateLimitMiddleware limits the number of requests per minute
100+
func rateLimitMiddleware(next http.Handler) http.Handler {
101+
rate := limiter.Rate{
102+
Period: 1 * time.Minute,
103+
Limit: 10,
104+
}
105+
store := memory.NewStore()
106+
instance := limiter.New(store, rate)
107+
108+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109+
limiterCtx, err := instance.Get(r.Context(), r.RemoteAddr)
110+
if err != nil {
111+
http.Error(w, "Too many requests", http.StatusTooManyRequests)
112+
return
113+
}
114+
w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", limiterCtx.Limit))
115+
w.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", limiterCtx.Remaining))
116+
w.Header().Set("X-RateLimit-Reset", fmt.Sprintf("%d", limiterCtx.Reset))
117+
118+
if limiterCtx.Reached {
119+
http.Error(w, "Too many requests", http.StatusTooManyRequests)
120+
return
121+
}
122+
123+
next.ServeHTTP(w, r)
124+
})
125+
}
126+
127+
// authenticationMiddleware checks for a valid Authorization header
128+
func authenticationMiddleware(next http.Handler) http.Handler {
129+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
130+
token := r.Header.Get("Authorization")
131+
if token != "Bearer my-secret-token" {
132+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
133+
return
134+
}
135+
next.ServeHTTP(w, r)
136+
})
137+
}
138+
139+
// contentTypeJsonMiddleware sets the Content-Type header to application/json
140+
func contentTypeJsonMiddleware(next http.Handler) http.Handler {
141+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
142+
w.Header().Set("Content-Type", "application/json")
143+
next.ServeHTTP(w, r)
144+
})
145+
}
146+
147+
// panicRecoveryMiddleware recovers from panics and returns a 500 error
148+
func panicRecoveryMiddleware(next http.Handler) http.Handler {
149+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
150+
defer func() {
151+
if err := recover(); err != nil {
152+
log.Printf("Recovered from panic: %v", err)
153+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
154+
}
155+
}()
156+
next.ServeHTTP(w, r)
157+
})
158+
}
159+
160+
// cacheItem stores a response and its timestamp
161+
type cacheItem struct {
162+
response []byte
163+
timestamp time.Time
164+
}
165+
166+
// cacheStorage is a simple in-memory cache
167+
type cacheStorage struct {
168+
data map[string]*cacheItem
169+
mutex sync.RWMutex
170+
}
171+
172+
// newCacheStorage creates a new cacheStorage
173+
func newCacheStorage() *cacheStorage {
174+
return &cacheStorage{data: make(map[string]*cacheItem)}
175+
}
176+
177+
func (cs *cacheStorage) get(key string) (value []byte, found bool) {
178+
cs.mutex.RLock()
179+
defer cs.mutex.RUnlock()
180+
181+
item, exists := cs.data[key]
182+
if exists && !isExpired(item.timestamp) {
183+
return item.response, true
184+
}
185+
return nil, false
186+
}
187+
188+
func (cs *cacheStorage) set(key string, value []byte) {
189+
cs.mutex.Lock()
190+
defer cs.mutex.Unlock()
191+
192+
cs.data[key] = &cacheItem{response: value, timestamp: time.Now()}
193+
}
194+
195+
func isExpired(timestamp time.Time) bool {
196+
return time.Since(timestamp) > 10*time.Minute
197+
}
198+
199+
var cache = newCacheStorage()
200+
201+
// cacheMiddleware is a middleware that caches responses
202+
func cacheMiddleware(next http.HandlerFunc) http.HandlerFunc {
203+
return func(w http.ResponseWriter, r *http.Request) {
204+
cacheKey := generateCacheKey(r)
205+
if response, found := cache.get(cacheKey); found {
206+
w.Write(response)
207+
return
208+
}
209+
210+
recorder := newResponseRecorder()
211+
next.ServeHTTP(recorder, r)
212+
response := recorder.Bytes()
213+
214+
cache.set(cacheKey, response)
215+
w.Write(response)
216+
}
217+
}
218+
219+
// generateCacheKey generates a cache key from a request
220+
func generateCacheKey(r *http.Request) string {
221+
hasher := sha1.New()
222+
io.WriteString(hasher, r.URL.String())
223+
io.WriteString(hasher, r.Method)
224+
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))
225+
}
226+
227+
type responseRecorder struct {
228+
http.ResponseWriter
229+
buffer bytes.Buffer
230+
}
231+
232+
// newResponseRecorder creates a new responseRecorder
233+
func newResponseRecorder() *responseRecorder {
234+
return &responseRecorder{}
235+
}
236+
237+
func (rr *responseRecorder) Write(data []byte) (int, error) {
238+
rr.buffer.Write(data)
239+
return rr.ResponseWriter.Write(data)
240+
}
241+
242+
func (rr *responseRecorder) Bytes() []byte {
243+
return rr.buffer.Bytes()
244+
}

0 commit comments

Comments
 (0)