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  }