func runHTTPTestCases(t *testing.T, h ctxhttp.Handler, cs []httpTestCase) { for i, c := range cs { req, err := http.NewRequest("GET", c.path, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() serveErr := h.ServeHTTPContext(context.Background(), rr, req) if serveErr != nil { http.Error(rr, serveErr.Error(), http.StatusInternalServerError) } if have, want := rr.Code, c.code; have != want { t.Errorf("Expected request %d at %s to return %d but got %d", i, c.path, want, have) } for name, want := range c.headers { if have := rr.HeaderMap.Get(name); have != want { t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'", i, c.path, name, want, have) } } } }
// Handler is an adapter which allows the usage of an ctxhttp.Handler as a // request handle. func (r *Router) Handler(method, path string, handler ctxhttp.Handler) { r.Handle(method, path, func(ctx context.Context, w http.ResponseWriter, req *http.Request) error { return handler.ServeHTTPContext(ctx, w, req) }, ) }
// WithParseAndValidate represent a middleware handler. For POST or // PUT requests, it also parses the request body as a form. The extracted valid // token will be added to the context. The extracted token will be checked // against the Blacklist. errHandler is an optional argument. Only the first // item in the slice will be considered. Default errHandler is: // // http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) // // ProTip: Instead of passing the token as an HTML Header you can also add the token // to a form (multipart/form-data) with an input name of access_token. If the // token cannot be found within the Header the fallback triggers the lookup within the form. func (s *Service) WithParseAndValidate(errHandler ...ctxhttp.Handler) ctxhttp.Middleware { var errH ctxhttp.Handler errH = ctxhttp.HandlerFunc(defaultTokenErrorHandler) if len(errHandler) == 1 && errHandler[0] != nil { errH = errHandler[0] } return func(h ctxhttp.Handler) ctxhttp.Handler { return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { token, err := jwt.ParseFromRequest(r, s.keyFunc) var inBL bool if token != nil { inBL = s.Blacklist.Has(token.Raw) } if token != nil && err == nil && token.Valid && !inBL { return h.ServeHTTPContext(NewContext(ctx, token), w, r) } if PkgLog.IsDebug() { PkgLog.Debug("ctxjwt.Service.Authenticate", "err", err, "token", token, "inBlacklist", inBL) } return errH.ServeHTTPContext(NewContextWithError(ctx, err), w, r) }) } }
// WithRateLimit wraps an ctxhttp.Handler to limit incoming requests. // Requests that are not limited will be passed to the handler // unchanged. Limited requests will be passed to the DeniedHandler. // X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and // Retry-After headers will be written to the response based on the // values in the RateLimitResult. func (t *HTTPRateLimit) WithRateLimit(rlStore throttled.GCRAStore, h ctxhttp.Handler) ctxhttp.Handler { if t.Config == nil { t.Config = config.DefaultManager } if t.DeniedHandler == nil { t.DeniedHandler = DefaultDeniedHandler } if t.RateLimiter == nil { if rlStore == nil { var err error rlStore, err = memstore.New(65536) if err != nil { panic(err) } } var err error t.RateLimiter, err = throttled.NewGCRARateLimiter(rlStore, t.quota()) if err != nil { panic(err) } } return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var k string if t.VaryBy != nil { k = t.VaryBy.Key(r) } limited, context, err := t.RateLimiter.RateLimit(k, 1) if err != nil { return err } setRateLimitHeaders(w, context) if !limited { return h.ServeHTTPContext(ctx, w, r) } return t.DeniedHandler.ServeHTTPContext(ctx, w, r) }) }