gitlab.com/infor-cloud/martian-cloud/tharsis/go-limiter@v0.0.0-20230411193226-3247984d5abc/httplimit/middleware.go (about) 1 // Package httplimit provides middleware for rate limiting HTTP handlers. 2 // 3 // The implementation is designed to work with Go's built-in http.Handler and 4 // http.HandlerFunc interfaces, so it will also work with any popular web 5 // frameworks that support middleware with these properties. 6 package httplimit 7 8 import ( 9 "fmt" 10 "net" 11 "net/http" 12 "strconv" 13 "time" 14 15 "gitlab.com/infor-cloud/martian-cloud/tharsis/go-limiter" 16 ) 17 18 const ( 19 // HeaderRateLimitLimit, HeaderRateLimitRemaining, and HeaderRateLimitReset 20 // are the recommended return header values from IETF on rate limiting. Reset 21 // is in UTC time. 22 HeaderRateLimitLimit = "X-RateLimit-Limit" 23 HeaderRateLimitRemaining = "X-RateLimit-Remaining" 24 HeaderRateLimitReset = "X-RateLimit-Reset" 25 26 // HeaderRetryAfter is the header used to indicate when a client should retry 27 // requests (when the rate limit expires), in UTC time. 28 HeaderRetryAfter = "Retry-After" 29 ) 30 31 // KeyFunc is a function that accepts an http request and returns a string key 32 // that uniquely identifies this request for the purpose of rate limiting. 33 // 34 // KeyFuncs are called on each request, so be mindful of performance and 35 // implement caching where possible. If a KeyFunc returns an error, the HTTP 36 // handler will return Internal Server Error and will NOT take from the limiter 37 // store. 38 type KeyFunc func(r *http.Request) (string, error) 39 40 // IPKeyFunc returns a function that keys data based on the incoming requests IP 41 // address. By default this uses the RemoteAddr, but you can also specify a list 42 // of headers which will be checked for an IP address first (e.g. 43 // "X-Forwarded-For"). Headers are retrieved using Header.Get(), which means 44 // they are case insensitive. 45 func IPKeyFunc(headers ...string) KeyFunc { 46 return func(r *http.Request) (string, error) { 47 for _, h := range headers { 48 if v := r.Header.Get(h); v != "" { 49 return v, nil 50 } 51 } 52 53 ip, _, err := net.SplitHostPort(r.RemoteAddr) 54 if err != nil { 55 return "", err 56 } 57 return ip, nil 58 } 59 } 60 61 // Middleware is a handler/mux that can wrap other middlware to implement HTTP 62 // rate limiting. It can rate limit based on an arbitrary KeyFunc, and supports 63 // anything that implements limiter.StoreWithContext. 64 type Middleware struct { 65 store limiter.Store 66 keyFunc KeyFunc 67 } 68 69 // NewMiddleware creates a new middleware suitable for use as an HTTP handler. 70 // This function returns an error if either the Store or KeyFunc are nil. 71 func NewMiddleware(s limiter.Store, f KeyFunc) (*Middleware, error) { 72 if s == nil { 73 return nil, fmt.Errorf("store cannot be nil") 74 } 75 76 if f == nil { 77 return nil, fmt.Errorf("key function cannot be nil") 78 } 79 80 return &Middleware{ 81 store: s, 82 keyFunc: f, 83 }, nil 84 } 85 86 // Handle returns the HTTP handler as a middleware. This handler calls Take() on 87 // the store and sets the common rate limiting headers. If the take is 88 // successful, the remaining middleware is called. If take is unsuccessful, the 89 // middleware chain is halted and the function renders a 429 to the caller with 90 // metadata about when it's safe to retry. 91 func (m *Middleware) Handle(next http.Handler) http.Handler { 92 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 93 ctx := r.Context() 94 95 // Call the key function - if this fails, it's an internal server error. 96 key, err := m.keyFunc(r) 97 if err != nil { 98 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 99 return 100 } 101 102 // Take from the store. 103 limit, remaining, reset, ok, err := m.store.Take(ctx, key) 104 if err != nil { 105 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 106 return 107 } 108 109 resetTime := time.Unix(0, int64(reset)).UTC().Format(time.RFC1123) 110 111 // Set headers (we do this regardless of whether the request is permitted). 112 w.Header().Set(HeaderRateLimitLimit, strconv.FormatUint(limit, 10)) 113 w.Header().Set(HeaderRateLimitRemaining, strconv.FormatUint(remaining, 10)) 114 w.Header().Set(HeaderRateLimitReset, resetTime) 115 116 // Fail if there were no tokens remaining. 117 if !ok { 118 w.Header().Set(HeaderRetryAfter, resetTime) 119 http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) 120 return 121 } 122 123 // If we got this far, we're allowed to continue, so call the next middleware 124 // in the stack to continue processing. 125 next.ServeHTTP(w, r) 126 }) 127 }