diff --git a/ctl/import_test.go b/ctl/import_test.go index ba8a9dc6e..2284ca873 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -183,6 +183,7 @@ func TestImportCommand_InvalidFile(t *testing.T) { // MustNewHTTPRequest creates a new HTTP request. Panic on error. func MustNewHTTPRequest(method, urlStr string, body io.Reader) *http.Request { req, err := http.NewRequest(method, urlStr, body) + req.Header.Add("Accept", "application/json") if err != nil { panic(err) } diff --git a/http/client.go b/http/client.go index d8f0d290d..5090072a3 100644 --- a/http/client.go +++ b/http/client.go @@ -90,6 +90,7 @@ func (c *InternalClient) maxSliceByIndex(ctx context.Context) (map[string]uint64 } req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) @@ -120,6 +121,7 @@ func (c *InternalClient) Schema(ctx context.Context) ([]*pilosa.IndexInfo, error } req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) @@ -195,6 +197,7 @@ func (c *InternalClient) FragmentNodes(ctx context.Context, index string, slice } req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) @@ -685,6 +688,7 @@ func (c *InternalClient) FragmentBlocks(ctx context.Context, uri *pilosa.URI, in } req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) @@ -777,6 +781,7 @@ func (c *InternalClient) ColumnAttrDiff(ctx context.Context, uri *pilosa.URI, in } req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) @@ -820,6 +825,7 @@ func (c *InternalClient) RowAttrDiff(ctx context.Context, uri *pilosa.URI, index } req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) @@ -859,6 +865,7 @@ func (c *InternalClient) SendMessage(ctx context.Context, uri *pilosa.URI, pb pr } req.Header.Set("Content-Type", "application/x-protobuf") req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) + req.Header.Set("Accept", "application/json") // Execute request. resp, err := c.HTTPClient.Do(req.WithContext(ctx)) diff --git a/http/handler.go b/http/handler.go index 509378d6e..674381905 100644 --- a/http/handler.go +++ b/http/handler.go @@ -164,11 +164,12 @@ func (h *Handler) queryArgValidator(next http.Handler) http.Handler { func NewRouter(handler *Handler) *mux.Router { router := mux.NewRouter() router.HandleFunc("/", handler.handleHome).Methods("GET") - router.HandleFunc("/cluster/message", handler.handlePostClusterMessage).Methods("POST") - router.HandleFunc("/cluster/resize/set-coordinator", handler.handlePostClusterResizeSetCoordinator).Methods("POST") router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux).Methods("GET") router.Handle("/debug/vars", expvar.Handler()).Methods("GET") + router.HandleFunc("/schema", handler.handleGetSchema).Methods("GET") + router.HandleFunc("/cluster/message", handler.handlePostClusterMessage).Methods("POST") + router.HandleFunc("/cluster/resize/set-coordinator", handler.handlePostClusterResizeSetCoordinator).Methods("POST") router.HandleFunc("/slices/max", handler.handleGetSlicesMax).Methods("GET") // TODO: deprecate, but it's being used by the client router.HandleFunc("/status", handler.handleGetStatus).Methods("GET") router.HandleFunc("/info", handler.handleGetInfo).Methods("GET") @@ -257,8 +258,28 @@ func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) { http.Error(w, "Welcome. Pilosa is running. Visit https://www.pilosa.com/docs/ for more information.", http.StatusNotFound) } +func checkHeaderAcceptJSON(header http.Header) bool { + v, found := header["Accept"] + sendError := false + if found { + sendError = true + for _, v := range v { + if v == "application/json" { + sendError = false + + } + } + } + return sendError +} + // handleGetSchema handles GET /schema requests. func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } + schema := h.API.Schema(r.Context()) if err := json.NewEncoder(w).Encode(getSchemaResponse{ Indexes: schema, @@ -269,6 +290,10 @@ func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { // handleGetStatus handles GET /status requests. func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } status := getStatusResponse{ State: h.API.State(), Nodes: h.API.Hosts(r.Context()), @@ -280,6 +305,10 @@ func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleGetInfo(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } info := h.API.Info() if err := json.NewEncoder(w).Encode(info); err != nil { h.Logger.Printf("write info response error: %s", err) @@ -333,6 +362,10 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { // handleGetSlicesMax handles GET /schema requests. func (h *Handler) handleGetSlicesMax(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } if err := json.NewEncoder(w).Encode(getSlicesMaxResponse{ Standard: h.API.MaxSlices(r.Context()), }); err != nil { @@ -351,6 +384,10 @@ func (h *Handler) handleGetIndexes(w http.ResponseWriter, r *http.Request) { // handleGetIndex handles GET /index/ requests. func (h *Handler) handleGetIndex(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } indexName := mux.Vars(r)["index"] for _, idx := range h.API.Schema(r.Context()) { if idx.Name == indexName { @@ -429,6 +466,10 @@ type postIndexResponse struct{} // handleDeleteIndex handles DELETE /index request. func (h *Handler) handleDeleteIndex(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } indexName := mux.Vars(r)["index"] err := h.API.DeleteIndex(r.Context(), indexName) if err != nil { @@ -447,6 +488,10 @@ type deleteIndexResponse struct{} // handlePostIndex handles POST /index request. func (h *Handler) handlePostIndex(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } indexName := mux.Vars(r)["index"] // Decode request. @@ -477,6 +522,10 @@ func (h *Handler) handlePostIndex(w http.ResponseWriter, r *http.Request) { // handlePostIndexAttrDiff handles POST /index/attr/diff requests. func (h *Handler) handlePostIndexAttrDiff(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } indexName := mux.Vars(r)["index"] // Decode request. @@ -514,6 +563,10 @@ type postIndexAttrDiffResponse struct { // handlePostField handles POST /field request. func (h *Handler) handlePostField(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } indexName := mux.Vars(r)["index"] fieldName := mux.Vars(r)["field"] @@ -592,6 +645,11 @@ type postFieldResponse struct{} // handleDeleteField handles DELETE /field request. func (h *Handler) handleDeleteField(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } + indexName := mux.Vars(r)["index"] fieldName := mux.Vars(r)["field"] @@ -617,6 +675,10 @@ type deleteFieldResponse struct{} // handlePostFieldAttrDiff handles POST /field/attr/diff requests. func (h *Handler) handlePostFieldAttrDiff(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } indexName := mux.Vars(r)["index"] fieldName := mux.Vars(r)["field"] @@ -709,7 +771,7 @@ func (h *Handler) readURLQueryRequest(r *http.Request) (*pilosa.QueryRequest, er // writeQueryResponse writes the response from the executor to w. func (h *Handler) writeQueryResponse(w http.ResponseWriter, r *http.Request, resp *pilosa.QueryResponse) error { - if strings.Contains(r.Header.Get("Accept"), "application/x-protobuf") { + if checkHeaderAcceptJSON(r.Header) { return h.writeProtobufQueryResponse(w, resp) } return h.writeJSONQueryResponse(w, resp) @@ -872,6 +934,10 @@ func (h *Handler) handleGetExportCSV(w http.ResponseWriter, r *http.Request) { // handleGetFragmentNodes handles /fragment/nodes requests. func (h *Handler) handleGetFragmentNodes(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } q := r.URL.Query() index := q.Get("index") @@ -917,6 +983,10 @@ func (h *Handler) handleGetFragmentBlockData(w http.ResponseWriter, r *http.Requ // handleGetFragmentBlocks handles GET /fragment/blocks requests. func (h *Handler) handleGetFragmentBlocks(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } // Read slice parameter. q := r.URL.Query() slice, err := strconv.ParseUint(q.Get("slice"), 10, 64) @@ -949,6 +1019,10 @@ type getFragmentBlocksResponse struct { // handleGetVersion handles /version requests. func (h *Handler) handleGetVersion(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } err := json.NewEncoder(w).Encode(struct { Version string `json:"version"` }{ @@ -1047,6 +1121,10 @@ func errorString(err error) string { } func (h *Handler) handlePostClusterResizeSetCoordinator(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } // Decode request. var req setCoordinatorRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -1084,6 +1162,10 @@ type setCoordinatorResponse struct { // handlePostClusterResizeRemoveNode handles POST /cluster/resize/remove-node request. func (h *Handler) handlePostClusterResizeRemoveNode(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } // Decode request. var req removeNodeRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -1120,6 +1202,10 @@ type removeNodeResponse struct { // handlePostClusterResizeAbort handles POST /cluster/resize/abort request. func (h *Handler) handlePostClusterResizeAbort(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } err := h.API.ResizeAbort() var msg string if err != nil { @@ -1157,6 +1243,10 @@ func (h *Handler) handleRecalculateCaches(w http.ResponseWriter, r *http.Request } func (h *Handler) handlePostClusterMessage(w http.ResponseWriter, r *http.Request) { + if checkHeaderAcceptJSON(r.Header) { + http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) + return + } // Verify that request is only communicating over protobufs. if r.Header.Get("Content-Type") != "application/x-protobuf" { http.Error(w, "Unsupported media type", http.StatusUnsupportedMediaType) diff --git a/http/handler_test.go b/http/handler_test.go index 2f3ab0aba..ddedef49a 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -750,11 +750,17 @@ func TestHandler_Index_AttrStore_Diff(t *testing.T) { blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") // Send block checksums to determine diff. - resp, err := gohttp.Post( + req, err := gohttp.NewRequest( + "POST", s.URL+"/index/i/attr/diff", - "application/json", strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), ) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + client := &gohttp.Client{} + resp, err := client.Do(req) if err != nil { t.Fatal(err) } @@ -800,11 +806,17 @@ func TestHandler_Field_AttrStore_Diff(t *testing.T) { blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") // Send block checksums to determine diff. - resp, err := gohttp.Post( + req, err := gohttp.NewRequest( + "POST", s.URL+"/index/i/field/meta/attr/diff", - "application/json", strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), ) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + client := &gohttp.Client{} + resp, err := client.Do(req) if err != nil { t.Fatal(err) } diff --git a/test/handler.go b/test/handler.go index 27fa20503..5256413b5 100644 --- a/test/handler.go +++ b/test/handler.go @@ -148,6 +148,7 @@ func MustParseURLHost(rawurl string) string { // MustNewHTTPRequest creates a new HTTP request. Panic on error. func MustNewHTTPRequest(method, urlStr string, body io.Reader) *gohttp.Request { req, err := gohttp.NewRequest(method, urlStr, body) + req.Header.Add("Accept", "application/json") if err != nil { panic(err) } diff --git a/test/pilosa.go b/test/pilosa.go index bd1628aa6..757e5a96c 100644 --- a/test/pilosa.go +++ b/test/pilosa.go @@ -263,10 +263,15 @@ func (m *Main) RecalculateCaches() error { // MustDo executes http.Do() with an http.NewRequest(). Panic on error. func MustDo(method, urlStr string, body string) *httpResponse { - req, err := gohttp.NewRequest(method, urlStr, strings.NewReader(body)) - if err != nil { - panic(err) - } + req, err := gohttp.NewRequest( + method, + urlStr, + strings.NewReader(body), + ) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + resp, err := gohttp.DefaultClient.Do(req) if err != nil { panic(err) diff --git a/test/pilosa_test.go b/test/pilosa_test.go index 583a244c0..0a1acc551 100644 --- a/test/pilosa_test.go +++ b/test/pilosa_test.go @@ -17,6 +17,7 @@ package test_test import ( "encoding/json" "net/http" + "strings" "testing" "github.com/pilosa/pilosa" @@ -32,12 +33,21 @@ func TestNewCluster(t *testing.T) { t.Fatalf("node %d does not have the same coordinator as node 0. '%v' and '%v' respectively", i, coordi, coordinator) } } + req, err := http.NewRequest( + "GET", + "http://"+cluster[0].Server.Addr().String()+"/status", + strings.NewReader(""), + ) - response, err := http.Get("http://" + cluster[0].Server.Addr().String() + "/status") + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) if err != nil { - t.Fatalf("getting schema: %v", err) + t.Fatalf("sending request: %v", err) } - dec := json.NewDecoder(response.Body) + defer resp.Body.Close() + + dec := json.NewDecoder(resp.Body) body := struct { State string Nodes []struct {