Handle OPTIONS HTTP requests correctly.

Fixes https://github.com/prometheus/prometheus/issues/1346
This commit is contained in:
Julius Volz 2016-01-26 01:32:46 +01:00
parent 4dc8c4f94c
commit 1ae23bf5e9
6 changed files with 69 additions and 9 deletions

View file

@ -75,6 +75,11 @@ func (r *Router) Get(path string, h http.HandlerFunc) {
r.rtr.GET(r.prefix+path, handle(h))
}
// Options registers a new OPTIONS route.
func (r *Router) Options(path string, h http.HandlerFunc) {
r.rtr.OPTIONS(r.prefix+path, handle(h))
}
// Del registers a new DELETE route.
func (r *Router) Del(path string, h http.HandlerFunc) {
r.rtr.DELETE(r.prefix+path, handle(h))

4
vendor/vendor.json vendored
View file

@ -174,8 +174,8 @@
},
{
"path": "github.com/prometheus/common/route",
"revision": "4fdc91a58c9d3696b982e8a680f4997403132d44",
"revisionTime": "2015-10-26T12:04:34+01:00"
"revision": "14ca1097bbe21584194c15e391a9dab95ad42a59",
"revisionTime": "2016-01-25T23:57:51+01:00"
},
{
"path": "github.com/prometheus/procfs",

View file

@ -34,6 +34,12 @@ type API struct {
// Register registers the handler for the various endpoints below /api.
func (api *API) Register(router *route.Router) {
// List all the endpoints here instead of using a wildcard route because we
// would otherwise handle /api/v1 as well.
router.Options("/query", handle("options", api.Options))
router.Options("/query_range", handle("options", api.Options))
router.Options("/metrics", handle("options", api.Options))
router.Get("/query", handle("query", api.Query))
router.Get("/query_range", handle("query_range", api.QueryRange))
router.Get("/metrics", handle("metrics", api.Metrics))

View file

@ -31,7 +31,7 @@ import (
// Enables cross-site script calls.
func setAccessControlHeaders(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Origin")
w.Header().Set("Access-Control-Allow-Methods", "GET")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Expose-Headers", "Date")
}
@ -61,6 +61,12 @@ func parseDuration(d string) (time.Duration, error) {
return time.Duration(dFloat * float64(time.Second/time.Nanosecond)), nil
}
// Options handles OPTIONS requests to /api/... endpoints.
func (api *API) Options(w http.ResponseWriter, r *http.Request) {
setAccessControlHeaders(w)
w.WriteHeader(http.StatusNoContent)
}
// Query handles the /api/query endpoint.
func (api *API) Query(w http.ResponseWriter, r *http.Request) {
setAccessControlHeaders(w)

View file

@ -38,6 +38,13 @@ const (
errorBadData = "bad_data"
)
var corsHeaders = map[string]string{
"Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Origin": "*",
"Access-Control-Expose-Headers": "Date",
}
type apiError struct {
typ errorType
err error
@ -56,10 +63,9 @@ type response struct {
// Enables cross-site script calls.
func setCORS(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Origin")
w.Header().Set("Access-Control-Allow-Methods", "GET")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Expose-Headers", "Date")
for h, v := range corsHeaders {
w.Header().Set(h, v)
}
}
type apiFunc func(r *http.Request) (interface{}, *apiError)
@ -91,8 +97,10 @@ func (api *API) Register(r *route.Router) {
setCORS(w)
if data, err := f(r); err != nil {
respondError(w, err, data)
} else {
} else if data != nil {
respond(w, data)
} else {
w.WriteHeader(http.StatusNoContent)
}
})
return prometheus.InstrumentHandler(name, httputil.CompressionHandler{
@ -100,6 +108,8 @@ func (api *API) Register(r *route.Router) {
})
}
r.Options("/*path", instr("options", api.options))
r.Get("/query", instr("query", api.query))
r.Get("/query_range", instr("query_range", api.queryRange))
@ -114,6 +124,10 @@ type queryData struct {
Result model.Value `json:"result"`
}
func (api *API) options(r *http.Request) (interface{}, *apiError) {
return nil, nil
}
func (api *API) query(r *http.Request) (interface{}, *apiError) {
var ts model.Time
if t := r.FormValue("time"); t != "" {
@ -255,7 +269,7 @@ func (api *API) dropSeries(r *http.Request) (interface{}, *apiError) {
func respond(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.WriteHeader(http.StatusOK)
b, err := json.Marshal(&response{
Status: statusSuccess,

View file

@ -503,3 +503,32 @@ func TestParseDuration(t *testing.T) {
}
}
}
func TestOptionsMethod(t *testing.T) {
r := route.New()
api := &API{}
api.Register(r)
s := httptest.NewServer(r)
defer s.Close()
req, err := http.NewRequest("OPTIONS", s.URL+"/any_path", nil)
if err != nil {
t.Fatalf("Error creating OPTIONS request: %s", err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatalf("Error executing OPTIONS request: %s", err)
}
if resp.StatusCode != http.StatusNoContent {
t.Fatalf("Expected status %d, got %d", http.StatusNoContent, resp.StatusCode)
}
for h, v := range corsHeaders {
if resp.Header.Get(h) != v {
t.Fatalf("Expected %q for header %q, got %q", v, h, resp.Header.Get(h))
}
}
}