func (s *httpServer) pauseChannelHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, channelName, err := util.GetTopicChannelArgs(reqParams) if err != nil { util.ApiResponse(w, 500, err.Error(), nil) return } topic, err := s.context.nsqd.GetExistingTopic(topicName) if err != nil { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } channel, err := topic.GetExistingChannel(channelName) if err != nil { util.ApiResponse(w, 500, "INVALID_CHANNEL", nil) return } if strings.HasPrefix(req.URL.Path, "/pause") { channel.Pause() } else { channel.UnPause() } util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) deleteChannelHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, channelName, err := util.GetTopicChannelArgs(reqParams) if err != nil { util.ApiResponse(w, 500, err.Error(), nil) return } topic, err := s.context.nsqd.GetExistingTopic(topicName) if err != nil { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } err = topic.DeleteExistingChannel(channelName) if err != nil { util.ApiResponse(w, 500, "INVALID_CHANNEL", nil) return } util.ApiResponse(w, 200, "OK", nil) }
func deleteTopicHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } registrations := lookupd.DB.FindRegistrations("channel", topicName, "*") for _, registration := range registrations { log.Printf("DB: removing channel(%s) from topic(%s)", registration.SubKey, topicName) lookupd.DB.RemoveRegistration(*registration) } registrations = lookupd.DB.FindRegistrations("topic", topicName, "") for _, registration := range registrations { log.Printf("DB: removing topic(%s)", topicName) lookupd.DB.RemoveRegistration(*registration) } util.ApiResponse(w, 200, "OK", nil) }
func putHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } if !nsq.IsValidTopicName(topicName) { util.ApiResponse(w, 500, "INVALID_ARG_TOPIC", nil) return } if int64(len(reqParams.Body)) > nsqd.options.maxMessageSize { util.ApiResponse(w, 500, "MSG_TOO_BIG", nil) return } topic := nsqd.GetTopic(topicName) msg := nsq.NewMessage(<-nsqd.idChan, reqParams.Body) err = topic.PutMessage(msg) if err != nil { util.ApiResponse(w, 500, "NOK", nil) return } w.Header().Set("Content-Length", "2") io.WriteString(w, "OK") }
func tombstoneTopicProducerHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } node, err := reqParams.Get("node") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_NODE", nil) return } log.Printf("DB: setting tombstone for producer@%s of topic(%s)", node, topicName) producers := lookupd.DB.FindProducers("topic", topicName, "") for _, p := range producers { thisNode := fmt.Sprintf("%s:%d", p.peerInfo.BroadcastAddress, p.peerInfo.HttpPort) if thisNode == node { p.Tombstone() } } util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) pauseTopicHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } topic, err := s.context.nsqd.GetExistingTopic(topicName) if err != nil { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } if strings.HasPrefix(req.URL.Path, "/pause") { err = topic.Pause() } else { err = topic.UnPause() } if err != nil { log.Printf("ERROR: failure in %s - %s", req.URL.Path, err.Error()) } util.ApiResponse(w, 200, "OK", nil) }
func deleteChannelHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, channelName, err := util.GetTopicChannelArgs(reqParams) if err != nil { util.ApiResponse(w, 500, err.Error(), nil) return } registrations := lookupd.DB.FindRegistrations("channel", topicName, channelName) if len(registrations) == 0 { util.ApiResponse(w, 404, "NOT_FOUND", nil) return } log.Printf("DB: removing channel(%s) from topic(%s)", channelName, topicName) for _, registration := range registrations { lookupd.DB.RemoveRegistration(*registration) } util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) statsHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err) if acceptVersion(req) == 1 { util.V1ApiResponse(w, 400, "INVALID_REQUEST") } else { // this handler always returns 500 for backwards compatibility util.ApiResponse(w, 500, "INVALID_REQUEST", nil) } return } formatString, _ := reqParams.Get("format") jsonFormat := formatString == "json" stats := s.context.nsqd.GetStats() if !jsonFormat { s.doTextStats(stats, w) return } if acceptVersion(req) == 1 { util.V1ApiResponse(w, 200, struct { Version string `json:"version"` Topics []TopicStats `json:"topics"` }{util.BINARY_VERSION, stats}) } else { util.ApiResponse(w, 200, "OK", struct { Version string `json:"version"` Topics []TopicStats `json:"topics"` }{util.BINARY_VERSION, stats}) } }
func lookupHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } registration := lookupd.DB.FindRegistrations("topic", topicName, "") if len(registration) == 0 { util.ApiResponse(w, 500, "INVALID_ARG_TOPIC", nil) return } channels := lookupd.DB.FindRegistrations("channel", topicName, "*").SubKeys() producers := lookupd.DB.FindProducers("topic", topicName, "") producers = producers.FilterByActive(lookupd.inactiveProducerTimeout, lookupd.tombstoneLifetime) data := make(map[string]interface{}) data["channels"] = channels data["producers"] = producers.PeerInfo() util.ApiResponse(w, 200, "OK", data) }
// this endpoint works by giving out an ID that maps to a stats dictionary // The initial request is the number of messages processed since each nsqd started up. // Subsequent requsts pass that ID and get an updated delta based on each individual channel/nsqd message count // That ID must be re-requested or it will be expired. func (s *httpServer) counterDataHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } statsID, _ := reqParams.Get("id") now := time.Now() if statsID == "" { // make a new one statsID = fmt.Sprintf("%d.%d", now.Unix(), now.UnixNano()) } stats, ok := s.counters[statsID] if !ok { stats = make(map[string]int64) } newStats := make(map[string]int64) newStats["time"] = now.Unix() producers, _ := lookupd.GetLookupdProducers(s.context.nsqadmin.options.NSQLookupdHTTPAddresses) addresses := make([]string, len(producers)) for i, p := range producers { addresses[i] = p.HTTPAddress() } _, channelStats, _ := lookupd.GetNSQDStats(addresses, "") var newMessages int64 var totalMessages int64 for _, channelStats := range channelStats { for _, hostChannelStats := range channelStats.HostStats { key := fmt.Sprintf("%s:%s:%s", channelStats.TopicName, channelStats.ChannelName, hostChannelStats.HostAddress) d, ok := stats[key] if ok && d <= hostChannelStats.MessageCount { newMessages += (hostChannelStats.MessageCount - d) } totalMessages += hostChannelStats.MessageCount newStats[key] = hostChannelStats.MessageCount } } s.counters[statsID] = newStats data := make(map[string]interface{}) data["new_messages"] = newMessages data["total_messages"] = totalMessages data["id"] = statsID util.ApiResponse(w, 200, "OK", data) }
func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/ping": s.pingHandler(w, req) case "/info": s.infoHandler(w, req) case "/lookup": s.lookupHandler(w, req) case "/topics": s.topicsHandler(w, req) case "/channels": s.channelsHandler(w, req) case "/nodes": s.nodesHandler(w, req) case "/delete_topic": s.deleteTopicHandler(w, req) case "/delete_channel": s.deleteChannelHandler(w, req) case "/tombstone_topic_producer": s.tombstoneTopicProducerHandler(w, req) case "/create_topic": s.createTopicHandler(w, req) case "/create_channel": s.createChannelHandler(w, req) case "/debug": s.debugHandler(w, req) default: util.ApiResponse(w, 404, "NOT_FOUND", nil) } }
func infoHandler(w http.ResponseWriter, req *http.Request) { util.ApiResponse(w, 200, "OK", struct { Version string `json:"version"` }{ Version: util.BINARY_VERSION, }) }
func debugHandler(w http.ResponseWriter, req *http.Request) { lookupd.DB.RLock() defer lookupd.DB.RUnlock() data := make(map[string][]map[string]interface{}) for r, producers := range lookupd.DB.registrationMap { key := r.Category + ":" + r.Key + ":" + r.SubKey data[key] = make([]map[string]interface{}, 0) for _, p := range producers { m := make(map[string]interface{}) m["id"] = p.peerInfo.id m["address"] = p.peerInfo.Address //TODO: remove for 1.0 m["hostname"] = p.peerInfo.Hostname m["broadcast_address"] = p.peerInfo.BroadcastAddress m["tcp_port"] = p.peerInfo.TcpPort m["http_port"] = p.peerInfo.HttpPort m["version"] = p.peerInfo.Version m["last_update"] = p.peerInfo.lastUpdate.UnixNano() m["tombstoned"] = p.tombstoned m["tombstoned_at"] = p.tombstonedAt.UnixNano() data[key] = append(data[key], m) } } util.ApiResponse(w, 200, "OK", data) }
func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { err := s.debugRouter(w, req) if err != nil { log.Printf("ERROR: %s", err) util.ApiResponse(w, 404, "NOT_FOUND", nil) } }
func (s *httpServer) pauseChannelHandler(w http.ResponseWriter, req *http.Request) { err := s.doPauseChannel(req) if err != nil { if acceptVersion(req) == 1 { util.V1ApiResponse(w, err.(httpError).Code(), err) } else { // this handler always returns 500 for backwards compatibility util.ApiResponse(w, 500, err.Error(), nil) } return } if acceptVersion(req) == 1 { util.V1ApiResponse(w, 200, nil) } else { util.ApiResponse(w, 200, "OK", nil) } }
func (s *httpServer) createTopicHandler(w http.ResponseWriter, req *http.Request) { _, _, err := s.getTopicFromQuery(req) if err != nil { if acceptVersion(req) == 1 { util.V1ApiResponse(w, err.(httpError).Code(), err) } else { // this handler always returns 500 for backwards compatibility util.ApiResponse(w, 500, err.Error(), nil) } return } if acceptVersion(req) == 1 { util.V1ApiResponse(w, 200, nil) } else { util.ApiResponse(w, 200, "OK", nil) } }
func channelsHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } channels := lookupd.DB.FindRegistrations("channel", topicName, "*").SubKeys() data := make(map[string]interface{}) data["channels"] = channels util.ApiResponse(w, 200, "OK", data) }
func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/pub": fallthrough case "/put": s.putHandler(w, req) case "/mpub": fallthrough case "/mput": s.mputHandler(w, req) case "/stats": s.statsHandler(w, req) case "/ping": s.pingHandler(w, req) case "/info": s.infoHandler(w, req) case "/empty_topic": s.emptyTopicHandler(w, req) case "/delete_topic": s.deleteTopicHandler(w, req) case "/pause_topic": s.pauseTopicHandler(w, req) case "/unpause_topic": s.pauseTopicHandler(w, req) case "/empty_channel": s.emptyChannelHandler(w, req) case "/delete_channel": s.deleteChannelHandler(w, req) case "/pause_channel": s.pauseChannelHandler(w, req) case "/unpause_channel": s.pauseChannelHandler(w, req) case "/create_topic": s.createTopicHandler(w, req) case "/create_channel": s.createChannelHandler(w, req) case "/debug/pprof": httpprof.Index(w, req) case "/debug/pprof/cmdline": httpprof.Cmdline(w, req) case "/debug/pprof/symbol": httpprof.Symbol(w, req) case "/debug/pprof/heap": httpprof.Handler("heap").ServeHTTP(w, req) case "/debug/pprof/goroutine": httpprof.Handler("goroutine").ServeHTTP(w, req) case "/debug/pprof/profile": httpprof.Profile(w, req) case "/debug/pprof/block": httpprof.Handler("block").ServeHTTP(w, req) case "/debug/pprof/threadcreate": httpprof.Handler("threadcreate").ServeHTTP(w, req) default: log.Printf("ERROR: 404 %s", req.URL.Path) util.ApiResponse(w, 404, "NOT_FOUND", nil) } }
func emptyChannelHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, channelName, err := util.GetTopicChannelArgs(reqParams) if err != nil { util.ApiResponse(w, 500, err.Error(), nil) return } topic, err := nsqd.GetExistingTopic(topicName) if err != nil { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } channel, err := topic.GetExistingChannel(channelName) if err != nil { util.ApiResponse(w, 500, "INVALID_CHANNEL", nil) return } err = channel.Empty() if err != nil { util.ApiResponse(w, 500, "INTERNAL_ERROR", nil) return } util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) emptyTopicHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } if !nsq.IsValidTopicName(topicName) { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } topic, err := s.context.nsqd.GetExistingTopic(topicName) if err != nil { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } err = topic.Empty() if err != nil { util.ApiResponse(w, 500, "INTERNAL_ERROR", nil) return } util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { if !s.tlsEnabled && s.tlsRequired { util.ApiResponse(w, 403, "TLS_REQUIRED", nil) return } err := s.v1Router(w, req) if err == nil { return } err = s.deprecatedRouter(w, req) if err == nil { return } err = s.debugRouter(w, req) if err != nil { log.Printf("ERROR: %s", err) util.ApiResponse(w, 404, "NOT_FOUND", nil) } }
func deleteTopicHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } err = nsqd.DeleteExistingTopic(topicName) if err != nil { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) mpubHandler(w http.ResponseWriter, req *http.Request) { err := s.doMPUB(req) if err != nil { if acceptVersion(req) == 1 { util.V1ApiResponse(w, err.(httpError).Code(), err) } else { // this handler always returns 500 for backwards compatibility util.ApiResponse(w, 500, err.Error(), nil) } return } w.Header().Set("Content-Length", "2") io.WriteString(w, "OK") }
func createChannelHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, channelName, err := util.GetTopicChannelArgs(reqParams) if err != nil { util.ApiResponse(w, 500, err.Error(), nil) return } log.Printf("DB: adding channel(%s) in topic(%s)", channelName, topicName) key := Registration{"channel", topicName, channelName} lookupd.DB.AddRegistration(key) log.Printf("DB: adding topic(%s)", topicName) key = Registration{"topic", topicName, ""} lookupd.DB.AddRegistration(key) util.ApiResponse(w, 200, "OK", nil) }
func createTopicHandler(w http.ResponseWriter, req *http.Request) { reqParams, err := util.NewReqParams(req) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } if !nsq.IsValidTopicName(topicName) { util.ApiResponse(w, 500, "INVALID_TOPIC", nil) return } log.Printf("DB: adding topic(%s)", topicName) key := Registration{"topic", topicName, ""} lookupd.DB.AddRegistration(key) util.ApiResponse(w, 200, "OK", nil) }
func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { err := s.v1Router(w, req) if err == nil { return } err = s.deprecatedRouter(w, req) if err == nil { return } err = s.debugRouter(w, req) if err != nil { s.ctx.nsqlookupd.logf("ERROR: %s", err) util.ApiResponse(w, 404, "NOT_FOUND", nil) } }
func (s *httpServer) putHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } // TODO: one day I'd really like to just error on chunked requests // to be able to fail "too big" requests before we even read if req.ContentLength > s.context.nsqd.options.maxMessageSize { util.ApiResponse(w, 500, "MSG_TOO_BIG", nil) return } // add 1 so that it's greater than our max when we test for it // (LimitReader returns a "fake" EOF) readMax := s.context.nsqd.options.maxMessageSize + 1 body, err := ioutil.ReadAll(io.LimitReader(req.Body, readMax)) if err != nil { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } if int64(len(body)) == readMax { log.Printf("ERROR: /put hit max message size") util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } if len(body) == 0 { util.ApiResponse(w, 500, "MSG_EMPTY", nil) return } topic, err := s.getTopicFromQuery(req) if err != nil { util.ApiResponse(w, 500, err.Error(), nil) return } msg := nsq.NewMessage(<-s.context.nsqd.idChan, body) err = topic.PutMessage(msg) if err != nil { util.ApiResponse(w, 500, "NOK", nil) return } w.Header().Set("Content-Length", "2") io.WriteString(w, "OK") }
func nodesHandler(w http.ResponseWriter, req *http.Request) { producers := lookupd.DB.FindProducers("client", "", "") producerTopics := make([]*producerTopic, len(producers)) for i, p := range producers { producerTopics[i] = &producerTopic{ Address: p.peerInfo.Address, //TODO: drop for 1.0 Hostname: p.peerInfo.Hostname, BroadcastAddress: p.peerInfo.BroadcastAddress, TcpPort: p.peerInfo.TcpPort, HttpPort: p.peerInfo.HttpPort, Version: p.peerInfo.Version, Topics: lookupd.DB.LookupRegistrations(p.peerInfo.id).Filter("topic", "*", "").Keys(), } } data := make(map[string]interface{}) data["producers"] = producerTopics util.ApiResponse(w, 200, "OK", data) }
func (s *httpServer) mputHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) util.ApiResponse(w, 500, "INVALID_REQUEST", nil) return } topicName, err := reqParams.Get("topic") if err != nil { util.ApiResponse(w, 500, "MISSING_ARG_TOPIC", nil) return } if !nsq.IsValidTopicName(topicName) { util.ApiResponse(w, 500, "INVALID_ARG_TOPIC", nil) return } topic := s.context.nsqd.GetTopic(topicName) for _, block := range bytes.Split(reqParams.Body, []byte("\n")) { if len(block) != 0 { if int64(len(reqParams.Body)) > s.context.nsqd.options.maxMessageSize { util.ApiResponse(w, 500, "MSG_TOO_BIG", nil) return } msg := nsq.NewMessage(<-s.context.nsqd.idChan, block) err := topic.PutMessage(msg) if err != nil { util.ApiResponse(w, 500, "NOK", nil) return } } } w.Header().Set("Content-Length", "2") io.WriteString(w, "OK") }
func (s *httpServer) nodesHandler(w http.ResponseWriter, req *http.Request) { // dont filter out tombstoned nodes producers := s.context.nsqlookupd.DB.FindProducers("client", "", "").FilterByActive(s.context.nsqlookupd.inactiveProducerTimeout, 0) nodes := make([]*node, len(producers)) for i, p := range producers { topics := s.context.nsqlookupd.DB.LookupRegistrations(p.peerInfo.id).Filter("topic", "*", "").Keys() // for each topic find the producer that matches this peer // to add tombstone information tombstones := make([]bool, len(topics)) for j, t := range topics { topicProducers := s.context.nsqlookupd.DB.FindProducers("topic", t, "") for _, tp := range topicProducers { if tp.peerInfo == p.peerInfo { tombstones[j] = tp.IsTombstoned(s.context.nsqlookupd.tombstoneLifetime) } } } nodes[i] = &node{ RemoteAddress: p.peerInfo.RemoteAddress, Address: p.peerInfo.Address, //TODO: drop for 1.0 Hostname: p.peerInfo.Hostname, BroadcastAddress: p.peerInfo.BroadcastAddress, TcpPort: p.peerInfo.TcpPort, HttpPort: p.peerInfo.HttpPort, Version: p.peerInfo.Version, Tombstones: tombstones, Topics: topics, } } data := make(map[string]interface{}) data["producers"] = nodes util.ApiResponse(w, 200, "OK", data) }