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  }