// QoS 1, 2 are available. but really suck implementation. // reconsider qos design later. func NewMomonga(config *configuration.Config) *Momonga { engine := &Momonga{ OutGoingTable: util.NewMessageTable(), TopicMatcher: util.NewQlobber(), Connections: map[uint32]map[string]*MmuxConnection{}, RetryMap: map[string][]*Retryable{}, Started: time.Now(), EnableSys: false, DataStore: datastore.NewMemstore(), LockPool: map[uint32]*sync.RWMutex{}, config: config, InflightTable: map[string]*util.MessageTable{}, } // initialize lock pool for i := 0; i < config.GetLockPoolSize(); i++ { engine.LockPool[uint32(i)] = &sync.RWMutex{} engine.Connections[uint32(i)] = make(map[string]*MmuxConnection) } auth := config.GetAuthenticators() if len(auth) > 0 { for i := 0; i < len(auth); i++ { var authenticator Authenticator switch auth[i].Type { case "empty": authenticator = &EmptyAuthenticator{} authenticator.Init(config) default: panic(fmt.Sprintf("Unsupported type specified: [%s]", auth[i].Type)) } engine.registerAuthenticator(authenticator) } } else { authenticator := &EmptyAuthenticator{} authenticator.Init(config) engine.registerAuthenticator(authenticator) } engine.setupCallback() return engine }
func (self *MyHttpServer) debugRouter(w http.ResponseWriter, req *http.Request) error { switch req.URL.Path { case "/debug/vars": w.Header().Set("Content-Type", "application/json; charset=utf-8") fmt.Fprintf(w, "{\n") first := true expvar.Do(func(kv expvar.KeyValue) { if kv.Key == "cmdline" || kv.Key == "memstats" { return } if !first { fmt.Fprintf(w, ",\n") } first = false fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) }) fmt.Fprintf(w, "\n}\n") 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) case "/debug/retain": itr := self.Engine.DataStore.Iterator() for ; itr.Valid(); itr.Next() { k := string(itr.Key()) fmt.Fprintf(w, "<div>key: %s</div>", k) } case "/debug/retain/clear": itr := self.Engine.DataStore.Iterator() var targets []string for ; itr.Valid(); itr.Next() { x := itr.Key() targets = append(targets, string(x)) } for _, s := range targets { self.Engine.DataStore.Del([]byte(s), []byte(s)) } fmt.Fprintf(w, "<textarea>%#v</textarea>", self.Engine.DataStore) case "/debug/connections": for _, v := range self.Engine.Connections { fmt.Fprintf(w, "<div>%#v</div>", v) } case "/debug/connections/clear": self.Engine.Connections = make(map[uint32]map[string]*MmuxConnection) fmt.Fprintf(w, "cleared") case "/debug/qlobber/clear": self.Engine.TopicMatcher = util.NewQlobber() fmt.Fprintf(w, "cleared") case "/debug/qlobber/dump": fmt.Fprintf(w, "qlobber:\n") self.Engine.TopicMatcher.Dump(w) case "/debug/config/dump": e := toml.NewEncoder(w) e.Encode(self.Engine.Config()) default: return fmt.Errorf("404 %s", req.URL.Path) } return nil }
// TODO: どっかで綺麗にしたい func NewMyConnection(conf *MyConfig) *MyConnection { if conf == nil { conf = &defaultConfig } c := &MyConnection{ Events: make(map[string]interface{}), Queue: make(chan codec.Message, conf.QueueSize), OfflineQueue: make([]codec.Message, 0), MaxOfflineQueue: conf.OfflineQueueSize, InflightTable: util.NewMessageTable(), SubscribeHistory: make(map[string]int), Mutex: sync.RWMutex{}, Qlobber: util.NewQlobber(), SubscribedTopics: make(map[string]int), Last: time.Now(), CleanSession: true, Keepalive: conf.Keepalive, State: STATE_INIT, Closed: make(chan bool), MaxMessageSize: conf.MaxMessageSize, fch: make(chan bool, 1), } c.t = time.AfterFunc(time.Second*300, func() { // NOTE: assume flush if c.State == STATE_CONNECTED { c.kickFlusher() } }) c.logger = log.Global if conf.Logger != nil { c.logger = conf.Logger } if conf.WritePerSec > 0 { c.balancer = &util.Balancer{ PerSec: conf.WritePerSec, } } if conf.MaxMessageSize > 0 { c.MaxMessageSize = conf.MaxMessageSize } c.Events["connected"] = func() { c.State = STATE_CONNECTED } c.Events["connack"] = func(result uint8) { if result == 0 { c.SetState(STATE_CONNECTED) if c.Reconnect { for key, qos := range c.SubscribeHistory { c.Subscribe(key, qos) } } //TODO: このアホっぽい実装はあとでちゃんとなおす。なおしたい if len(c.OfflineQueue) > 0 { c.Mutex.Lock() var targets []codec.Message for len(c.OfflineQueue) > 0 { targets = append(targets, c.OfflineQueue[0]) c.OfflineQueue = c.OfflineQueue[1:] } c.Mutex.Unlock() for i := 0; i < len(targets); i++ { c.Queue <- targets[i] } } c.setupKicker() } else { c.State = STATE_CLOSED } } // for Wait API c.InflightTable.SetOnFinish(func(id uint16, message codec.Message, opaque interface{}) { if m, ok := message.(*codec.PublishMessage); ok { if m.QosLevel == 1 { if b, ok := opaque.(chan bool); ok { close(b) } } else if m.QosLevel == 2 { if b, ok := opaque.(chan bool); ok { close(b) } } } }) // こっちに集約できるとClientが薄くなれる c.Events["publish"] = func(msg *codec.PublishMessage) { if msg.QosLevel == 1 { ack := codec.NewPubackMessage() ack.PacketIdentifier = msg.PacketIdentifier c.WriteMessageQueue(ack) c.logger.Debug("Send puback message to sender. [%s: %d]", c.GetId(), ack.PacketIdentifier) } else if msg.QosLevel == 2 { ack := codec.NewPubrecMessage() ack.PacketIdentifier = msg.PacketIdentifier c.WriteMessageQueue(ack) c.logger.Debug("Send pubrec message to sender. [%s: %d]", c.GetId(), ack.PacketIdentifier) } } c.Events["puback"] = func(messageId uint16) { c.InflightTable.Unref(messageId) } c.Events["pubrec"] = func(messageId uint16) { ack := codec.NewPubrelMessage() ack.PacketIdentifier = messageId c.Queue <- ack } c.Events["pubrel"] = func(messageId uint16) { ack := codec.NewPubcompMessage() ack.PacketIdentifier = messageId c.Queue <- ack c.InflightTable.Unref(ack.PacketIdentifier) // Unackknowleged } c.Events["pubcomp"] = func(messageId uint16) { c.InflightTable.Unref(messageId) } c.Events["unsuback"] = func(messageId uint16) { mm, err := c.InflightTable.Get(messageId) if err == nil { if v, ok := mm.(*codec.UnsubscribeMessage); ok { delete(c.SubscribeHistory, v.TopicName) } } c.InflightTable.Remove(messageId) } c.Events["subscribe"] = func(p *codec.SubscribeMessage) { } c.Events["suback"] = func(messageId uint16, grunted int) { c.InflightTable.Remove(messageId) } c.Events["unsubscribe"] = func(messageId uint16, granted int, payload []codec.SubscribePayload) { for i := 0; i < len(payload); i++ { delete(c.SubscribeHistory, payload[i].TopicPath) } } // これはコネクション渡したほうがいいんではないだろうか。 c.Events["pingreq"] = func() { // TODO: check Ping count periodically, abort MyConnection when the counter exceeded. c.PingCounter++ } c.Events["pingresp"] = func() { // nothing to do. c.PingCounter-- } c.Events["disconnect"] = func() { // nothing to do ? c.State = STATE_CLOSED } c.Events["error"] = func(err error) { //fmt.Printf("Error: %s\n", err) } c.Events["connect"] = func(msg *codec.ConnectMessage) { } c.Events["parsed"] = func() { } // Write Queue go func() { for { select { case msg := <-c.Queue: state := c.GetState() if state == STATE_CONNECTED || state == STATE_CONNECTING { if msg.GetType() == codec.PACKET_TYPE_PUBLISH { sb := msg.(*codec.PublishMessage) if sb.QosLevel < 0 { c.logger.Error("QoS under zero. %s: %#v", c.Id, sb) break } if sb.QosLevel > 0 { id := c.InflightTable.NewId() sb.PacketIdentifier = id c.InflightTable.Register(id, sb, nil) } } e := c.writeMessage(msg) if e != nil { if v, ok := c.Events["error"].(func(error)); ok { v(e) } } c.invalidateTimer() } else { c.OfflineQueue = append(c.OfflineQueue, msg) } case <-c.Closed: if c.KeepLoop { time.Sleep(time.Second) } else { return } } } }() go c.flusher() return c }