diff options
Diffstat (limited to 'vendor/github.com/prometheus/common/route')
-rw-r--r-- | vendor/github.com/prometheus/common/route/route.go | 137 | ||||
-rw-r--r-- | vendor/github.com/prometheus/common/route/route_test.go | 75 |
2 files changed, 212 insertions, 0 deletions
diff --git a/vendor/github.com/prometheus/common/route/route.go b/vendor/github.com/prometheus/common/route/route.go new file mode 100644 index 000000000..930b52d4f --- /dev/null +++ b/vendor/github.com/prometheus/common/route/route.go @@ -0,0 +1,137 @@ +package route + +import ( + "fmt" + "net/http" + "sync" + + "github.com/julienschmidt/httprouter" + "golang.org/x/net/context" +) + +var ( + mtx = sync.RWMutex{} + ctxts = map[*http.Request]context.Context{} +) + +// Context returns the context for the request. +func Context(r *http.Request) context.Context { + mtx.RLock() + defer mtx.RUnlock() + return ctxts[r] +} + +type param string + +// Param returns param p for the context. +func Param(ctx context.Context, p string) string { + return ctx.Value(param(p)).(string) +} + +// WithParam returns a new context with param p set to v. +func WithParam(ctx context.Context, p, v string) context.Context { + return context.WithValue(ctx, param(p), v) +} + +type contextFn func(r *http.Request) (context.Context, error) + +// Router wraps httprouter.Router and adds support for prefixed sub-routers +// and per-request context injections. +type Router struct { + rtr *httprouter.Router + prefix string + ctxFn contextFn +} + +// New returns a new Router. +func New(ctxFn contextFn) *Router { + if ctxFn == nil { + ctxFn = func(r *http.Request) (context.Context, error) { + return context.Background(), nil + } + } + return &Router{ + rtr: httprouter.New(), + ctxFn: ctxFn, + } +} + +// WithPrefix returns a router that prefixes all registered routes with prefix. +func (r *Router) WithPrefix(prefix string) *Router { + return &Router{rtr: r.rtr, prefix: r.prefix + prefix, ctxFn: r.ctxFn} +} + +// handle turns a HandlerFunc into an httprouter.Handle. +func (r *Router) handle(h http.HandlerFunc) httprouter.Handle { + return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { + reqCtx, err := r.ctxFn(req) + if err != nil { + http.Error(w, fmt.Sprintf("Error creating request context: %v", err), http.StatusBadRequest) + return + } + ctx, cancel := context.WithCancel(reqCtx) + defer cancel() + + for _, p := range params { + ctx = context.WithValue(ctx, param(p.Key), p.Value) + } + + mtx.Lock() + ctxts[req] = ctx + mtx.Unlock() + + h(w, req) + + mtx.Lock() + delete(ctxts, req) + mtx.Unlock() + } +} + +// Get registers a new GET route. +func (r *Router) Get(path string, h http.HandlerFunc) { + r.rtr.GET(r.prefix+path, r.handle(h)) +} + +// Options registers a new OPTIONS route. +func (r *Router) Options(path string, h http.HandlerFunc) { + r.rtr.OPTIONS(r.prefix+path, r.handle(h)) +} + +// Del registers a new DELETE route. +func (r *Router) Del(path string, h http.HandlerFunc) { + r.rtr.DELETE(r.prefix+path, r.handle(h)) +} + +// Put registers a new PUT route. +func (r *Router) Put(path string, h http.HandlerFunc) { + r.rtr.PUT(r.prefix+path, r.handle(h)) +} + +// Post registers a new POST route. +func (r *Router) Post(path string, h http.HandlerFunc) { + r.rtr.POST(r.prefix+path, r.handle(h)) +} + +// Redirect takes an absolute path and sends an internal HTTP redirect for it, +// prefixed by the router's path prefix. Note that this method does not include +// functionality for handling relative paths or full URL redirects. +func (r *Router) Redirect(w http.ResponseWriter, req *http.Request, path string, code int) { + http.Redirect(w, req, r.prefix+path, code) +} + +// ServeHTTP implements http.Handler. +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + r.rtr.ServeHTTP(w, req) +} + +// FileServe returns a new http.HandlerFunc that serves files from dir. +// Using routes must provide the *filepath parameter. +func FileServe(dir string) http.HandlerFunc { + fs := http.FileServer(http.Dir(dir)) + + return func(w http.ResponseWriter, r *http.Request) { + r.URL.Path = Param(Context(r), "filepath") + fs.ServeHTTP(w, r) + } +} diff --git a/vendor/github.com/prometheus/common/route/route_test.go b/vendor/github.com/prometheus/common/route/route_test.go new file mode 100644 index 000000000..4055d69d5 --- /dev/null +++ b/vendor/github.com/prometheus/common/route/route_test.go @@ -0,0 +1,75 @@ +package route + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "golang.org/x/net/context" +) + +func TestRedirect(t *testing.T) { + router := New(nil).WithPrefix("/test/prefix") + w := httptest.NewRecorder() + r, err := http.NewRequest("GET", "http://localhost:9090/foo", nil) + if err != nil { + t.Fatalf("Error building test request: %s", err) + } + + router.Redirect(w, r, "/some/endpoint", http.StatusFound) + if w.Code != http.StatusFound { + t.Fatalf("Unexpected redirect status code: got %d, want %d", w.Code, http.StatusFound) + } + + want := "/test/prefix/some/endpoint" + got := w.Header()["Location"][0] + if want != got { + t.Fatalf("Unexpected redirect location: got %s, want %s", got, want) + } +} + +func TestContextFn(t *testing.T) { + router := New(func(r *http.Request) (context.Context, error) { + return context.WithValue(context.Background(), "testkey", "testvalue"), nil + }) + + router.Get("/test", func(w http.ResponseWriter, r *http.Request) { + want := "testvalue" + got := Context(r).Value("testkey") + if want != got { + t.Fatalf("Unexpected context value: want %q, got %q", want, got) + } + }) + + r, err := http.NewRequest("GET", "http://localhost:9090/test", nil) + if err != nil { + t.Fatalf("Error building test request: %s", err) + } + router.ServeHTTP(nil, r) +} + +func TestContextFnError(t *testing.T) { + router := New(func(r *http.Request) (context.Context, error) { + return context.Background(), fmt.Errorf("test error") + }) + + router.Get("/test", func(w http.ResponseWriter, r *http.Request) {}) + + r, err := http.NewRequest("GET", "http://localhost:9090/test", nil) + if err != nil { + t.Fatalf("Error building test request: %s", err) + } + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + + if w.Code != http.StatusBadRequest { + t.Fatalf("Unexpected response status: got %q, want %q", w.Code, http.StatusBadRequest) + } + + want := "Error creating request context: test error\n" + got := w.Body.String() + if want != got { + t.Fatalf("Unexpected response body: got %q, want %q", got, want) + } +} |