github.com/ilhicas/nomad@v0.11.8/command/agent/http.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"net/http/pprof"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/NYTimes/gziphandler"
    17  	assetfs "github.com/elazarl/go-bindata-assetfs"
    18  	"github.com/gorilla/websocket"
    19  	"github.com/hashicorp/go-connlimit"
    20  	log "github.com/hashicorp/go-hclog"
    21  	"github.com/hashicorp/go-msgpack/codec"
    22  	"github.com/hashicorp/nomad/helper/noxssrw"
    23  	"github.com/hashicorp/nomad/helper/tlsutil"
    24  	"github.com/hashicorp/nomad/nomad/structs"
    25  	"github.com/rs/cors"
    26  )
    27  
    28  const (
    29  	// ErrInvalidMethod is used if the HTTP method is not supported
    30  	ErrInvalidMethod = "Invalid method"
    31  
    32  	// ErrEntOnly is the error returned if accessing an enterprise only
    33  	// endpoint
    34  	ErrEntOnly = "Nomad Enterprise only endpoint"
    35  
    36  	// ContextKeyReqID is a unique ID for a given request
    37  	ContextKeyReqID = "requestID"
    38  
    39  	// MissingRequestID is a placeholder if we cannot retrieve a request
    40  	// UUID from context
    41  	MissingRequestID = "<missing request id>"
    42  )
    43  
    44  var (
    45  	// Set to false by stub_asset if the ui build tag isn't enabled
    46  	uiEnabled = true
    47  
    48  	// Overridden if the ui build tag isn't enabled
    49  	stubHTML = ""
    50  
    51  	// allowCORS sets permissive CORS headers for a handler
    52  	allowCORS = cors.New(cors.Options{
    53  		AllowedOrigins:   []string{"*"},
    54  		AllowedMethods:   []string{"HEAD", "GET"},
    55  		AllowedHeaders:   []string{"*"},
    56  		AllowCredentials: true,
    57  	})
    58  )
    59  
    60  type handlerFn func(resp http.ResponseWriter, req *http.Request) (interface{}, error)
    61  type handlerByteFn func(resp http.ResponseWriter, req *http.Request) ([]byte, error)
    62  
    63  // HTTPServer is used to wrap an Agent and expose it over an HTTP interface
    64  type HTTPServer struct {
    65  	agent      *Agent
    66  	mux        *http.ServeMux
    67  	listener   net.Listener
    68  	listenerCh chan struct{}
    69  	logger     log.Logger
    70  	Addr       string
    71  
    72  	wsUpgrader *websocket.Upgrader
    73  }
    74  
    75  // NewHTTPServer starts new HTTP server over the agent
    76  func NewHTTPServer(agent *Agent, config *Config) (*HTTPServer, error) {
    77  	// Start the listener
    78  	lnAddr, err := net.ResolveTCPAddr("tcp", config.normalizedAddrs.HTTP)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	ln, err := config.Listener("tcp", lnAddr.IP.String(), lnAddr.Port)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("failed to start HTTP listener: %v", err)
    85  	}
    86  
    87  	// If TLS is enabled, wrap the listener with a TLS listener
    88  	if config.TLSConfig.EnableHTTP {
    89  		tlsConf, err := tlsutil.NewTLSConfiguration(config.TLSConfig, config.TLSConfig.VerifyHTTPSClient, true)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  
    94  		tlsConfig, err := tlsConf.IncomingTLSConfig()
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  		ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig)
    99  	}
   100  
   101  	// Create the mux
   102  	mux := http.NewServeMux()
   103  
   104  	wsUpgrader := &websocket.Upgrader{
   105  		ReadBufferSize:  2048,
   106  		WriteBufferSize: 2048,
   107  	}
   108  
   109  	// Create the server
   110  	srv := &HTTPServer{
   111  		agent:      agent,
   112  		mux:        mux,
   113  		listener:   ln,
   114  		listenerCh: make(chan struct{}),
   115  		logger:     agent.httpLogger,
   116  		Addr:       ln.Addr().String(),
   117  		wsUpgrader: wsUpgrader,
   118  	}
   119  	srv.registerHandlers(config.EnableDebug)
   120  
   121  	// Handle requests with gzip compression
   122  	gzip, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(0))
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	// Get connection handshake timeout limit
   128  	handshakeTimeout, err := time.ParseDuration(config.Limits.HTTPSHandshakeTimeout)
   129  	if err != nil {
   130  		return nil, fmt.Errorf("error parsing https_handshake_timeout: %v", err)
   131  	} else if handshakeTimeout < 0 {
   132  		return nil, fmt.Errorf("https_handshake_timeout must be >= 0")
   133  	}
   134  
   135  	// Get max connection limit
   136  	maxConns := 0
   137  	if mc := config.Limits.HTTPMaxConnsPerClient; mc != nil {
   138  		maxConns = *mc
   139  	}
   140  	if maxConns < 0 {
   141  		return nil, fmt.Errorf("http_max_conns_per_client must be >= 0")
   142  	}
   143  
   144  	// Create HTTP server with timeouts
   145  	httpServer := http.Server{
   146  		Addr:      srv.Addr,
   147  		Handler:   gzip(mux),
   148  		ConnState: makeConnState(config.TLSConfig.EnableHTTP, handshakeTimeout, maxConns),
   149  		ErrorLog:  newHTTPServerLogger(srv.logger),
   150  	}
   151  
   152  	go func() {
   153  		defer close(srv.listenerCh)
   154  		httpServer.Serve(ln)
   155  	}()
   156  
   157  	return srv, nil
   158  }
   159  
   160  // makeConnState returns a ConnState func for use in an http.Server. If
   161  // isTLS=true and handshakeTimeout>0 then the handshakeTimeout will be applied
   162  // as a connection deadline to new connections and removed when the connection
   163  // is active (meaning it has successfully completed the TLS handshake).
   164  //
   165  // If limit > 0, a per-address connection limit will be enabled regardless of
   166  // TLS. If connLimit == 0 there is no connection limit.
   167  func makeConnState(isTLS bool, handshakeTimeout time.Duration, connLimit int) func(conn net.Conn, state http.ConnState) {
   168  	if !isTLS || handshakeTimeout == 0 {
   169  		if connLimit > 0 {
   170  			// Still return the connection limiter
   171  			return connlimit.NewLimiter(connlimit.Config{
   172  				MaxConnsPerClientIP: connLimit,
   173  			}).HTTPConnStateFunc()
   174  		}
   175  
   176  		return nil
   177  	}
   178  
   179  	if connLimit > 0 {
   180  		// Return conn state callback with connection limiting and a
   181  		// handshake timeout.
   182  
   183  		connLimiter := connlimit.NewLimiter(connlimit.Config{
   184  			MaxConnsPerClientIP: connLimit,
   185  		}).HTTPConnStateFunc()
   186  
   187  		return func(conn net.Conn, state http.ConnState) {
   188  			switch state {
   189  			case http.StateNew:
   190  				// Set deadline to prevent slow send before TLS handshake or first
   191  				// byte of request.
   192  				conn.SetDeadline(time.Now().Add(handshakeTimeout))
   193  			case http.StateActive:
   194  				// Clear read deadline. We should maybe set read timeouts more
   195  				// generally but that's a bigger task as some HTTP endpoints may
   196  				// stream large requests and responses (e.g. snapshot) so we can't
   197  				// set sensible blanket timeouts here.
   198  				conn.SetDeadline(time.Time{})
   199  			}
   200  
   201  			// Call connection limiter
   202  			connLimiter(conn, state)
   203  		}
   204  	}
   205  
   206  	// Return conn state callback with just a handshake timeout
   207  	// (connection limiting disabled).
   208  	return func(conn net.Conn, state http.ConnState) {
   209  		switch state {
   210  		case http.StateNew:
   211  			// Set deadline to prevent slow send before TLS handshake or first
   212  			// byte of request.
   213  			conn.SetDeadline(time.Now().Add(handshakeTimeout))
   214  		case http.StateActive:
   215  			// Clear read deadline. We should maybe set read timeouts more
   216  			// generally but that's a bigger task as some HTTP endpoints may
   217  			// stream large requests and responses (e.g. snapshot) so we can't
   218  			// set sensible blanket timeouts here.
   219  			conn.SetDeadline(time.Time{})
   220  		}
   221  	}
   222  }
   223  
   224  // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
   225  // connections. It's used by NewHttpServer so
   226  // dead TCP connections eventually go away.
   227  type tcpKeepAliveListener struct {
   228  	*net.TCPListener
   229  }
   230  
   231  func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
   232  	tc, err := ln.AcceptTCP()
   233  	if err != nil {
   234  		return
   235  	}
   236  	tc.SetKeepAlive(true)
   237  	tc.SetKeepAlivePeriod(30 * time.Second)
   238  	return tc, nil
   239  }
   240  
   241  // Shutdown is used to shutdown the HTTP server
   242  func (s *HTTPServer) Shutdown() {
   243  	if s != nil {
   244  		s.logger.Debug("shutting down http server")
   245  		s.listener.Close()
   246  		<-s.listenerCh // block until http.Serve has returned.
   247  	}
   248  }
   249  
   250  // registerHandlers is used to attach our handlers to the mux
   251  func (s *HTTPServer) registerHandlers(enableDebug bool) {
   252  	s.mux.HandleFunc("/v1/jobs", s.wrap(s.JobsRequest))
   253  	s.mux.HandleFunc("/v1/jobs/parse", s.wrap(s.JobsParseRequest))
   254  	s.mux.HandleFunc("/v1/job/", s.wrap(s.JobSpecificRequest))
   255  
   256  	s.mux.HandleFunc("/v1/nodes", s.wrap(s.NodesRequest))
   257  	s.mux.HandleFunc("/v1/node/", s.wrap(s.NodeSpecificRequest))
   258  
   259  	s.mux.HandleFunc("/v1/allocations", s.wrap(s.AllocsRequest))
   260  	s.mux.HandleFunc("/v1/allocation/", s.wrap(s.AllocSpecificRequest))
   261  
   262  	s.mux.HandleFunc("/v1/evaluations", s.wrap(s.EvalsRequest))
   263  	s.mux.HandleFunc("/v1/evaluation/", s.wrap(s.EvalSpecificRequest))
   264  
   265  	s.mux.HandleFunc("/v1/deployments", s.wrap(s.DeploymentsRequest))
   266  	s.mux.HandleFunc("/v1/deployment/", s.wrap(s.DeploymentSpecificRequest))
   267  
   268  	s.mux.HandleFunc("/v1/volumes", s.wrap(s.CSIVolumesRequest))
   269  	s.mux.HandleFunc("/v1/volume/csi/", s.wrap(s.CSIVolumeSpecificRequest))
   270  	s.mux.HandleFunc("/v1/plugins", s.wrap(s.CSIPluginsRequest))
   271  	s.mux.HandleFunc("/v1/plugin/csi/", s.wrap(s.CSIPluginSpecificRequest))
   272  
   273  	s.mux.HandleFunc("/v1/acl/policies", s.wrap(s.ACLPoliciesRequest))
   274  	s.mux.HandleFunc("/v1/acl/policy/", s.wrap(s.ACLPolicySpecificRequest))
   275  
   276  	s.mux.HandleFunc("/v1/acl/bootstrap", s.wrap(s.ACLTokenBootstrap))
   277  	s.mux.HandleFunc("/v1/acl/tokens", s.wrap(s.ACLTokensRequest))
   278  	s.mux.HandleFunc("/v1/acl/token", s.wrap(s.ACLTokenSpecificRequest))
   279  	s.mux.HandleFunc("/v1/acl/token/", s.wrap(s.ACLTokenSpecificRequest))
   280  
   281  	s.mux.Handle("/v1/client/fs/", wrapCORS(s.wrap(s.FsRequest)))
   282  	s.mux.HandleFunc("/v1/client/gc", s.wrap(s.ClientGCRequest))
   283  	s.mux.Handle("/v1/client/stats", wrapCORS(s.wrap(s.ClientStatsRequest)))
   284  	s.mux.Handle("/v1/client/allocation/", wrapCORS(s.wrap(s.ClientAllocRequest)))
   285  
   286  	s.mux.HandleFunc("/v1/agent/self", s.wrap(s.AgentSelfRequest))
   287  	s.mux.HandleFunc("/v1/agent/join", s.wrap(s.AgentJoinRequest))
   288  	s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembersRequest))
   289  	s.mux.HandleFunc("/v1/agent/force-leave", s.wrap(s.AgentForceLeaveRequest))
   290  	s.mux.HandleFunc("/v1/agent/servers", s.wrap(s.AgentServersRequest))
   291  	s.mux.HandleFunc("/v1/agent/keyring/", s.wrap(s.KeyringOperationRequest))
   292  	s.mux.HandleFunc("/v1/agent/health", s.wrap(s.HealthRequest))
   293  
   294  	// Monitor is *not* an untrusted endpoint despite the log contents
   295  	// potentially containing unsanitized user input. Monitor, like
   296  	// "/v1/client/fs/logs", explicitly sets a "text/plain" or
   297  	// "application/json" Content-Type depending on the ?plain= query
   298  	// parameter.
   299  	s.mux.HandleFunc("/v1/agent/monitor", s.wrap(s.AgentMonitor))
   300  
   301  	s.mux.HandleFunc("/v1/agent/pprof/", s.wrapNonJSON(s.AgentPprofRequest))
   302  
   303  	s.mux.HandleFunc("/v1/metrics", s.wrap(s.MetricsRequest))
   304  
   305  	s.mux.HandleFunc("/v1/validate/job", s.wrap(s.ValidateJobRequest))
   306  
   307  	s.mux.HandleFunc("/v1/regions", s.wrap(s.RegionListRequest))
   308  
   309  	s.mux.HandleFunc("/v1/scaling/policies", s.wrap(s.ScalingPoliciesRequest))
   310  	s.mux.HandleFunc("/v1/scaling/policy/", s.wrap(s.ScalingPolicySpecificRequest))
   311  
   312  	s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest))
   313  	s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest))
   314  
   315  	s.mux.HandleFunc("/v1/search", s.wrap(s.SearchRequest))
   316  
   317  	s.mux.HandleFunc("/v1/operator/raft/", s.wrap(s.OperatorRequest))
   318  	s.mux.HandleFunc("/v1/operator/autopilot/configuration", s.wrap(s.OperatorAutopilotConfiguration))
   319  	s.mux.HandleFunc("/v1/operator/autopilot/health", s.wrap(s.OperatorServerHealth))
   320  
   321  	s.mux.HandleFunc("/v1/system/gc", s.wrap(s.GarbageCollectRequest))
   322  	s.mux.HandleFunc("/v1/system/reconcile/summaries", s.wrap(s.ReconcileJobSummaries))
   323  
   324  	s.mux.HandleFunc("/v1/operator/scheduler/configuration", s.wrap(s.OperatorSchedulerConfiguration))
   325  
   326  	if uiEnabled {
   327  		s.mux.Handle("/ui/", http.StripPrefix("/ui/", s.handleUI(http.FileServer(&UIAssetWrapper{FileSystem: assetFS()}))))
   328  	} else {
   329  		// Write the stubHTML
   330  		s.mux.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
   331  			w.Write([]byte(stubHTML))
   332  		})
   333  	}
   334  	s.mux.Handle("/", s.handleRootFallthrough())
   335  
   336  	if enableDebug {
   337  		if !s.agent.config.DevMode {
   338  			s.logger.Warn("enable_debug is set to true. This is insecure and should not be enabled in production")
   339  		}
   340  		s.mux.HandleFunc("/debug/pprof/", pprof.Index)
   341  		s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
   342  		s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
   343  		s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
   344  		s.mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
   345  	}
   346  
   347  	// Register enterprise endpoints.
   348  	s.registerEnterpriseHandlers()
   349  }
   350  
   351  // HTTPCodedError is used to provide the HTTP error code
   352  type HTTPCodedError interface {
   353  	error
   354  	Code() int
   355  }
   356  
   357  type UIAssetWrapper struct {
   358  	FileSystem *assetfs.AssetFS
   359  }
   360  
   361  func (fs *UIAssetWrapper) Open(name string) (http.File, error) {
   362  	if file, err := fs.FileSystem.Open(name); err == nil {
   363  		return file, nil
   364  	} else {
   365  		// serve index.html instead of 404ing
   366  		if err == os.ErrNotExist {
   367  			return fs.FileSystem.Open("index.html")
   368  		}
   369  		return nil, err
   370  	}
   371  }
   372  
   373  func CodedError(c int, s string) HTTPCodedError {
   374  	return &codedError{s, c}
   375  }
   376  
   377  type codedError struct {
   378  	s    string
   379  	code int
   380  }
   381  
   382  func (e *codedError) Error() string {
   383  	return e.s
   384  }
   385  
   386  func (e *codedError) Code() int {
   387  	return e.code
   388  }
   389  
   390  func (s *HTTPServer) handleUI(h http.Handler) http.Handler {
   391  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   392  		header := w.Header()
   393  		header.Add("Content-Security-Policy", "default-src 'none'; connect-src *; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'none'; frame-ancestors 'none'")
   394  		h.ServeHTTP(w, req)
   395  		return
   396  	})
   397  }
   398  
   399  func (s *HTTPServer) handleRootFallthrough() http.Handler {
   400  	return s.auditHTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   401  		if req.URL.Path == "/" {
   402  			http.Redirect(w, req, "/ui/", 307)
   403  		} else {
   404  			w.WriteHeader(http.StatusNotFound)
   405  		}
   406  	}))
   407  }
   408  
   409  func errCodeFromHandler(err error) (int, string) {
   410  	if err == nil {
   411  		return 0, ""
   412  	}
   413  
   414  	code := 500
   415  	errMsg := err.Error()
   416  	if http, ok := err.(HTTPCodedError); ok {
   417  		code = http.Code()
   418  	} else if ecode, emsg, ok := structs.CodeFromRPCCodedErr(err); ok {
   419  		code = ecode
   420  		errMsg = emsg
   421  	} else {
   422  		// RPC errors get wrapped, so manually unwrap by only looking at their suffix
   423  		if strings.HasSuffix(errMsg, structs.ErrPermissionDenied.Error()) {
   424  			errMsg = structs.ErrPermissionDenied.Error()
   425  			code = 403
   426  		} else if strings.HasSuffix(errMsg, structs.ErrTokenNotFound.Error()) {
   427  			errMsg = structs.ErrTokenNotFound.Error()
   428  			code = 403
   429  		}
   430  	}
   431  
   432  	return code, errMsg
   433  }
   434  
   435  // wrap is used to wrap functions to make them more convenient
   436  func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
   437  	f := func(resp http.ResponseWriter, req *http.Request) {
   438  		setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders)
   439  		// Invoke the handler
   440  		reqURL := req.URL.String()
   441  		start := time.Now()
   442  		defer func() {
   443  			s.logger.Debug("request complete", "method", req.Method, "path", reqURL, "duration", time.Now().Sub(start))
   444  		}()
   445  		obj, err := s.auditHandler(handler)(resp, req)
   446  
   447  		// Check for an error
   448  	HAS_ERR:
   449  		if err != nil {
   450  			code := 500
   451  			errMsg := err.Error()
   452  			if http, ok := err.(HTTPCodedError); ok {
   453  				code = http.Code()
   454  			} else if ecode, emsg, ok := structs.CodeFromRPCCodedErr(err); ok {
   455  				code = ecode
   456  				errMsg = emsg
   457  			} else {
   458  				// RPC errors get wrapped, so manually unwrap by only looking at their suffix
   459  				if strings.HasSuffix(errMsg, structs.ErrPermissionDenied.Error()) {
   460  					errMsg = structs.ErrPermissionDenied.Error()
   461  					code = 403
   462  				} else if strings.HasSuffix(errMsg, structs.ErrTokenNotFound.Error()) {
   463  					errMsg = structs.ErrTokenNotFound.Error()
   464  					code = 403
   465  				}
   466  			}
   467  
   468  			resp.WriteHeader(code)
   469  			resp.Write([]byte(errMsg))
   470  			if isAPIClientError(code) {
   471  				s.logger.Debug("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code)
   472  			} else {
   473  				s.logger.Error("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code)
   474  			}
   475  			return
   476  		}
   477  
   478  		prettyPrint := false
   479  		if v, ok := req.URL.Query()["pretty"]; ok {
   480  			if len(v) > 0 && (len(v[0]) == 0 || v[0] != "0") {
   481  				prettyPrint = true
   482  			}
   483  		}
   484  
   485  		// Write out the JSON object
   486  		if obj != nil {
   487  			var buf bytes.Buffer
   488  			if prettyPrint {
   489  				enc := codec.NewEncoder(&buf, structs.JsonHandlePretty)
   490  				err = enc.Encode(obj)
   491  				if err == nil {
   492  					buf.Write([]byte("\n"))
   493  				}
   494  			} else {
   495  				enc := codec.NewEncoder(&buf, structs.JsonHandle)
   496  				err = enc.Encode(obj)
   497  			}
   498  			if err != nil {
   499  				goto HAS_ERR
   500  			}
   501  			resp.Header().Set("Content-Type", "application/json")
   502  			resp.Write(buf.Bytes())
   503  		}
   504  	}
   505  	return f
   506  }
   507  
   508  // wrapNonJSON is used to wrap functions returning non JSON
   509  // serializeable data to make them more convenient. It is primarily
   510  // responsible for setting nomad headers and logging.
   511  // Handler functions are responsible for setting Content-Type Header
   512  func (s *HTTPServer) wrapNonJSON(handler func(resp http.ResponseWriter, req *http.Request) ([]byte, error)) func(resp http.ResponseWriter, req *http.Request) {
   513  	f := func(resp http.ResponseWriter, req *http.Request) {
   514  		setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders)
   515  		// Invoke the handler
   516  		reqURL := req.URL.String()
   517  		start := time.Now()
   518  		defer func() {
   519  			s.logger.Debug("request complete", "method", req.Method, "path", reqURL, "duration", time.Now().Sub(start))
   520  		}()
   521  		obj, err := s.auditNonJSONHandler(handler)(resp, req)
   522  
   523  		// Check for an error
   524  		if err != nil {
   525  			code, errMsg := errCodeFromHandler(err)
   526  			resp.WriteHeader(code)
   527  			resp.Write([]byte(errMsg))
   528  			if isAPIClientError(code) {
   529  				s.logger.Debug("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code)
   530  			} else {
   531  				s.logger.Error("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code)
   532  			}
   533  			return
   534  		}
   535  
   536  		// write response
   537  		if obj != nil {
   538  			resp.Write(obj)
   539  		}
   540  	}
   541  	return f
   542  }
   543  
   544  // isAPIClientError returns true if the passed http code represents a client error
   545  func isAPIClientError(code int) bool {
   546  	return 400 <= code && code <= 499
   547  }
   548  
   549  // decodeBody is used to decode a JSON request body
   550  func decodeBody(req *http.Request, out interface{}) error {
   551  	dec := json.NewDecoder(req.Body)
   552  	return dec.Decode(&out)
   553  }
   554  
   555  // setIndex is used to set the index response header
   556  func setIndex(resp http.ResponseWriter, index uint64) {
   557  	resp.Header().Set("X-Nomad-Index", strconv.FormatUint(index, 10))
   558  }
   559  
   560  // setKnownLeader is used to set the known leader header
   561  func setKnownLeader(resp http.ResponseWriter, known bool) {
   562  	s := "true"
   563  	if !known {
   564  		s = "false"
   565  	}
   566  	resp.Header().Set("X-Nomad-KnownLeader", s)
   567  }
   568  
   569  // setLastContact is used to set the last contact header
   570  func setLastContact(resp http.ResponseWriter, last time.Duration) {
   571  	lastMsec := uint64(last / time.Millisecond)
   572  	resp.Header().Set("X-Nomad-LastContact", strconv.FormatUint(lastMsec, 10))
   573  }
   574  
   575  // setMeta is used to set the query response meta data
   576  func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) {
   577  	setIndex(resp, m.Index)
   578  	setLastContact(resp, m.LastContact)
   579  	setKnownLeader(resp, m.KnownLeader)
   580  }
   581  
   582  // setHeaders is used to set canonical response header fields
   583  func setHeaders(resp http.ResponseWriter, headers map[string]string) {
   584  	for field, value := range headers {
   585  		resp.Header().Set(http.CanonicalHeaderKey(field), value)
   586  	}
   587  }
   588  
   589  // parseWait is used to parse the ?wait and ?index query params
   590  // Returns true on error
   591  func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool {
   592  	query := req.URL.Query()
   593  	if wait := query.Get("wait"); wait != "" {
   594  		dur, err := time.ParseDuration(wait)
   595  		if err != nil {
   596  			resp.WriteHeader(400)
   597  			resp.Write([]byte("Invalid wait time"))
   598  			return true
   599  		}
   600  		b.MaxQueryTime = dur
   601  	}
   602  	if idx := query.Get("index"); idx != "" {
   603  		index, err := strconv.ParseUint(idx, 10, 64)
   604  		if err != nil {
   605  			resp.WriteHeader(400)
   606  			resp.Write([]byte("Invalid index"))
   607  			return true
   608  		}
   609  		b.MinQueryIndex = index
   610  	}
   611  	return false
   612  }
   613  
   614  // parseConsistency is used to parse the ?stale query params.
   615  func parseConsistency(req *http.Request, b *structs.QueryOptions) {
   616  	query := req.URL.Query()
   617  	if _, ok := query["stale"]; ok {
   618  		b.AllowStale = true
   619  	}
   620  }
   621  
   622  // parsePrefix is used to parse the ?prefix query param
   623  func parsePrefix(req *http.Request, b *structs.QueryOptions) {
   624  	query := req.URL.Query()
   625  	if prefix := query.Get("prefix"); prefix != "" {
   626  		b.Prefix = prefix
   627  	}
   628  }
   629  
   630  // parseRegion is used to parse the ?region query param
   631  func (s *HTTPServer) parseRegion(req *http.Request, r *string) {
   632  	if other := req.URL.Query().Get("region"); other != "" {
   633  		*r = other
   634  	} else if *r == "" {
   635  		*r = s.agent.config.Region
   636  	}
   637  }
   638  
   639  // parseNamespace is used to parse the ?namespace parameter
   640  func parseNamespace(req *http.Request, n *string) {
   641  	if other := req.URL.Query().Get("namespace"); other != "" {
   642  		*n = other
   643  	} else if *n == "" {
   644  		*n = structs.DefaultNamespace
   645  	}
   646  }
   647  
   648  // parseToken is used to parse the X-Nomad-Token param
   649  func (s *HTTPServer) parseToken(req *http.Request, token *string) {
   650  	if other := req.Header.Get("X-Nomad-Token"); other != "" {
   651  		*token = other
   652  		return
   653  	}
   654  }
   655  
   656  // parse is a convenience method for endpoints that need to parse multiple flags
   657  func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, r *string, b *structs.QueryOptions) bool {
   658  	s.parseRegion(req, r)
   659  	s.parseToken(req, &b.AuthToken)
   660  	parseConsistency(req, b)
   661  	parsePrefix(req, b)
   662  	parseNamespace(req, &b.Namespace)
   663  	return parseWait(resp, req, b)
   664  }
   665  
   666  // parseWriteRequest is a convenience method for endpoints that need to parse a
   667  // write request.
   668  func (s *HTTPServer) parseWriteRequest(req *http.Request, w *structs.WriteRequest) {
   669  	parseNamespace(req, &w.Namespace)
   670  	s.parseToken(req, &w.AuthToken)
   671  	s.parseRegion(req, &w.Region)
   672  }
   673  
   674  // wrapUntrustedContent wraps handlers in a http.ResponseWriter that prevents
   675  // setting Content-Types that a browser may render (eg text/html). Any API that
   676  // returns service-generated content (eg /v1/client/fs/cat) must be wrapped.
   677  func (s *HTTPServer) wrapUntrustedContent(handler handlerFn) handlerFn {
   678  	return func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   679  		resp, closeWriter := noxssrw.NewResponseWriter(resp)
   680  		defer func() {
   681  			if _, err := closeWriter(); err != nil {
   682  				// Can't write an error response at this point so just
   683  				// log. s.wrap does not even log when resp.Write fails,
   684  				// so log at low level.
   685  				s.logger.Debug("error writing HTTP response", "error", err,
   686  					"method", req.Method, "path", req.URL.String())
   687  			}
   688  		}()
   689  
   690  		// Call the wrapped handler
   691  		return handler(resp, req)
   692  	}
   693  }
   694  
   695  // wrapCORS wraps a HandlerFunc in allowCORS and returns a http.Handler
   696  func wrapCORS(f func(http.ResponseWriter, *http.Request)) http.Handler {
   697  	return allowCORS.Handler(http.HandlerFunc(f))
   698  }