github.com/nsqio/nsq@v1.3.0/nsqd/http.go (about) 1 package nsqd 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/http/pprof" 12 "net/url" 13 "os" 14 "reflect" 15 "runtime" 16 "runtime/debug" 17 "strconv" 18 "strings" 19 "time" 20 21 "github.com/julienschmidt/httprouter" 22 "github.com/nsqio/nsq/internal/http_api" 23 "github.com/nsqio/nsq/internal/lg" 24 "github.com/nsqio/nsq/internal/protocol" 25 "github.com/nsqio/nsq/internal/version" 26 ) 27 28 var boolParams = map[string]bool{ 29 "true": true, 30 "1": true, 31 "false": false, 32 "0": false, 33 } 34 35 type httpServer struct { 36 nsqd *NSQD 37 tlsEnabled bool 38 tlsRequired bool 39 router http.Handler 40 } 41 42 func newHTTPServer(nsqd *NSQD, tlsEnabled bool, tlsRequired bool) *httpServer { 43 log := http_api.Log(nsqd.logf) 44 45 router := httprouter.New() 46 router.HandleMethodNotAllowed = true 47 router.PanicHandler = http_api.LogPanicHandler(nsqd.logf) 48 router.NotFound = http_api.LogNotFoundHandler(nsqd.logf) 49 router.MethodNotAllowed = http_api.LogMethodNotAllowedHandler(nsqd.logf) 50 s := &httpServer{ 51 nsqd: nsqd, 52 tlsEnabled: tlsEnabled, 53 tlsRequired: tlsRequired, 54 router: router, 55 } 56 57 router.Handle("GET", "/ping", http_api.Decorate(s.pingHandler, log, http_api.PlainText)) 58 router.Handle("GET", "/info", http_api.Decorate(s.doInfo, log, http_api.V1)) 59 60 // v1 negotiate 61 router.Handle("POST", "/pub", http_api.Decorate(s.doPUB, http_api.V1)) 62 router.Handle("POST", "/mpub", http_api.Decorate(s.doMPUB, http_api.V1)) 63 router.Handle("GET", "/stats", http_api.Decorate(s.doStats, log, http_api.V1)) 64 65 // only v1 66 router.Handle("POST", "/topic/create", http_api.Decorate(s.doCreateTopic, log, http_api.V1)) 67 router.Handle("POST", "/topic/delete", http_api.Decorate(s.doDeleteTopic, log, http_api.V1)) 68 router.Handle("POST", "/topic/empty", http_api.Decorate(s.doEmptyTopic, log, http_api.V1)) 69 router.Handle("POST", "/topic/pause", http_api.Decorate(s.doPauseTopic, log, http_api.V1)) 70 router.Handle("POST", "/topic/unpause", http_api.Decorate(s.doPauseTopic, log, http_api.V1)) 71 router.Handle("POST", "/channel/create", http_api.Decorate(s.doCreateChannel, log, http_api.V1)) 72 router.Handle("POST", "/channel/delete", http_api.Decorate(s.doDeleteChannel, log, http_api.V1)) 73 router.Handle("POST", "/channel/empty", http_api.Decorate(s.doEmptyChannel, log, http_api.V1)) 74 router.Handle("POST", "/channel/pause", http_api.Decorate(s.doPauseChannel, log, http_api.V1)) 75 router.Handle("POST", "/channel/unpause", http_api.Decorate(s.doPauseChannel, log, http_api.V1)) 76 router.Handle("GET", "/config/:opt", http_api.Decorate(s.doConfig, log, http_api.V1)) 77 router.Handle("PUT", "/config/:opt", http_api.Decorate(s.doConfig, log, http_api.V1)) 78 79 // debug 80 router.HandlerFunc("GET", "/debug/pprof/", pprof.Index) 81 router.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) 82 router.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol) 83 router.HandlerFunc("POST", "/debug/pprof/symbol", pprof.Symbol) 84 router.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile) 85 router.Handler("GET", "/debug/pprof/heap", pprof.Handler("heap")) 86 router.Handler("GET", "/debug/pprof/goroutine", pprof.Handler("goroutine")) 87 router.Handler("GET", "/debug/pprof/block", pprof.Handler("block")) 88 router.Handle("PUT", "/debug/setblockrate", http_api.Decorate(setBlockRateHandler, log, http_api.PlainText)) 89 router.Handle("POST", "/debug/freememory", http_api.Decorate(freeMemory, log, http_api.PlainText)) 90 router.Handler("GET", "/debug/pprof/threadcreate", pprof.Handler("threadcreate")) 91 92 return s 93 } 94 95 func setBlockRateHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 96 rate, err := strconv.Atoi(req.FormValue("rate")) 97 if err != nil { 98 return nil, http_api.Err{http.StatusBadRequest, fmt.Sprintf("invalid block rate : %s", err.Error())} 99 } 100 runtime.SetBlockProfileRate(rate) 101 return nil, nil 102 } 103 104 func freeMemory(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 105 debug.FreeOSMemory() 106 return nil, nil 107 } 108 109 func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 110 if !s.tlsEnabled && s.tlsRequired { 111 resp := fmt.Sprintf(`{"message": "TLS_REQUIRED", "https_port": %d}`, 112 s.nsqd.RealHTTPSAddr().Port) 113 w.Header().Set("X-NSQ-Content-Type", "nsq; version=1.0") 114 w.Header().Set("Content-Type", "application/json; charset=utf-8") 115 w.WriteHeader(403) 116 io.WriteString(w, resp) 117 return 118 } 119 s.router.ServeHTTP(w, req) 120 } 121 122 func (s *httpServer) pingHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 123 health := s.nsqd.GetHealth() 124 if !s.nsqd.IsHealthy() { 125 return nil, http_api.Err{500, health} 126 } 127 return health, nil 128 } 129 130 func (s *httpServer) doInfo(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 131 hostname, err := os.Hostname() 132 if err != nil { 133 return nil, http_api.Err{500, err.Error()} 134 } 135 tcpPort := -1 // in case of unix socket 136 if s.nsqd.RealTCPAddr().Network() == "tcp" { 137 tcpPort = s.nsqd.RealTCPAddr().(*net.TCPAddr).Port 138 } 139 httpPort := -1 // in case of unix socket 140 if s.nsqd.RealHTTPAddr().Network() == "tcp" { 141 httpPort = s.nsqd.RealHTTPAddr().(*net.TCPAddr).Port 142 } 143 144 return struct { 145 Version string `json:"version"` 146 BroadcastAddress string `json:"broadcast_address"` 147 Hostname string `json:"hostname"` 148 HTTPPort int `json:"http_port"` 149 TCPPort int `json:"tcp_port"` 150 StartTime int64 `json:"start_time"` 151 MaxHeartBeatInterval time.Duration `json:"max_heartbeat_interval"` 152 MaxOutBufferSize int64 `json:"max_output_buffer_size"` 153 MaxOutBufferTimeout time.Duration `json:"max_output_buffer_timeout"` 154 MaxDeflateLevel int `json:"max_deflate_level"` 155 }{ 156 Version: version.Binary, 157 BroadcastAddress: s.nsqd.getOpts().BroadcastAddress, 158 Hostname: hostname, 159 TCPPort: tcpPort, 160 HTTPPort: httpPort, 161 StartTime: s.nsqd.GetStartTime().Unix(), 162 MaxHeartBeatInterval: s.nsqd.getOpts().MaxHeartbeatInterval, 163 MaxOutBufferSize: s.nsqd.getOpts().MaxOutputBufferSize, 164 MaxOutBufferTimeout: s.nsqd.getOpts().MaxOutputBufferTimeout, 165 MaxDeflateLevel: s.nsqd.getOpts().MaxDeflateLevel, 166 }, nil 167 } 168 169 func (s *httpServer) getExistingTopicFromQuery(req *http.Request) (*http_api.ReqParams, *Topic, string, error) { 170 reqParams, err := http_api.NewReqParams(req) 171 if err != nil { 172 s.nsqd.logf(LOG_ERROR, "failed to parse request params - %s", err) 173 return nil, nil, "", http_api.Err{400, "INVALID_REQUEST"} 174 } 175 176 topicName, channelName, err := http_api.GetTopicChannelArgs(reqParams) 177 if err != nil { 178 return nil, nil, "", http_api.Err{400, err.Error()} 179 } 180 181 topic, err := s.nsqd.GetExistingTopic(topicName) 182 if err != nil { 183 return nil, nil, "", http_api.Err{404, "TOPIC_NOT_FOUND"} 184 } 185 186 return reqParams, topic, channelName, err 187 } 188 189 func (s *httpServer) getTopicFromQuery(req *http.Request) (url.Values, *Topic, error) { 190 reqParams, err := url.ParseQuery(req.URL.RawQuery) 191 if err != nil { 192 s.nsqd.logf(LOG_ERROR, "failed to parse request params - %s", err) 193 return nil, nil, http_api.Err{400, "INVALID_REQUEST"} 194 } 195 196 topicNames, ok := reqParams["topic"] 197 if !ok { 198 return nil, nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 199 } 200 topicName := topicNames[0] 201 202 if !protocol.IsValidTopicName(topicName) { 203 return nil, nil, http_api.Err{400, "INVALID_TOPIC"} 204 } 205 206 return reqParams, s.nsqd.GetTopic(topicName), nil 207 } 208 209 func (s *httpServer) doPUB(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 210 // TODO: one day I'd really like to just error on chunked requests 211 // to be able to fail "too big" requests before we even read 212 213 if req.ContentLength > s.nsqd.getOpts().MaxMsgSize { 214 return nil, http_api.Err{413, "MSG_TOO_BIG"} 215 } 216 217 // add 1 so that it's greater than our max when we test for it 218 // (LimitReader returns a "fake" EOF) 219 readMax := s.nsqd.getOpts().MaxMsgSize + 1 220 body, err := io.ReadAll(io.LimitReader(req.Body, readMax)) 221 if err != nil { 222 return nil, http_api.Err{500, "INTERNAL_ERROR"} 223 } 224 if int64(len(body)) == readMax { 225 return nil, http_api.Err{413, "MSG_TOO_BIG"} 226 } 227 if len(body) == 0 { 228 return nil, http_api.Err{400, "MSG_EMPTY"} 229 } 230 231 reqParams, topic, err := s.getTopicFromQuery(req) 232 if err != nil { 233 return nil, err 234 } 235 236 var deferred time.Duration 237 if ds, ok := reqParams["defer"]; ok { 238 var di int64 239 di, err = strconv.ParseInt(ds[0], 10, 64) 240 if err != nil { 241 return nil, http_api.Err{400, "INVALID_DEFER"} 242 } 243 deferred = time.Duration(di) * time.Millisecond 244 if deferred < 0 || deferred > s.nsqd.getOpts().MaxReqTimeout { 245 return nil, http_api.Err{400, "INVALID_DEFER"} 246 } 247 } 248 249 msg := NewMessage(topic.GenerateID(), body) 250 msg.deferred = deferred 251 err = topic.PutMessage(msg) 252 if err != nil { 253 return nil, http_api.Err{503, "EXITING"} 254 } 255 256 return "OK", nil 257 } 258 259 func (s *httpServer) doMPUB(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 260 var msgs []*Message 261 var exit bool 262 263 // TODO: one day I'd really like to just error on chunked requests 264 // to be able to fail "too big" requests before we even read 265 266 if req.ContentLength > s.nsqd.getOpts().MaxBodySize { 267 return nil, http_api.Err{413, "BODY_TOO_BIG"} 268 } 269 270 reqParams, topic, err := s.getTopicFromQuery(req) 271 if err != nil { 272 return nil, err 273 } 274 275 // text mode is default, but unrecognized binary opt considered true 276 binaryMode := false 277 if vals, ok := reqParams["binary"]; ok { 278 if binaryMode, ok = boolParams[vals[0]]; !ok { 279 binaryMode = true 280 s.nsqd.logf(LOG_WARN, "deprecated value '%s' used for /mpub binary param", vals[0]) 281 } 282 } 283 if binaryMode { 284 tmp := make([]byte, 4) 285 msgs, err = readMPUB(req.Body, tmp, topic, 286 s.nsqd.getOpts().MaxMsgSize, s.nsqd.getOpts().MaxBodySize) 287 if err != nil { 288 return nil, http_api.Err{413, err.(*protocol.FatalClientErr).Code[2:]} 289 } 290 } else { 291 // add 1 so that it's greater than our max when we test for it 292 // (LimitReader returns a "fake" EOF) 293 readMax := s.nsqd.getOpts().MaxBodySize + 1 294 rdr := bufio.NewReader(io.LimitReader(req.Body, readMax)) 295 total := 0 296 for !exit { 297 var block []byte 298 block, err = rdr.ReadBytes('\n') 299 if err != nil { 300 if err != io.EOF { 301 return nil, http_api.Err{500, "INTERNAL_ERROR"} 302 } 303 exit = true 304 } 305 total += len(block) 306 if int64(total) == readMax { 307 return nil, http_api.Err{413, "BODY_TOO_BIG"} 308 } 309 310 if len(block) > 0 && block[len(block)-1] == '\n' { 311 block = block[:len(block)-1] 312 } 313 314 // silently discard 0 length messages 315 // this maintains the behavior pre 0.2.22 316 if len(block) == 0 { 317 continue 318 } 319 320 if int64(len(block)) > s.nsqd.getOpts().MaxMsgSize { 321 return nil, http_api.Err{413, "MSG_TOO_BIG"} 322 } 323 324 msg := NewMessage(topic.GenerateID(), block) 325 msgs = append(msgs, msg) 326 } 327 } 328 329 err = topic.PutMessages(msgs) 330 if err != nil { 331 return nil, http_api.Err{503, "EXITING"} 332 } 333 334 return "OK", nil 335 } 336 337 func (s *httpServer) doCreateTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 338 _, _, err := s.getTopicFromQuery(req) 339 return nil, err 340 } 341 342 func (s *httpServer) doEmptyTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 343 reqParams, err := http_api.NewReqParams(req) 344 if err != nil { 345 s.nsqd.logf(LOG_ERROR, "failed to parse request params - %s", err) 346 return nil, http_api.Err{400, "INVALID_REQUEST"} 347 } 348 349 topicName, err := reqParams.Get("topic") 350 if err != nil { 351 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 352 } 353 354 if !protocol.IsValidTopicName(topicName) { 355 return nil, http_api.Err{400, "INVALID_TOPIC"} 356 } 357 358 topic, err := s.nsqd.GetExistingTopic(topicName) 359 if err != nil { 360 return nil, http_api.Err{404, "TOPIC_NOT_FOUND"} 361 } 362 363 err = topic.Empty() 364 if err != nil { 365 return nil, http_api.Err{500, "INTERNAL_ERROR"} 366 } 367 368 return nil, nil 369 } 370 371 func (s *httpServer) doDeleteTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 372 reqParams, err := http_api.NewReqParams(req) 373 if err != nil { 374 s.nsqd.logf(LOG_ERROR, "failed to parse request params - %s", err) 375 return nil, http_api.Err{400, "INVALID_REQUEST"} 376 } 377 378 topicName, err := reqParams.Get("topic") 379 if err != nil { 380 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 381 } 382 383 err = s.nsqd.DeleteExistingTopic(topicName) 384 if err != nil { 385 return nil, http_api.Err{404, "TOPIC_NOT_FOUND"} 386 } 387 388 return nil, nil 389 } 390 391 func (s *httpServer) doPauseTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 392 reqParams, err := http_api.NewReqParams(req) 393 if err != nil { 394 s.nsqd.logf(LOG_ERROR, "failed to parse request params - %s", err) 395 return nil, http_api.Err{400, "INVALID_REQUEST"} 396 } 397 398 topicName, err := reqParams.Get("topic") 399 if err != nil { 400 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 401 } 402 403 topic, err := s.nsqd.GetExistingTopic(topicName) 404 if err != nil { 405 return nil, http_api.Err{404, "TOPIC_NOT_FOUND"} 406 } 407 408 if strings.Contains(req.URL.Path, "unpause") { 409 err = topic.UnPause() 410 } else { 411 err = topic.Pause() 412 } 413 if err != nil { 414 s.nsqd.logf(LOG_ERROR, "failure in %s - %s", req.URL.Path, err) 415 return nil, http_api.Err{500, "INTERNAL_ERROR"} 416 } 417 418 // pro-actively persist metadata so in case of process failure 419 // nsqd won't suddenly (un)pause a topic 420 s.nsqd.Lock() 421 s.nsqd.PersistMetadata() 422 s.nsqd.Unlock() 423 return nil, nil 424 } 425 426 func (s *httpServer) doCreateChannel(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 427 _, topic, channelName, err := s.getExistingTopicFromQuery(req) 428 if err != nil { 429 return nil, err 430 } 431 topic.GetChannel(channelName) 432 return nil, nil 433 } 434 435 func (s *httpServer) doEmptyChannel(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 436 _, topic, channelName, err := s.getExistingTopicFromQuery(req) 437 if err != nil { 438 return nil, err 439 } 440 441 channel, err := topic.GetExistingChannel(channelName) 442 if err != nil { 443 return nil, http_api.Err{404, "CHANNEL_NOT_FOUND"} 444 } 445 446 err = channel.Empty() 447 if err != nil { 448 return nil, http_api.Err{500, "INTERNAL_ERROR"} 449 } 450 451 return nil, nil 452 } 453 454 func (s *httpServer) doDeleteChannel(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 455 _, topic, channelName, err := s.getExistingTopicFromQuery(req) 456 if err != nil { 457 return nil, err 458 } 459 460 err = topic.DeleteExistingChannel(channelName) 461 if err != nil { 462 return nil, http_api.Err{404, "CHANNEL_NOT_FOUND"} 463 } 464 465 return nil, nil 466 } 467 468 func (s *httpServer) doPauseChannel(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 469 _, topic, channelName, err := s.getExistingTopicFromQuery(req) 470 if err != nil { 471 return nil, err 472 } 473 474 channel, err := topic.GetExistingChannel(channelName) 475 if err != nil { 476 return nil, http_api.Err{404, "CHANNEL_NOT_FOUND"} 477 } 478 479 if strings.Contains(req.URL.Path, "unpause") { 480 err = channel.UnPause() 481 } else { 482 err = channel.Pause() 483 } 484 if err != nil { 485 s.nsqd.logf(LOG_ERROR, "failure in %s - %s", req.URL.Path, err) 486 return nil, http_api.Err{500, "INTERNAL_ERROR"} 487 } 488 489 // pro-actively persist metadata so in case of process failure 490 // nsqd won't suddenly (un)pause a channel 491 s.nsqd.Lock() 492 s.nsqd.PersistMetadata() 493 s.nsqd.Unlock() 494 return nil, nil 495 } 496 497 func (s *httpServer) doStats(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 498 reqParams, err := http_api.NewReqParams(req) 499 if err != nil { 500 s.nsqd.logf(LOG_ERROR, "failed to parse request params - %s", err) 501 return nil, http_api.Err{400, "INVALID_REQUEST"} 502 } 503 formatString, _ := reqParams.Get("format") 504 topicName, _ := reqParams.Get("topic") 505 channelName, _ := reqParams.Get("channel") 506 includeClientsParam, _ := reqParams.Get("include_clients") 507 includeMemParam, _ := reqParams.Get("include_mem") 508 jsonFormat := formatString == "json" 509 510 includeClients, ok := boolParams[includeClientsParam] 511 if !ok { 512 includeClients = true 513 } 514 includeMem, ok := boolParams[includeMemParam] 515 if !ok { 516 includeMem = true 517 } 518 519 stats := s.nsqd.GetStats(topicName, channelName, includeClients) 520 health := s.nsqd.GetHealth() 521 startTime := s.nsqd.GetStartTime() 522 uptime := time.Since(startTime) 523 524 var ms *memStats 525 if includeMem { 526 m := getMemStats() 527 ms = &m 528 } 529 if !jsonFormat { 530 return s.printStats(stats, ms, health, startTime, uptime), nil 531 } 532 533 // TODO: should producer stats be hung off topics? 534 return struct { 535 Version string `json:"version"` 536 Health string `json:"health"` 537 StartTime int64 `json:"start_time"` 538 Topics []TopicStats `json:"topics"` 539 Memory *memStats `json:"memory,omitempty"` 540 Producers []ClientStats `json:"producers"` 541 }{version.Binary, health, startTime.Unix(), stats.Topics, ms, stats.Producers}, nil 542 } 543 544 func (s *httpServer) printStats(stats Stats, ms *memStats, health string, startTime time.Time, uptime time.Duration) []byte { 545 var buf bytes.Buffer 546 w := &buf 547 548 fmt.Fprintf(w, "%s\n", version.String("nsqd")) 549 fmt.Fprintf(w, "start_time %v\n", startTime.Format(time.RFC3339)) 550 fmt.Fprintf(w, "uptime %s\n", uptime) 551 552 fmt.Fprintf(w, "\nHealth: %s\n", health) 553 554 if ms != nil { 555 fmt.Fprintf(w, "\nMemory:\n") 556 fmt.Fprintf(w, " %-25s\t%d\n", "heap_objects", ms.HeapObjects) 557 fmt.Fprintf(w, " %-25s\t%d\n", "heap_idle_bytes", ms.HeapIdleBytes) 558 fmt.Fprintf(w, " %-25s\t%d\n", "heap_in_use_bytes", ms.HeapInUseBytes) 559 fmt.Fprintf(w, " %-25s\t%d\n", "heap_released_bytes", ms.HeapReleasedBytes) 560 fmt.Fprintf(w, " %-25s\t%d\n", "gc_pause_usec_100", ms.GCPauseUsec100) 561 fmt.Fprintf(w, " %-25s\t%d\n", "gc_pause_usec_99", ms.GCPauseUsec99) 562 fmt.Fprintf(w, " %-25s\t%d\n", "gc_pause_usec_95", ms.GCPauseUsec95) 563 fmt.Fprintf(w, " %-25s\t%d\n", "next_gc_bytes", ms.NextGCBytes) 564 fmt.Fprintf(w, " %-25s\t%d\n", "gc_total_runs", ms.GCTotalRuns) 565 } 566 567 if len(stats.Topics) == 0 { 568 fmt.Fprintf(w, "\nTopics: None\n") 569 } else { 570 fmt.Fprintf(w, "\nTopics:") 571 } 572 573 for _, t := range stats.Topics { 574 var pausedPrefix string 575 if t.Paused { 576 pausedPrefix = "*P " 577 } else { 578 pausedPrefix = " " 579 } 580 fmt.Fprintf(w, "\n%s[%-15s] depth: %-5d be-depth: %-5d msgs: %-8d e2e%%: %s\n", 581 pausedPrefix, 582 t.TopicName, 583 t.Depth, 584 t.BackendDepth, 585 t.MessageCount, 586 t.E2eProcessingLatency, 587 ) 588 for _, c := range t.Channels { 589 if c.Paused { 590 pausedPrefix = " *P " 591 } else { 592 pausedPrefix = " " 593 } 594 fmt.Fprintf(w, "%s[%-25s] depth: %-5d be-depth: %-5d inflt: %-4d def: %-4d re-q: %-5d timeout: %-5d msgs: %-8d e2e%%: %s\n", 595 pausedPrefix, 596 c.ChannelName, 597 c.Depth, 598 c.BackendDepth, 599 c.InFlightCount, 600 c.DeferredCount, 601 c.RequeueCount, 602 c.TimeoutCount, 603 c.MessageCount, 604 c.E2eProcessingLatency, 605 ) 606 for _, client := range c.Clients { 607 fmt.Fprintf(w, " %s\n", client) 608 } 609 } 610 } 611 612 if len(stats.Producers) == 0 { 613 fmt.Fprintf(w, "\nProducers: None\n") 614 } else { 615 fmt.Fprintf(w, "\nProducers:\n") 616 for _, client := range stats.Producers { 617 fmt.Fprintf(w, " %s\n", client) 618 } 619 } 620 621 return buf.Bytes() 622 } 623 624 func (s *httpServer) doConfig(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 625 opt := ps.ByName("opt") 626 627 if req.Method == "PUT" { 628 // add 1 so that it's greater than our max when we test for it 629 // (LimitReader returns a "fake" EOF) 630 readMax := s.nsqd.getOpts().MaxMsgSize + 1 631 body, err := io.ReadAll(io.LimitReader(req.Body, readMax)) 632 if err != nil { 633 return nil, http_api.Err{500, "INTERNAL_ERROR"} 634 } 635 if int64(len(body)) == readMax || len(body) == 0 { 636 return nil, http_api.Err{413, "INVALID_VALUE"} 637 } 638 639 opts := *s.nsqd.getOpts() 640 switch opt { 641 case "nsqlookupd_tcp_addresses": 642 err := json.Unmarshal(body, &opts.NSQLookupdTCPAddresses) 643 if err != nil { 644 return nil, http_api.Err{400, "INVALID_VALUE"} 645 } 646 case "log_level": 647 logLevelStr := string(body) 648 logLevel, err := lg.ParseLogLevel(logLevelStr) 649 if err != nil { 650 return nil, http_api.Err{400, "INVALID_VALUE"} 651 } 652 opts.LogLevel = logLevel 653 default: 654 return nil, http_api.Err{400, "INVALID_OPTION"} 655 } 656 s.nsqd.swapOpts(&opts) 657 s.nsqd.triggerOptsNotification() 658 } 659 660 v, ok := getOptByCfgName(s.nsqd.getOpts(), opt) 661 if !ok { 662 return nil, http_api.Err{400, "INVALID_OPTION"} 663 } 664 665 return v, nil 666 } 667 668 func getOptByCfgName(opts interface{}, name string) (interface{}, bool) { 669 val := reflect.ValueOf(opts).Elem() 670 typ := val.Type() 671 for i := 0; i < typ.NumField(); i++ { 672 field := typ.Field(i) 673 flagName := field.Tag.Get("flag") 674 cfgName := field.Tag.Get("cfg") 675 if flagName == "" { 676 continue 677 } 678 if cfgName == "" { 679 cfgName = strings.Replace(flagName, "-", "_", -1) 680 } 681 if name != cfgName { 682 continue 683 } 684 return val.FieldByName(field.Name).Interface(), true 685 } 686 return nil, false 687 }