github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/search.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package handler 22 23 import ( 24 "context" 25 "encoding/json" 26 "io/ioutil" 27 "net/http" 28 "strconv" 29 30 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 31 "github.com/m3db/m3/src/query/api/v1/options" 32 "github.com/m3db/m3/src/query/errors" 33 "github.com/m3db/m3/src/query/storage" 34 "github.com/m3db/m3/src/query/util/logging" 35 xerrors "github.com/m3db/m3/src/x/errors" 36 "github.com/m3db/m3/src/x/instrument" 37 xhttp "github.com/m3db/m3/src/x/net/http" 38 39 "go.uber.org/zap" 40 ) 41 42 const ( 43 // SearchURL is the url to search for metric ids 44 SearchURL = "/search" 45 46 // SearchHTTPMethod is the HTTP method used with this resource. 47 SearchHTTPMethod = http.MethodPost 48 49 defaultLimit = 1000 50 ) 51 52 // SearchHandler represents a handler for the search endpoint 53 type SearchHandler struct { 54 store storage.Storage 55 fetchOptionsBuilder handleroptions.FetchOptionsBuilder 56 instrumentOpts instrument.Options 57 } 58 59 // NewSearchHandler returns a new instance of handler 60 func NewSearchHandler(opts options.HandlerOptions) http.Handler { 61 return &SearchHandler{ 62 store: opts.Storage(), 63 fetchOptionsBuilder: opts.FetchOptionsBuilder(), 64 instrumentOpts: opts.InstrumentOpts(), 65 } 66 } 67 68 func (h *SearchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 69 ctx := r.Context() 70 logger := logging.WithContext(ctx, h.instrumentOpts) 71 72 query, parseBodyErr := h.parseBody(r) 73 ctx, fetchOpts, parseURLParamsErr := h.parseURLParams(ctx, r) 74 if err := xerrors.FirstError(parseBodyErr, parseURLParamsErr); err != nil { 75 logger.Error("unable to parse request", zap.Error(err)) 76 xhttp.WriteError(w, err) 77 return 78 } 79 80 results, err := h.search(ctx, query, fetchOpts) 81 if err != nil { 82 logger.Error("search query error", 83 zap.Error(err), 84 zap.Any("query", query), 85 zap.Any("fetchOpts", fetchOpts)) 86 if errors.IsTimeout(err) { 87 err = errors.NewErrQueryTimeout(err) 88 } 89 xhttp.WriteError(w, err) 90 return 91 } 92 93 xhttp.WriteJSONResponse(w, results, logger) 94 } 95 96 func (h *SearchHandler) parseBody(r *http.Request) (*storage.FetchQuery, error) { 97 body, err := ioutil.ReadAll(r.Body) 98 if err != nil { 99 return nil, xerrors.NewInvalidParamsError(err) 100 } 101 defer r.Body.Close() 102 103 var fetchQuery storage.FetchQuery 104 if err := json.Unmarshal(body, &fetchQuery); err != nil { 105 return nil, xerrors.NewInvalidParamsError(err) 106 } 107 108 return &fetchQuery, nil 109 } 110 111 func (h *SearchHandler) parseURLParams( 112 ctx context.Context, 113 r *http.Request, 114 ) (context.Context, *storage.FetchOptions, error) { 115 ctx, fetchOpts, parseErr := h.fetchOptionsBuilder.NewFetchOptions(ctx, r) 116 if parseErr != nil { 117 return nil, nil, xerrors.NewInvalidParamsError(parseErr) 118 } 119 120 // Parse for series and docs limits as query params. 121 // For backwards compat, allow "limit" and "seriesLimit" 122 // for the series limit name. 123 if str := r.URL.Query().Get("limit"); str != "" { 124 var err error 125 fetchOpts.SeriesLimit, err = strconv.Atoi(str) 126 if err != nil { 127 return nil, nil, xerrors.NewInvalidParamsError(err) 128 } 129 } else if str := r.URL.Query().Get("seriesLimit"); str != "" { 130 var err error 131 fetchOpts.SeriesLimit, err = strconv.Atoi(str) 132 if err != nil { 133 return nil, nil, xerrors.NewInvalidParamsError(err) 134 } 135 } 136 137 if str := r.URL.Query().Get("docsLimit"); str != "" { 138 var err error 139 fetchOpts.DocsLimit, err = strconv.Atoi(str) 140 if err != nil { 141 return nil, nil, xerrors.NewInvalidParamsError(err) 142 } 143 } 144 145 if str := r.URL.Query().Get("requireExhaustive"); str != "" { 146 var err error 147 fetchOpts.RequireExhaustive, err = strconv.ParseBool(str) 148 if err != nil { 149 return nil, nil, xerrors.NewInvalidParamsError(err) 150 } 151 } 152 153 return ctx, fetchOpts, nil 154 } 155 156 func (h *SearchHandler) search( 157 ctx context.Context, 158 query *storage.FetchQuery, 159 opts *storage.FetchOptions, 160 ) (*storage.SearchResults, error) { 161 return h.store.SearchSeries(ctx, query, opts) 162 }