github.com/nsqio/nsq@v1.3.0/nsqlookupd/http.go (about) 1 package nsqlookupd 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/pprof" 7 "sync/atomic" 8 9 "github.com/julienschmidt/httprouter" 10 "github.com/nsqio/nsq/internal/http_api" 11 "github.com/nsqio/nsq/internal/protocol" 12 "github.com/nsqio/nsq/internal/version" 13 ) 14 15 type httpServer struct { 16 nsqlookupd *NSQLookupd 17 router http.Handler 18 } 19 20 func newHTTPServer(l *NSQLookupd) *httpServer { 21 log := http_api.Log(l.logf) 22 23 router := httprouter.New() 24 router.HandleMethodNotAllowed = true 25 router.PanicHandler = http_api.LogPanicHandler(l.logf) 26 router.NotFound = http_api.LogNotFoundHandler(l.logf) 27 router.MethodNotAllowed = http_api.LogMethodNotAllowedHandler(l.logf) 28 s := &httpServer{ 29 nsqlookupd: l, 30 router: router, 31 } 32 33 router.Handle("GET", "/ping", http_api.Decorate(s.pingHandler, log, http_api.PlainText)) 34 router.Handle("GET", "/info", http_api.Decorate(s.doInfo, log, http_api.V1)) 35 36 // v1 negotiate 37 router.Handle("GET", "/debug", http_api.Decorate(s.doDebug, log, http_api.V1)) 38 router.Handle("GET", "/lookup", http_api.Decorate(s.doLookup, log, http_api.V1)) 39 router.Handle("GET", "/topics", http_api.Decorate(s.doTopics, log, http_api.V1)) 40 router.Handle("GET", "/channels", http_api.Decorate(s.doChannels, log, http_api.V1)) 41 router.Handle("GET", "/nodes", http_api.Decorate(s.doNodes, log, http_api.V1)) 42 43 // only v1 44 router.Handle("POST", "/topic/create", http_api.Decorate(s.doCreateTopic, log, http_api.V1)) 45 router.Handle("POST", "/topic/delete", http_api.Decorate(s.doDeleteTopic, log, http_api.V1)) 46 router.Handle("POST", "/channel/create", http_api.Decorate(s.doCreateChannel, log, http_api.V1)) 47 router.Handle("POST", "/channel/delete", http_api.Decorate(s.doDeleteChannel, log, http_api.V1)) 48 router.Handle("POST", "/topic/tombstone", http_api.Decorate(s.doTombstoneTopicProducer, log, http_api.V1)) 49 50 // debug 51 router.HandlerFunc("GET", "/debug/pprof", pprof.Index) 52 router.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) 53 router.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol) 54 router.HandlerFunc("POST", "/debug/pprof/symbol", pprof.Symbol) 55 router.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile) 56 router.Handler("GET", "/debug/pprof/heap", pprof.Handler("heap")) 57 router.Handler("GET", "/debug/pprof/goroutine", pprof.Handler("goroutine")) 58 router.Handler("GET", "/debug/pprof/block", pprof.Handler("block")) 59 router.Handler("GET", "/debug/pprof/threadcreate", pprof.Handler("threadcreate")) 60 61 return s 62 } 63 64 func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 65 s.router.ServeHTTP(w, req) 66 } 67 68 func (s *httpServer) pingHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 69 return "OK", nil 70 } 71 72 func (s *httpServer) doInfo(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 73 return struct { 74 Version string `json:"version"` 75 }{ 76 Version: version.Binary, 77 }, nil 78 } 79 80 func (s *httpServer) doTopics(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 81 topics := s.nsqlookupd.DB.FindRegistrations("topic", "*", "").Keys() 82 return map[string]interface{}{ 83 "topics": topics, 84 }, nil 85 } 86 87 func (s *httpServer) doChannels(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 88 reqParams, err := http_api.NewReqParams(req) 89 if err != nil { 90 return nil, http_api.Err{400, "INVALID_REQUEST"} 91 } 92 93 topicName, err := reqParams.Get("topic") 94 if err != nil { 95 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 96 } 97 98 channels := s.nsqlookupd.DB.FindRegistrations("channel", topicName, "*").SubKeys() 99 return map[string]interface{}{ 100 "channels": channels, 101 }, nil 102 } 103 104 func (s *httpServer) doLookup(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 105 reqParams, err := http_api.NewReqParams(req) 106 if err != nil { 107 return nil, http_api.Err{400, "INVALID_REQUEST"} 108 } 109 110 topicName, err := reqParams.Get("topic") 111 if err != nil { 112 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 113 } 114 115 registration := s.nsqlookupd.DB.FindRegistrations("topic", topicName, "") 116 if len(registration) == 0 { 117 return nil, http_api.Err{404, "TOPIC_NOT_FOUND"} 118 } 119 120 channels := s.nsqlookupd.DB.FindRegistrations("channel", topicName, "*").SubKeys() 121 producers := s.nsqlookupd.DB.FindProducers("topic", topicName, "") 122 producers = producers.FilterByActive(s.nsqlookupd.opts.InactiveProducerTimeout, 123 s.nsqlookupd.opts.TombstoneLifetime) 124 return map[string]interface{}{ 125 "channels": channels, 126 "producers": producers.PeerInfo(), 127 }, nil 128 } 129 130 func (s *httpServer) doCreateTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 131 reqParams, err := http_api.NewReqParams(req) 132 if err != nil { 133 return nil, http_api.Err{400, "INVALID_REQUEST"} 134 } 135 136 topicName, err := reqParams.Get("topic") 137 if err != nil { 138 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 139 } 140 141 if !protocol.IsValidTopicName(topicName) { 142 return nil, http_api.Err{400, "INVALID_ARG_TOPIC"} 143 } 144 145 s.nsqlookupd.logf(LOG_INFO, "DB: adding topic(%s)", topicName) 146 key := Registration{"topic", topicName, ""} 147 s.nsqlookupd.DB.AddRegistration(key) 148 149 return nil, nil 150 } 151 152 func (s *httpServer) doDeleteTopic(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 153 reqParams, err := http_api.NewReqParams(req) 154 if err != nil { 155 return nil, http_api.Err{400, "INVALID_REQUEST"} 156 } 157 158 topicName, err := reqParams.Get("topic") 159 if err != nil { 160 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 161 } 162 163 registrations := s.nsqlookupd.DB.FindRegistrations("channel", topicName, "*") 164 for _, registration := range registrations { 165 s.nsqlookupd.logf(LOG_INFO, "DB: removing channel(%s) from topic(%s)", registration.SubKey, topicName) 166 s.nsqlookupd.DB.RemoveRegistration(registration) 167 } 168 169 registrations = s.nsqlookupd.DB.FindRegistrations("topic", topicName, "") 170 for _, registration := range registrations { 171 s.nsqlookupd.logf(LOG_INFO, "DB: removing topic(%s)", topicName) 172 s.nsqlookupd.DB.RemoveRegistration(registration) 173 } 174 175 return nil, nil 176 } 177 178 func (s *httpServer) doTombstoneTopicProducer(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 179 reqParams, err := http_api.NewReqParams(req) 180 if err != nil { 181 return nil, http_api.Err{400, "INVALID_REQUEST"} 182 } 183 184 topicName, err := reqParams.Get("topic") 185 if err != nil { 186 return nil, http_api.Err{400, "MISSING_ARG_TOPIC"} 187 } 188 189 node, err := reqParams.Get("node") 190 if err != nil { 191 return nil, http_api.Err{400, "MISSING_ARG_NODE"} 192 } 193 194 s.nsqlookupd.logf(LOG_INFO, "DB: setting tombstone for producer@%s of topic(%s)", node, topicName) 195 producers := s.nsqlookupd.DB.FindProducers("topic", topicName, "") 196 for _, p := range producers { 197 thisNode := fmt.Sprintf("%s:%d", p.peerInfo.BroadcastAddress, p.peerInfo.HTTPPort) 198 if thisNode == node { 199 p.Tombstone() 200 } 201 } 202 203 return nil, nil 204 } 205 206 func (s *httpServer) doCreateChannel(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 207 reqParams, err := http_api.NewReqParams(req) 208 if err != nil { 209 return nil, http_api.Err{400, "INVALID_REQUEST"} 210 } 211 212 topicName, channelName, err := http_api.GetTopicChannelArgs(reqParams) 213 if err != nil { 214 return nil, http_api.Err{400, err.Error()} 215 } 216 217 s.nsqlookupd.logf(LOG_INFO, "DB: adding channel(%s) in topic(%s)", channelName, topicName) 218 key := Registration{"channel", topicName, channelName} 219 s.nsqlookupd.DB.AddRegistration(key) 220 221 s.nsqlookupd.logf(LOG_INFO, "DB: adding topic(%s)", topicName) 222 key = Registration{"topic", topicName, ""} 223 s.nsqlookupd.DB.AddRegistration(key) 224 225 return nil, nil 226 } 227 228 func (s *httpServer) doDeleteChannel(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 229 reqParams, err := http_api.NewReqParams(req) 230 if err != nil { 231 return nil, http_api.Err{400, "INVALID_REQUEST"} 232 } 233 234 topicName, channelName, err := http_api.GetTopicChannelArgs(reqParams) 235 if err != nil { 236 return nil, http_api.Err{400, err.Error()} 237 } 238 239 registrations := s.nsqlookupd.DB.FindRegistrations("channel", topicName, channelName) 240 if len(registrations) == 0 { 241 return nil, http_api.Err{404, "CHANNEL_NOT_FOUND"} 242 } 243 244 s.nsqlookupd.logf(LOG_INFO, "DB: removing channel(%s) from topic(%s)", channelName, topicName) 245 for _, registration := range registrations { 246 s.nsqlookupd.DB.RemoveRegistration(registration) 247 } 248 249 return nil, nil 250 } 251 252 type node struct { 253 RemoteAddress string `json:"remote_address"` 254 Hostname string `json:"hostname"` 255 BroadcastAddress string `json:"broadcast_address"` 256 TCPPort int `json:"tcp_port"` 257 HTTPPort int `json:"http_port"` 258 Version string `json:"version"` 259 Tombstones []bool `json:"tombstones"` 260 Topics []string `json:"topics"` 261 } 262 263 func (s *httpServer) doNodes(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 264 // dont filter out tombstoned nodes 265 producers := s.nsqlookupd.DB.FindProducers("client", "", "").FilterByActive( 266 s.nsqlookupd.opts.InactiveProducerTimeout, 0) 267 nodes := make([]*node, len(producers)) 268 topicProducersMap := make(map[string]Producers) 269 for i, p := range producers { 270 topics := s.nsqlookupd.DB.LookupRegistrations(p.peerInfo.id).Filter("topic", "*", "").Keys() 271 272 // for each topic find the producer that matches this peer 273 // to add tombstone information 274 tombstones := make([]bool, len(topics)) 275 for j, t := range topics { 276 if _, exists := topicProducersMap[t]; !exists { 277 topicProducersMap[t] = s.nsqlookupd.DB.FindProducers("topic", t, "") 278 } 279 280 topicProducers := topicProducersMap[t] 281 for _, tp := range topicProducers { 282 if tp.peerInfo == p.peerInfo { 283 tombstones[j] = tp.IsTombstoned(s.nsqlookupd.opts.TombstoneLifetime) 284 break 285 } 286 } 287 } 288 289 nodes[i] = &node{ 290 RemoteAddress: p.peerInfo.RemoteAddress, 291 Hostname: p.peerInfo.Hostname, 292 BroadcastAddress: p.peerInfo.BroadcastAddress, 293 TCPPort: p.peerInfo.TCPPort, 294 HTTPPort: p.peerInfo.HTTPPort, 295 Version: p.peerInfo.Version, 296 Tombstones: tombstones, 297 Topics: topics, 298 } 299 } 300 301 return map[string]interface{}{ 302 "producers": nodes, 303 }, nil 304 } 305 306 func (s *httpServer) doDebug(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 307 s.nsqlookupd.DB.RLock() 308 defer s.nsqlookupd.DB.RUnlock() 309 310 data := make(map[string][]map[string]interface{}) 311 for r, producers := range s.nsqlookupd.DB.registrationMap { 312 key := r.Category + ":" + r.Key + ":" + r.SubKey 313 for _, p := range producers { 314 m := map[string]interface{}{ 315 "id": p.peerInfo.id, 316 "hostname": p.peerInfo.Hostname, 317 "broadcast_address": p.peerInfo.BroadcastAddress, 318 "tcp_port": p.peerInfo.TCPPort, 319 "http_port": p.peerInfo.HTTPPort, 320 "version": p.peerInfo.Version, 321 "last_update": atomic.LoadInt64(&p.peerInfo.lastUpdate), 322 "tombstoned": p.tombstoned, 323 "tombstoned_at": p.tombstonedAt.UnixNano(), 324 } 325 data[key] = append(data[key], m) 326 } 327 } 328 329 return data, nil 330 }