github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/api/v1/httpd/handler.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 httpd
    22  
    23  import (
    24  	"encoding/json"
    25  	"fmt"
    26  	"net/http"
    27  	// needed for pprof handler registration
    28  	_ "net/http/pprof"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/cluster/placementhandler"
    32  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    33  	"github.com/m3db/m3/src/cmd/services/m3query/config"
    34  	"github.com/m3db/m3/src/query/api/v1/handler"
    35  	"github.com/m3db/m3/src/query/api/v1/handler/database"
    36  	"github.com/m3db/m3/src/query/api/v1/handler/graphite"
    37  	"github.com/m3db/m3/src/query/api/v1/handler/influxdb"
    38  	m3json "github.com/m3db/m3/src/query/api/v1/handler/json"
    39  	"github.com/m3db/m3/src/query/api/v1/handler/namespace"
    40  	"github.com/m3db/m3/src/query/api/v1/handler/openapi"
    41  	"github.com/m3db/m3/src/query/api/v1/handler/prom"
    42  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/native"
    43  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/remote"
    44  	"github.com/m3db/m3/src/query/api/v1/handler/topic"
    45  	"github.com/m3db/m3/src/query/api/v1/middleware"
    46  	"github.com/m3db/m3/src/query/api/v1/options"
    47  	"github.com/m3db/m3/src/query/api/v1/route"
    48  	"github.com/m3db/m3/src/query/parser/promql"
    49  	"github.com/m3db/m3/src/query/util/queryhttp"
    50  	xdebug "github.com/m3db/m3/src/x/debug"
    51  	xhttp "github.com/m3db/m3/src/x/net/http"
    52  
    53  	"github.com/gorilla/mux"
    54  	"github.com/jonboulle/clockwork"
    55  	"go.uber.org/zap"
    56  )
    57  
    58  const (
    59  	healthURL = "/health"
    60  	routesURL = "/routes"
    61  
    62  	// EngineURLParam defines query url parameter which is used to switch between
    63  	// prometheus and m3query engines.
    64  	EngineURLParam = "engine"
    65  )
    66  
    67  var (
    68  	remoteSource = map[string]string{"source": "remote"}
    69  	nativeSource = map[string]string{"source": "native"}
    70  
    71  	v1APIGroup = map[string]string{"api_group": "v1"}
    72  )
    73  
    74  // Handler represents the top-level HTTP handler.
    75  type Handler struct {
    76  	registry         *queryhttp.EndpointRegistry
    77  	handler          *mux.Router
    78  	options          options.HandlerOptions
    79  	customHandlers   []options.CustomHandler
    80  	logger           *zap.Logger
    81  	middlewareConfig config.MiddlewareConfiguration
    82  }
    83  
    84  // Router returns the http handler registered with all relevant routes for query.
    85  func (h *Handler) Router() http.Handler {
    86  	return h.handler
    87  }
    88  
    89  // NewHandler returns a new instance of handler with routes.
    90  func NewHandler(
    91  	handlerOptions options.HandlerOptions,
    92  	middlewareConfig config.MiddlewareConfiguration,
    93  	customHandlers ...options.CustomHandler,
    94  ) *Handler {
    95  	var (
    96  		r        = mux.NewRouter()
    97  		logger   = handlerOptions.InstrumentOpts().Logger()
    98  		registry = queryhttp.NewEndpointRegistry(r)
    99  	)
   100  	return &Handler{
   101  		registry:         registry,
   102  		handler:          r,
   103  		options:          handlerOptions,
   104  		customHandlers:   customHandlers,
   105  		logger:           logger,
   106  		middlewareConfig: middlewareConfig,
   107  	}
   108  }
   109  
   110  // RegisterRoutes registers all http routes.
   111  func (h *Handler) RegisterRoutes() error {
   112  	instrumentOpts := h.options.InstrumentOpts()
   113  
   114  	// OpenAPI.
   115  	if err := h.registry.Register(queryhttp.RegisterOptions{
   116  		Path:    openapi.URL,
   117  		Handler: openapi.NewDocHandler(instrumentOpts),
   118  		Methods: methods(openapi.HTTPMethod),
   119  	}); err != nil {
   120  		return err
   121  	}
   122  	if err := h.registry.Register(queryhttp.RegisterOptions{
   123  		PathPrefix: openapi.StaticURLPrefix,
   124  		Handler:    openapi.StaticHandler(),
   125  	}); err != nil {
   126  		return err
   127  	}
   128  
   129  	// Prometheus remote read/write endpoints.
   130  	remoteSourceOpts := h.options.SetInstrumentOpts(instrumentOpts.
   131  		SetMetricsScope(instrumentOpts.MetricsScope().
   132  			Tagged(remoteSource).
   133  			Tagged(v1APIGroup),
   134  		))
   135  
   136  	promRemoteReadHandler := remote.NewPromReadHandler(remoteSourceOpts)
   137  	promRemoteWriteHandler, err := remote.NewPromWriteHandler(remoteSourceOpts)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	nativeSourceOpts := h.options.SetInstrumentOpts(instrumentOpts.
   143  		SetMetricsScope(instrumentOpts.MetricsScope().
   144  			Tagged(nativeSource).
   145  			Tagged(v1APIGroup),
   146  		))
   147  
   148  	promqlQueryHandler, err := prom.NewReadHandler(nativeSourceOpts,
   149  		prom.WithEngine(h.options.PrometheusEngineFn()))
   150  	if err != nil {
   151  		return err
   152  	}
   153  	promqlInstantQueryHandler, err := prom.NewReadHandler(nativeSourceOpts,
   154  		prom.WithInstantEngine(h.options.PrometheusEngineFn()))
   155  	if err != nil {
   156  		return err
   157  	}
   158  	nativePromReadHandler := native.NewPromReadHandler(nativeSourceOpts)
   159  	nativePromReadInstantHandler := native.NewPromReadInstantHandler(nativeSourceOpts)
   160  
   161  	h.options.QueryRouter().Setup(options.QueryRouterOptions{
   162  		DefaultQueryEngine: h.options.DefaultQueryEngine(),
   163  		PromqlHandler:      promqlQueryHandler.ServeHTTP,
   164  		M3QueryHandler:     nativePromReadHandler.ServeHTTP,
   165  	})
   166  
   167  	h.options.InstantQueryRouter().Setup(options.QueryRouterOptions{
   168  		DefaultQueryEngine: h.options.DefaultQueryEngine(),
   169  		PromqlHandler:      promqlInstantQueryHandler.ServeHTTP,
   170  		M3QueryHandler:     nativePromReadInstantHandler.ServeHTTP,
   171  	})
   172  
   173  	// Query routable endpoints.
   174  	if err := h.registry.Register(queryhttp.RegisterOptions{
   175  		Path:               native.PromReadURL,
   176  		Handler:            h.options.QueryRouter(),
   177  		Methods:            native.PromReadHTTPMethods,
   178  		MiddlewareOverride: native.WithRangeQueryParamsAndRangeRewriting,
   179  	}); err != nil {
   180  		return err
   181  	}
   182  	if err := h.registry.Register(queryhttp.RegisterOptions{
   183  		Path:               native.PromReadInstantURL,
   184  		Handler:            h.options.InstantQueryRouter(),
   185  		Methods:            native.PromReadInstantHTTPMethods,
   186  		MiddlewareOverride: native.WithInstantQueryParamsAndRangeRewriting,
   187  	}); err != nil {
   188  		return err
   189  	}
   190  
   191  	// Prometheus endpoints.
   192  	if err := h.registry.Register(queryhttp.RegisterOptions{
   193  		Path:               "/prometheus" + native.PromReadURL,
   194  		Handler:            promqlQueryHandler,
   195  		Methods:            native.PromReadHTTPMethods,
   196  		MiddlewareOverride: native.WithRangeQueryParamsAndRangeRewriting,
   197  	}); err != nil {
   198  		return err
   199  	}
   200  	if err := h.registry.Register(queryhttp.RegisterOptions{
   201  		Path:               "/prometheus" + native.PromReadInstantURL,
   202  		Handler:            promqlInstantQueryHandler,
   203  		Methods:            native.PromReadInstantHTTPMethods,
   204  		MiddlewareOverride: native.WithInstantQueryParamsAndRangeRewriting,
   205  	}); err != nil {
   206  		return err
   207  	}
   208  
   209  	// M3Query endpoints.
   210  	if err := h.registry.Register(queryhttp.RegisterOptions{
   211  		Path:               "/m3query" + native.PromReadURL,
   212  		Handler:            nativePromReadHandler,
   213  		Methods:            native.PromReadHTTPMethods,
   214  		MiddlewareOverride: native.WithRangeQueryParamsAndRangeRewriting,
   215  	}); err != nil {
   216  		return err
   217  	}
   218  	if err := h.registry.Register(queryhttp.RegisterOptions{
   219  		Path:               "/m3query" + native.PromReadInstantURL,
   220  		Handler:            nativePromReadInstantHandler,
   221  		Methods:            native.PromReadInstantHTTPMethods,
   222  		MiddlewareOverride: native.WithInstantQueryParamsAndRangeRewriting,
   223  	}); err != nil {
   224  		return err
   225  	}
   226  
   227  	// Prometheus remote read and write endpoints.
   228  	if err := h.registry.Register(queryhttp.RegisterOptions{
   229  		Path:    remote.PromReadURL,
   230  		Handler: promRemoteReadHandler,
   231  		Methods: remote.PromReadHTTPMethods,
   232  	}); err != nil {
   233  		return err
   234  	}
   235  	if err := h.registry.Register(queryhttp.RegisterOptions{
   236  		Path:    remote.PromWriteURL,
   237  		Handler: promRemoteWriteHandler,
   238  		Methods: methods(remote.PromWriteHTTPMethod),
   239  		// Register with no response logging for write calls since so frequent.
   240  		MiddlewareOverride: middleware.WithNoResponseLogging,
   241  	}); err != nil {
   242  		return err
   243  	}
   244  
   245  	// InfluxDB write endpoint.
   246  	if err := h.registry.Register(queryhttp.RegisterOptions{
   247  		Path:    influxdb.InfluxWriteURL,
   248  		Handler: influxdb.NewInfluxWriterHandler(h.options),
   249  		Methods: methods(influxdb.InfluxWriteHTTPMethod),
   250  		// Register with no response logging for write calls since so frequent.
   251  		MiddlewareOverride: middleware.WithNoResponseLogging,
   252  	}); err != nil {
   253  		return err
   254  	}
   255  
   256  	// Native M3 search and write endpoints.
   257  	if err := h.registry.Register(queryhttp.RegisterOptions{
   258  		Path:    handler.SearchURL,
   259  		Handler: handler.NewSearchHandler(h.options),
   260  		Methods: methods(handler.SearchHTTPMethod),
   261  	}); err != nil {
   262  		return err
   263  	}
   264  	if err := h.registry.Register(queryhttp.RegisterOptions{
   265  		Path:    m3json.WriteJSONURL,
   266  		Handler: m3json.NewWriteJSONHandler(h.options),
   267  		Methods: methods(m3json.JSONWriteHTTPMethod),
   268  	}); err != nil {
   269  		return err
   270  	}
   271  
   272  	// Readiness endpoint.
   273  	if err := h.registry.Register(queryhttp.RegisterOptions{
   274  		Path:    handler.ReadyURL,
   275  		Handler: handler.NewReadyHandler(h.options),
   276  		Methods: methods(handler.ReadyHTTPMethod),
   277  	}); err != nil {
   278  		return err
   279  	}
   280  
   281  	// Tag completion endpoints.
   282  	if err := h.registry.Register(queryhttp.RegisterOptions{
   283  		Path:               native.CompleteTagsURL,
   284  		Handler:            native.NewCompleteTagsHandler(h.options),
   285  		Methods:            methods(native.CompleteTagsHTTPMethod),
   286  		MiddlewareOverride: native.WithQueryParams,
   287  	}); err != nil {
   288  		return err
   289  	}
   290  	if err := h.registry.Register(queryhttp.RegisterOptions{
   291  		Path:               remote.TagValuesURL,
   292  		Handler:            remote.NewTagValuesHandler(h.options),
   293  		Methods:            methods(remote.TagValuesHTTPMethod),
   294  		MiddlewareOverride: native.WithQueryParams,
   295  	}); err != nil {
   296  		return err
   297  	}
   298  
   299  	// List tag endpoints.
   300  	if err := h.registry.Register(queryhttp.RegisterOptions{
   301  		Path:               native.ListTagsURL,
   302  		Handler:            native.NewListTagsHandler(h.options),
   303  		Methods:            native.ListTagsHTTPMethods,
   304  		MiddlewareOverride: native.WithQueryParams,
   305  	}); err != nil {
   306  		return err
   307  	}
   308  
   309  	// Query parse endpoints.
   310  	if err := h.registry.Register(queryhttp.RegisterOptions{
   311  		Path:    native.PromParseURL,
   312  		Handler: native.NewPromParseHandler(h.options),
   313  		Methods: methods(native.PromParseHTTPMethod),
   314  	}); err != nil {
   315  		return err
   316  	}
   317  	if err := h.registry.Register(queryhttp.RegisterOptions{
   318  		Path:    native.PromThresholdURL,
   319  		Handler: native.NewPromThresholdHandler(h.options),
   320  		Methods: methods(native.PromThresholdHTTPMethod),
   321  	}); err != nil {
   322  		return err
   323  	}
   324  
   325  	// Series match endpoints.
   326  	if err := h.registry.Register(queryhttp.RegisterOptions{
   327  		Path:               route.SeriesMatchURL,
   328  		Handler:            remote.NewPromSeriesMatchHandler(h.options),
   329  		Methods:            remote.PromSeriesMatchHTTPMethods,
   330  		MiddlewareOverride: native.WithQueryParams,
   331  	}); err != nil {
   332  		return err
   333  	}
   334  
   335  	// Graphite routable endpoints.
   336  	h.options.GraphiteRenderRouter().Setup(options.GraphiteRenderRouterOptions{
   337  		RenderHandler: graphite.NewRenderHandler(h.options).ServeHTTP,
   338  	})
   339  	h.options.GraphiteFindRouter().Setup(options.GraphiteFindRouterOptions{
   340  		FindHandler: graphite.NewFindHandler(h.options).ServeHTTP,
   341  	})
   342  	if err := h.registry.Register(queryhttp.RegisterOptions{
   343  		Path:    graphite.ReadURL,
   344  		Handler: h.options.GraphiteRenderRouter(),
   345  		Methods: graphite.ReadHTTPMethods,
   346  	}); err != nil {
   347  		return err
   348  	}
   349  	if err := h.registry.Register(queryhttp.RegisterOptions{
   350  		Path:    graphite.FindURL,
   351  		Handler: h.options.GraphiteFindRouter(),
   352  		Methods: graphite.FindHTTPMethods,
   353  	}); err != nil {
   354  		return err
   355  	}
   356  
   357  	placementOpts, err := h.placementOpts()
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	var (
   363  		serviceOptionDefaults = h.options.ServiceOptionDefaults()
   364  		clusterClient         = h.options.ClusterClient()
   365  		config                = h.options.Config()
   366  	)
   367  
   368  	debugWriter, err := xdebug.NewZipWriterWithDefaultSources(
   369  		h.options.CPUProfileDuration(),
   370  		instrumentOpts)
   371  	if err != nil {
   372  		return fmt.Errorf("unable to create debug writer: %v", err)
   373  	}
   374  
   375  	// Register debug dump handler.
   376  	if err := h.registry.Register(queryhttp.RegisterOptions{
   377  		Path:    xdebug.DebugURL,
   378  		Handler: debugWriter.HTTPHandler(),
   379  		Methods: methods(xdebug.DebugMethod),
   380  	}); err != nil {
   381  		return err
   382  	}
   383  
   384  	if clusterClient != nil {
   385  		err = database.RegisterRoutes(h.registry, clusterClient,
   386  			h.options.Config(), h.options.EmbeddedDBCfg(),
   387  			serviceOptionDefaults, instrumentOpts,
   388  			h.options.NamespaceValidator(), h.options.KVStoreProtoParser())
   389  		if err != nil {
   390  			return err
   391  		}
   392  
   393  		routes := placementhandler.MakeRoutes(serviceOptionDefaults, placementOpts)
   394  		for _, route := range routes {
   395  			err := h.registry.RegisterPaths(route.Paths, queryhttp.RegisterPathsOptions{
   396  				Handler: route.Handler,
   397  				Methods: route.Methods,
   398  			})
   399  			if err != nil {
   400  				return err
   401  			}
   402  		}
   403  
   404  		err = namespace.RegisterRoutes(h.registry, clusterClient,
   405  			h.options.Clusters(), serviceOptionDefaults, instrumentOpts,
   406  			h.options.NamespaceValidator())
   407  		if err != nil {
   408  			return err
   409  		}
   410  
   411  		err = topic.RegisterRoutes(h.registry, clusterClient, config, instrumentOpts)
   412  		if err != nil {
   413  			return err
   414  		}
   415  	}
   416  
   417  	if err := h.registerHealthEndpoints(); err != nil {
   418  		return err
   419  	}
   420  	if err := h.registerProfileEndpoints(); err != nil {
   421  		return err
   422  	}
   423  	if err := h.registerRoutesEndpoint(); err != nil {
   424  		return err
   425  	}
   426  
   427  	customMiddle := make(map[*mux.Route]middleware.OverrideOptions)
   428  	// Register custom endpoints last to have these conflict with
   429  	// any existing routes.
   430  	for _, custom := range h.customHandlers {
   431  		for _, method := range custom.Methods() {
   432  			var prevHandler http.Handler
   433  			entry, prevRoute := h.registry.PathEntry(custom.Route(), method)
   434  			if prevRoute {
   435  				prevHandler = entry.GetHandler()
   436  			}
   437  
   438  			handler, err := custom.Handler(nativeSourceOpts, prevHandler)
   439  			if err != nil {
   440  				return err
   441  			}
   442  
   443  			if !prevRoute {
   444  				if err := h.registry.Register(queryhttp.RegisterOptions{
   445  					Path:               custom.Route(),
   446  					Handler:            handler,
   447  					Methods:            methods(method),
   448  					MiddlewareOverride: custom.MiddlewareOverride(),
   449  				}); err != nil {
   450  					return err
   451  				}
   452  			} else {
   453  				customMiddle[entry] = custom.MiddlewareOverride()
   454  				entry.Handler(handler)
   455  			}
   456  		}
   457  	}
   458  
   459  	// NB: the double http_handler was accidentally introduced and now we are
   460  	// stuck with it for backwards compatibility.
   461  	middleIOpts := instrumentOpts.SetMetricsScope(
   462  		h.options.InstrumentOpts().MetricsScope().SubScope("http_handler_http_handler"))
   463  
   464  	// Apply middleware after the custom handlers have overridden the previous handlers so the middleware functions
   465  	// are dispatched before the custom handler.
   466  	// req -> middleware fns -> custom handler -> previous handler.
   467  	err = h.registry.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
   468  		handler := route.GetHandler()
   469  		opts := middleware.Options{
   470  			InstrumentOpts: middleIOpts,
   471  			Route:          route,
   472  			Clock:          clockwork.NewRealClock(),
   473  			Logging:        middleware.NewLoggingOptions(h.middlewareConfig.Logging),
   474  			Metrics: middleware.MetricsOptions{
   475  				Config: h.middlewareConfig.Metrics,
   476  				ParseOptions: promql.NewParseOptions().
   477  					SetRequireStartEndTime(h.options.Config().Query.RequireLabelsEndpointStartEndTime).
   478  					SetNowFn(h.options.NowFn()),
   479  			},
   480  			PrometheusRangeRewrite: middleware.PrometheusRangeRewriteOptions{
   481  				FetchOptionsBuilder:  h.options.FetchOptionsBuilder(),
   482  				ResolutionMultiplier: h.middlewareConfig.Prometheus.ResolutionMultiplier,
   483  				DefaultLookback:      h.options.DefaultLookback(),
   484  				Storage:              h.options.Storage(),
   485  				PrometheusEngineFn:   h.options.PrometheusEngineFn(),
   486  			},
   487  		}
   488  		override := h.registry.MiddlewareOpts(route)
   489  		if override != nil {
   490  			opts = override(opts)
   491  		}
   492  		if customMiddle[route] != nil {
   493  			opts = customMiddle[route](opts)
   494  		}
   495  		middle := h.options.RegisterMiddleware()(opts)
   496  
   497  		// iterate through in reverse order so each Middleware fn gets the proper next handler to dispatch. this ensures the
   498  		// Middleware is dispatched in the expected order (first -> last).
   499  		for i := len(middle) - 1; i >= 0; i-- {
   500  			handler = middle[i].Middleware(handler)
   501  		}
   502  
   503  		route.Handler(handler)
   504  		return nil
   505  	})
   506  
   507  	if err != nil {
   508  		return err
   509  	}
   510  
   511  	return nil
   512  }
   513  
   514  func (h *Handler) placementOpts() (placementhandler.HandlerOptions, error) {
   515  	return placementhandler.NewHandlerOptions(
   516  		h.options.ClusterClient(),
   517  		h.options.Config().ClusterManagement.Placement,
   518  		h.m3AggServiceOptions(),
   519  		h.options.InstrumentOpts(),
   520  	)
   521  }
   522  
   523  func (h *Handler) m3AggServiceOptions() *handleroptions.M3AggServiceOptions {
   524  	clusters := h.options.Clusters()
   525  	if clusters == nil {
   526  		return nil
   527  	}
   528  
   529  	maxResolution := time.Duration(0)
   530  	for _, ns := range clusters.ClusterNamespaces() {
   531  		resolution := ns.Options().Attributes().Resolution
   532  		if resolution > maxResolution {
   533  			maxResolution = resolution
   534  		}
   535  	}
   536  
   537  	if maxResolution == 0 {
   538  		return nil
   539  	}
   540  
   541  	return &handleroptions.M3AggServiceOptions{
   542  		MaxAggregationWindowSize: maxResolution,
   543  	}
   544  }
   545  
   546  // Endpoints useful for profiling the service.
   547  func (h *Handler) registerHealthEndpoints() error {
   548  	return h.registry.Register(queryhttp.RegisterOptions{
   549  		Path: healthURL,
   550  		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   551  			json.NewEncoder(w).Encode(struct {
   552  				Uptime string `json:"uptime"`
   553  				Now    string `json:"now"`
   554  			}{
   555  				Uptime: time.Since(h.options.CreatedAt()).String(),
   556  				Now:    time.Now().String(),
   557  			})
   558  		}),
   559  		Methods: methods(http.MethodGet),
   560  	})
   561  }
   562  
   563  // Endpoints useful for profiling the service.
   564  func (h *Handler) registerProfileEndpoints() error {
   565  	debugHandler := http.NewServeMux()
   566  	xdebug.RegisterPProfHandlers(debugHandler)
   567  
   568  	return h.registry.Register(queryhttp.RegisterOptions{
   569  		PathPrefix: "/debug/pprof",
   570  		Handler:    debugHandler,
   571  	})
   572  }
   573  
   574  // Endpoints useful for viewing routes directory.
   575  func (h *Handler) registerRoutesEndpoint() error {
   576  	return h.registry.Register(queryhttp.RegisterOptions{
   577  		Path: routesURL,
   578  		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   579  			var routes []string
   580  			err := h.registry.Walk(
   581  				func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
   582  					str, err := route.GetPathTemplate()
   583  					if err != nil {
   584  						return err
   585  					}
   586  					routes = append(routes, str)
   587  					return nil
   588  				})
   589  			if err != nil {
   590  				xhttp.WriteError(w, err)
   591  				return
   592  			}
   593  			json.NewEncoder(w).Encode(struct {
   594  				Routes []string `json:"routes"`
   595  			}{
   596  				Routes: routes,
   597  			})
   598  		}),
   599  		Methods: methods(http.MethodGet),
   600  	})
   601  }
   602  
   603  func methods(str ...string) []string {
   604  	return str
   605  }