From 586e72bb223057dec694f8135f40a6489b1f2dbe Mon Sep 17 00:00:00 2001 From: Matt Schallert Date: Thu, 11 Jun 2020 13:19:59 -0400 Subject: [PATCH] [query] Fix Content-Type for OpenAPI handler (#2403) --- src/query/api/v1/handler/graphite/find.go | 2 +- .../api/v1/handler/graphite/render_parser.go | 5 ++-- src/query/api/v1/handler/openapi/openapi.go | 1 + src/query/api/v1/handler/prom/common.go | 6 +++-- .../api/v1/handler/prometheus/common_test.go | 3 ++- .../handler/prometheus/native/common_test.go | 2 +- .../prometheus/native/complete_tags.go | 2 +- .../v1/handler/prometheus/native/list_tags.go | 2 +- .../api/v1/handler/prometheus/native/read.go | 2 +- .../api/v1/handler/prometheus/remote/match.go | 2 +- .../api/v1/handler/prometheus/remote/read.go | 4 ++-- .../v1/handler/prometheus/remote/read_test.go | 7 +++--- .../handler/prometheus/remote/tag_values.go | 2 +- src/query/api/v1/handler/search_test.go | 3 ++- src/x/net/http/response.go | 24 +++++++++++++++++-- 15 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/query/api/v1/handler/graphite/find.go b/src/query/api/v1/handler/graphite/find.go index 147b24aa39..f49526d4cf 100644 --- a/src/query/api/v1/handler/graphite/find.go +++ b/src/query/api/v1/handler/graphite/find.go @@ -110,7 +110,7 @@ func (h *grahiteFindHandler) ServeHTTP( ) { ctx := context.WithValue(r.Context(), handler.HeaderKey, r.Header) logger := logging.WithContext(ctx, h.instrumentOpts) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) // NB: need to run two separate queries, one of which will match only the // provided matchers, and one which will match the provided matchers with at diff --git a/src/query/api/v1/handler/graphite/render_parser.go b/src/query/api/v1/handler/graphite/render_parser.go index 8a1e495c4a..6c8dd8a399 100644 --- a/src/query/api/v1/handler/graphite/render_parser.go +++ b/src/query/api/v1/handler/graphite/render_parser.go @@ -33,6 +33,7 @@ import ( "github.com/m3db/m3/src/query/graphite/graphite" "github.com/m3db/m3/src/query/graphite/ts" "github.com/m3db/m3/src/query/util/json" + xhttp "github.com/m3db/m3/src/x/net/http" ) const ( @@ -54,12 +55,12 @@ func WriteRenderResponse( format string, ) error { if format == pickleFormat { - w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeOctetStream) return renderResultsPickle(w, series.Values) } // NB: return json unless requesting specifically `pickleFormat` - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) return renderResultsJSON(w, series.Values) } diff --git a/src/query/api/v1/handler/openapi/openapi.go b/src/query/api/v1/handler/openapi/openapi.go index af31b0adf0..5175194fc1 100644 --- a/src/query/api/v1/handler/openapi/openapi.go +++ b/src/query/api/v1/handler/openapi/openapi.go @@ -74,6 +74,7 @@ func (h *DocHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeHTMLUTF8) w.Write(doc) } diff --git a/src/query/api/v1/handler/prom/common.go b/src/query/api/v1/handler/prom/common.go index f2a2b0998e..69f8e64a40 100644 --- a/src/query/api/v1/handler/prom/common.go +++ b/src/query/api/v1/handler/prom/common.go @@ -25,6 +25,8 @@ import ( "net/http" "time" + xhttp "github.com/m3db/m3/src/x/net/http" + jsoniter "github.com/json-iterator/go" promql "github.com/prometheus/prometheus/promql/parser" promstorage "github.com/prometheus/prometheus/storage" @@ -100,7 +102,7 @@ func respond(w http.ResponseWriter, data interface{}, warnings promstorage.Warni return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) w.WriteHeader(http.StatusOK) w.Write(b) } @@ -117,7 +119,7 @@ func respondError(w http.ResponseWriter, err error, code int) { return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) w.WriteHeader(code) w.Write(b) } diff --git a/src/query/api/v1/handler/prometheus/common_test.go b/src/query/api/v1/handler/prometheus/common_test.go index 7d84bdb938..5f3f993ca1 100644 --- a/src/query/api/v1/handler/prometheus/common_test.go +++ b/src/query/api/v1/handler/prometheus/common_test.go @@ -33,6 +33,7 @@ import ( "github.com/m3db/m3/src/query/models" "github.com/m3db/m3/src/query/test" + xhttp "github.com/m3db/m3/src/x/net/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -93,7 +94,7 @@ func TestTimeoutParseWithPostRequestParam(t *testing.T) { require.NoError(t, form.Close()) req := httptest.NewRequest("POST", "/dummy", buff) - req.Header.Set("Content-Type", form.FormDataContentType()) + req.Header.Set(xhttp.HeaderContentType, form.FormDataContentType()) timeout, err := ParseRequestTimeout(req, time.Second) assert.NoError(t, err) diff --git a/src/query/api/v1/handler/prometheus/native/common_test.go b/src/query/api/v1/handler/prometheus/native/common_test.go index 0e90a08e65..1737fa7033 100644 --- a/src/query/api/v1/handler/prometheus/native/common_test.go +++ b/src/query/api/v1/handler/prometheus/native/common_test.go @@ -92,7 +92,7 @@ func TestParamParsing(t *testing.T) { func TestParamParsing_POST(t *testing.T) { params := defaultParams().Encode() req := httptest.NewRequest("POST", PromReadURL, strings.NewReader(params)) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded) r, err := testParseParams(req) require.NoError(t, err, "unable to parse request") diff --git a/src/query/api/v1/handler/prometheus/native/complete_tags.go b/src/query/api/v1/handler/prometheus/native/complete_tags.go index ed2ffd0c3f..4111fb3de8 100644 --- a/src/query/api/v1/handler/prometheus/native/complete_tags.go +++ b/src/query/api/v1/handler/prometheus/native/complete_tags.go @@ -66,7 +66,7 @@ func NewCompleteTagsHandler(opts options.HandlerOptions) http.Handler { func (h *CompleteTagsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), handler.HeaderKey, r.Header) logger := logging.WithContext(ctx, h.instrumentOpts) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) tagCompletionQueries, rErr := prometheus.ParseTagCompletionParamsToQueries(r) if rErr != nil { diff --git a/src/query/api/v1/handler/prometheus/native/list_tags.go b/src/query/api/v1/handler/prometheus/native/list_tags.go index e90c8b580f..be5e18af10 100644 --- a/src/query/api/v1/handler/prometheus/native/list_tags.go +++ b/src/query/api/v1/handler/prometheus/native/list_tags.go @@ -70,7 +70,7 @@ func NewListTagsHandler(opts options.HandlerOptions) http.Handler { func (h *ListTagsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), handler.HeaderKey, r.Header) logger := logging.WithContext(ctx, h.instrumentOpts) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) query := &storage.CompleteTagsQuery{ CompleteNameOnly: true, diff --git a/src/query/api/v1/handler/prometheus/native/read.go b/src/query/api/v1/handler/prometheus/native/read.go index c14a98df1b..c5f8a958ba 100644 --- a/src/query/api/v1/handler/prometheus/native/read.go +++ b/src/query/api/v1/handler/prometheus/native/read.go @@ -131,7 +131,7 @@ func (h *promReadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) handleroptions.AddWarningHeaders(w, result.Meta) h.promReadMetrics.fetchSuccess.Inc(1) diff --git a/src/query/api/v1/handler/prometheus/remote/match.go b/src/query/api/v1/handler/prometheus/remote/match.go index b0ebb0fe51..1123522dae 100644 --- a/src/query/api/v1/handler/prometheus/remote/match.go +++ b/src/query/api/v1/handler/prometheus/remote/match.go @@ -70,7 +70,7 @@ func NewPromSeriesMatchHandler(opts options.HandlerOptions) http.Handler { func (h *PromSeriesMatchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), handler.HeaderKey, r.Header) logger := logging.WithContext(ctx, h.instrumentOpts) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) w.Header().Set("Access-Control-Allow-Origin", "*") queries, err := prometheus.ParseSeriesMatchQuery(r, h.tagOptions) diff --git a/src/query/api/v1/handler/prometheus/remote/read.go b/src/query/api/v1/handler/prometheus/remote/read.go index 15aeaac4b9..57ff06c978 100644 --- a/src/query/api/v1/handler/prometheus/remote/read.go +++ b/src/query/api/v1/handler/prometheus/remote/read.go @@ -173,7 +173,7 @@ func (h *promReadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { }) } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) handleroptions.AddWarningHeaders(w, readResult.Meta) err = json.NewEncoder(w).Encode(result) @@ -226,7 +226,7 @@ func WriteSnappyCompressed( return err } - w.Header().Set("Content-Type", "application/x-protobuf") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeProtobuf) w.Header().Set("Content-Encoding", "snappy") handleroptions.AddWarningHeaders(w, readResult.Meta) diff --git a/src/query/api/v1/handler/prometheus/remote/read_test.go b/src/query/api/v1/handler/prometheus/remote/read_test.go index aa7b75cf43..35d13941f3 100644 --- a/src/query/api/v1/handler/prometheus/remote/read_test.go +++ b/src/query/api/v1/handler/prometheus/remote/read_test.go @@ -50,6 +50,7 @@ import ( "github.com/m3db/m3/src/query/test/m3" xclock "github.com/m3db/m3/src/x/clock" "github.com/m3db/m3/src/x/instrument" + xhttp "github.com/m3db/m3/src/x/net/http" xtest "github.com/m3db/m3/src/x/test" "github.com/golang/mock/gomock" @@ -89,7 +90,7 @@ func TestParseExpr(t *testing.T) { start := time.Now().Truncate(time.Hour) req := httptest.NewRequest(http.MethodPost, "/", buildBody(query, start)) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded) readReq, err := ParseExpr(req) require.NoError(t, err) @@ -248,7 +249,7 @@ func TestQueryKillOnClientDisconnect(t *testing.T) { Timeout: 1 * time.Millisecond, } - _, err := c.Post(server.URL, "application/x-protobuf", test.GeneratePromReadBody(t)) + _, err := c.Post(server.URL, xhttp.ContentTypeProtobuf, test.GeneratePromReadBody(t)) assert.Error(t, err) } @@ -257,7 +258,7 @@ func TestQueryKillOnTimeout(t *testing.T) { defer server.Close() req, _ := http.NewRequest("POST", server.URL, test.GeneratePromReadBody(t)) - req.Header.Add("Content-Type", "application/x-protobuf") + req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeProtobuf) req.Header.Add("timeout", "1ms") resp, err := http.DefaultClient.Do(req) require.NoError(t, err) diff --git a/src/query/api/v1/handler/prometheus/remote/tag_values.go b/src/query/api/v1/handler/prometheus/remote/tag_values.go index d0d5b0caf4..09f4b846cd 100644 --- a/src/query/api/v1/handler/prometheus/remote/tag_values.go +++ b/src/query/api/v1/handler/prometheus/remote/tag_values.go @@ -79,7 +79,7 @@ func NewTagValuesHandler(options options.HandlerOptions) http.Handler { func (h *TagValuesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), handler.HeaderKey, r.Header) logger := logging.WithContext(ctx, h.instrumentOpts) - w.Header().Set("Content-Type", "application/json") + w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) query, err := h.parseTagValuesToQuery(r) if err != nil { diff --git a/src/query/api/v1/handler/search_test.go b/src/query/api/v1/handler/search_test.go index 584327a010..9a40fa287c 100644 --- a/src/query/api/v1/handler/search_test.go +++ b/src/query/api/v1/handler/search_test.go @@ -40,6 +40,7 @@ import ( "github.com/m3db/m3/src/query/test/m3" "github.com/m3db/m3/src/query/test/seriesiter" "github.com/m3db/m3/src/x/ident" + xhttp "github.com/m3db/m3/src/x/net/http" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" @@ -129,7 +130,7 @@ func TestSearchEndpoint(t *testing.T) { urlWithLimit := fmt.Sprintf("%s%s", server.URL, "?limit=90") req, _ := http.NewRequest("POST", urlWithLimit, generateSearchBody(t)) - req.Header.Add("Content-Type", "application/json") + req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeJSON) resp, err := http.DefaultClient.Do(req) require.NoError(t, err) defer resp.Body.Close() diff --git a/src/x/net/http/response.go b/src/x/net/http/response.go index e31a1a10c9..29f9c1e79f 100644 --- a/src/x/net/http/response.go +++ b/src/x/net/http/response.go @@ -30,6 +30,26 @@ import ( "go.uber.org/zap" ) +const ( + // HeaderContentType is the HTTP Content Type header. + HeaderContentType = "Content-Type" + + // ContentTypeJSON is the Content-Type value for a JSON response. + ContentTypeJSON = "application/json" + + // ContentTypeFormURLEncoded is the Content-Type value for a URL-encoded form. + ContentTypeFormURLEncoded = "application/x-www-form-urlencoded" + + // ContentTypeHTMLUTF8 is the Content-Type value for UTF8-encoded HTML. + ContentTypeHTMLUTF8 = "text/html; charset=utf-8" + + // ContentTypeProtobuf is the Content-Type value for a Protobuf message. + ContentTypeProtobuf = "application/x-protobuf" + + // ContentTypeOctetStream is the Content-Type value for binary data. + ContentTypeOctetStream = "application/octet-stream" +) + // WriteJSONResponse writes generic data to the ResponseWriter func WriteJSONResponse(w http.ResponseWriter, data interface{}, logger *zap.Logger) { jsonData, err := json.Marshal(data) @@ -39,7 +59,7 @@ func WriteJSONResponse(w http.ResponseWriter, data interface{}, logger *zap.Logg return } - w.Header().Set("Content-Type", "application/json") + w.Header().Set(HeaderContentType, ContentTypeJSON) w.Write(jsonData) } @@ -48,7 +68,7 @@ func WriteJSONResponse(w http.ResponseWriter, data interface{}, logger *zap.Logg func WriteProtoMsgJSONResponse(w http.ResponseWriter, data proto.Message, logger *zap.Logger) { marshaler := jsonpb.Marshaler{EmitDefaults: true} - w.Header().Set("Content-Type", "application/json") + w.Header().Set(HeaderContentType, ContentTypeJSON) err := marshaler.Marshal(w, data) if err != nil { logger.Error("unable to marshal json", zap.Error(err))