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  }