github.com/nsqio/nsq@v1.3.0/nsqlookupd/lookup_protocol_v1.go (about) 1 package nsqlookupd 2 3 import ( 4 "bufio" 5 "encoding/binary" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log" 10 "net" 11 "os" 12 "strings" 13 "sync/atomic" 14 "time" 15 16 "github.com/nsqio/nsq/internal/protocol" 17 "github.com/nsqio/nsq/internal/version" 18 ) 19 20 type LookupProtocolV1 struct { 21 nsqlookupd *NSQLookupd 22 } 23 24 func (p *LookupProtocolV1) NewClient(conn net.Conn) protocol.Client { 25 return NewClientV1(conn) 26 } 27 28 func (p *LookupProtocolV1) IOLoop(c protocol.Client) error { 29 var err error 30 var line string 31 32 client := c.(*ClientV1) 33 34 reader := bufio.NewReader(client) 35 for { 36 line, err = reader.ReadString('\n') 37 if err != nil { 38 break 39 } 40 41 line = strings.TrimSpace(line) 42 params := strings.Split(line, " ") 43 44 var response []byte 45 response, err = p.Exec(client, reader, params) 46 if err != nil { 47 ctx := "" 48 if parentErr := err.(protocol.ChildErr).Parent(); parentErr != nil { 49 ctx = " - " + parentErr.Error() 50 } 51 p.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, err, ctx) 52 53 _, sendErr := protocol.SendResponse(client, []byte(err.Error())) 54 if sendErr != nil { 55 p.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, sendErr, ctx) 56 break 57 } 58 59 // errors of type FatalClientErr should forceably close the connection 60 if _, ok := err.(*protocol.FatalClientErr); ok { 61 break 62 } 63 continue 64 } 65 66 if response != nil { 67 _, err = protocol.SendResponse(client, response) 68 if err != nil { 69 break 70 } 71 } 72 } 73 74 p.nsqlookupd.logf(LOG_INFO, "PROTOCOL(V1): [%s] exiting ioloop", client) 75 76 if client.peerInfo != nil { 77 registrations := p.nsqlookupd.DB.LookupRegistrations(client.peerInfo.id) 78 for _, r := range registrations { 79 if removed, _ := p.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id); removed { 80 p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s", 81 client, r.Category, r.Key, r.SubKey) 82 } 83 } 84 } 85 86 return err 87 } 88 89 func (p *LookupProtocolV1) Exec(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) { 90 switch params[0] { 91 case "PING": 92 return p.PING(client, params) 93 case "IDENTIFY": 94 return p.IDENTIFY(client, reader, params[1:]) 95 case "REGISTER": 96 return p.REGISTER(client, reader, params[1:]) 97 case "UNREGISTER": 98 return p.UNREGISTER(client, reader, params[1:]) 99 } 100 return nil, protocol.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("invalid command %s", params[0])) 101 } 102 103 func getTopicChan(command string, params []string) (string, string, error) { 104 if len(params) == 0 { 105 return "", "", protocol.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("%s insufficient number of params", command)) 106 } 107 108 topicName := params[0] 109 var channelName string 110 if len(params) >= 2 { 111 channelName = params[1] 112 } 113 114 if !protocol.IsValidTopicName(topicName) { 115 return "", "", protocol.NewFatalClientErr(nil, "E_BAD_TOPIC", fmt.Sprintf("%s topic name '%s' is not valid", command, topicName)) 116 } 117 118 if channelName != "" && !protocol.IsValidChannelName(channelName) { 119 return "", "", protocol.NewFatalClientErr(nil, "E_BAD_CHANNEL", fmt.Sprintf("%s channel name '%s' is not valid", command, channelName)) 120 } 121 122 return topicName, channelName, nil 123 } 124 125 func (p *LookupProtocolV1) REGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) { 126 if client.peerInfo == nil { 127 return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "client must IDENTIFY") 128 } 129 130 topic, channel, err := getTopicChan("REGISTER", params) 131 if err != nil { 132 return nil, err 133 } 134 135 if channel != "" { 136 key := Registration{"channel", topic, channel} 137 if p.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) { 138 p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s", 139 client, "channel", topic, channel) 140 } 141 } 142 key := Registration{"topic", topic, ""} 143 if p.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) { 144 p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s", 145 client, "topic", topic, "") 146 } 147 148 return []byte("OK"), nil 149 } 150 151 func (p *LookupProtocolV1) UNREGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) { 152 if client.peerInfo == nil { 153 return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "client must IDENTIFY") 154 } 155 156 topic, channel, err := getTopicChan("UNREGISTER", params) 157 if err != nil { 158 return nil, err 159 } 160 161 if channel != "" { 162 key := Registration{"channel", topic, channel} 163 removed, left := p.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id) 164 if removed { 165 p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s", 166 client, "channel", topic, channel) 167 } 168 // for ephemeral channels, remove the channel as well if it has no producers 169 if left == 0 && strings.HasSuffix(channel, "#ephemeral") { 170 p.nsqlookupd.DB.RemoveRegistration(key) 171 } 172 } else { 173 // no channel was specified so this is a topic unregistration 174 // remove all of the channel registrations... 175 // normally this shouldn't happen which is why we print a warning message 176 // if anything is actually removed 177 registrations := p.nsqlookupd.DB.FindRegistrations("channel", topic, "*") 178 for _, r := range registrations { 179 removed, _ := p.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id) 180 if removed { 181 p.nsqlookupd.logf(LOG_WARN, "client(%s) unexpected UNREGISTER category:%s key:%s subkey:%s", 182 client, "channel", topic, r.SubKey) 183 } 184 } 185 186 key := Registration{"topic", topic, ""} 187 removed, left := p.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id) 188 if removed { 189 p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s", 190 client, "topic", topic, "") 191 } 192 if left == 0 && strings.HasSuffix(topic, "#ephemeral") { 193 p.nsqlookupd.DB.RemoveRegistration(key) 194 } 195 } 196 197 return []byte("OK"), nil 198 } 199 200 func (p *LookupProtocolV1) IDENTIFY(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) { 201 var err error 202 203 if client.peerInfo != nil { 204 return nil, protocol.NewFatalClientErr(err, "E_INVALID", "cannot IDENTIFY again") 205 } 206 207 var bodyLen int32 208 err = binary.Read(reader, binary.BigEndian, &bodyLen) 209 if err != nil { 210 return nil, protocol.NewFatalClientErr(err, "E_BAD_BODY", "IDENTIFY failed to read body size") 211 } 212 213 body := make([]byte, bodyLen) 214 _, err = io.ReadFull(reader, body) 215 if err != nil { 216 return nil, protocol.NewFatalClientErr(err, "E_BAD_BODY", "IDENTIFY failed to read body") 217 } 218 219 // body is a json structure with producer information 220 peerInfo := PeerInfo{id: client.RemoteAddr().String()} 221 err = json.Unmarshal(body, &peerInfo) 222 if err != nil { 223 return nil, protocol.NewFatalClientErr(err, "E_BAD_BODY", "IDENTIFY failed to decode JSON body") 224 } 225 226 peerInfo.RemoteAddress = client.RemoteAddr().String() 227 228 // require all fields 229 if peerInfo.BroadcastAddress == "" || peerInfo.TCPPort == 0 || peerInfo.HTTPPort == 0 || peerInfo.Version == "" { 230 return nil, protocol.NewFatalClientErr(nil, "E_BAD_BODY", "IDENTIFY missing fields") 231 } 232 233 atomic.StoreInt64(&peerInfo.lastUpdate, time.Now().UnixNano()) 234 235 p.nsqlookupd.logf(LOG_INFO, "CLIENT(%s): IDENTIFY Address:%s TCP:%d HTTP:%d Version:%s", 236 client, peerInfo.BroadcastAddress, peerInfo.TCPPort, peerInfo.HTTPPort, peerInfo.Version) 237 238 client.peerInfo = &peerInfo 239 if p.nsqlookupd.DB.AddProducer(Registration{"client", "", ""}, &Producer{peerInfo: client.peerInfo}) { 240 p.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s", client, "client", "", "") 241 } 242 243 // build a response 244 data := make(map[string]interface{}) 245 data["tcp_port"] = p.nsqlookupd.RealTCPAddr().Port 246 data["http_port"] = p.nsqlookupd.RealHTTPAddr().Port 247 data["version"] = version.Binary 248 hostname, err := os.Hostname() 249 if err != nil { 250 log.Fatalf("ERROR: unable to get hostname %s", err) 251 } 252 data["broadcast_address"] = p.nsqlookupd.opts.BroadcastAddress 253 data["hostname"] = hostname 254 255 response, err := json.Marshal(data) 256 if err != nil { 257 p.nsqlookupd.logf(LOG_ERROR, "marshaling %v", data) 258 return []byte("OK"), nil 259 } 260 return response, nil 261 } 262 263 func (p *LookupProtocolV1) PING(client *ClientV1, params []string) ([]byte, error) { 264 if client.peerInfo != nil { 265 // we could get a PING before other commands on the same client connection 266 cur := time.Unix(0, atomic.LoadInt64(&client.peerInfo.lastUpdate)) 267 now := time.Now() 268 p.nsqlookupd.logf(LOG_INFO, "CLIENT(%s): pinged (last ping %s)", client.peerInfo.id, 269 now.Sub(cur)) 270 atomic.StoreInt64(&client.peerInfo.lastUpdate, now.UnixNano()) 271 } 272 return []byte("OK"), nil 273 }