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 }