コード例 #1
0
ファイル: engine_test.go プロジェクト: aonx/momonga
func (s *EngineSuite) BenchmarkSimple(c *C) {
	log.SetupLogging("error", "stdout")

	engine := CreateEngine()
	go engine.Run()

	mock := &MockConnection{}
	conn := NewMyConnection(nil)
	conn.SetMyConnection(mock)
	conn.SetId("debug")

	hndr := NewHandler(conn, engine)
	conn.SetOpaque(hndr)

	msg := codec.NewConnectMessage()
	msg.Magic = []byte("MQTT")
	msg.Version = uint8(4)
	msg.Identifier = "debug"
	msg.CleanSession = true
	msg.KeepAlive = uint16(0) // Ping is annoyed at this time
	codec.WriteMessageTo(msg, mock)

	// 6) just call conn.ParseMessage(). then handler will work.
	r, err := conn.ParseMessage()

	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNECT)

	// NOTE: Client turn. don't care this.
	time.Sleep(time.Millisecond)
	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNACK)

	for i := 0; i < c.N; i++ {
		// (Client) Publish
		pub := codec.NewPublishMessage()
		pub.TopicName = "/debug"
		pub.Payload = []byte("hello")
		codec.WriteMessageTo(pub, mock)

		// (Server)
		conn.ParseMessage()
	}

	// That's it.
	engine.Terminate()
}
コード例 #2
0
ファイル: myconnection.go プロジェクト: aonx/momonga
func (self *MyConnection) writeMessage(msg codec.Message) error {
	self.logger.Debug("Write Message [%s]: %+v", msg.GetTypeAsString(), msg)
	var e error
	self.Mutex.Lock()

	self.Last = time.Now()
	if self.balancer != nil && self.balancer.PerSec > 0 {
		self.balancer.Execute(func() {
			_, e = codec.WriteMessageTo(msg, self.Writer)
		})
	} else {
		_, e = codec.WriteMessageTo(msg, self.Writer)
	}

	if e != nil {
		self.Mutex.Unlock()
		return e
	}

	self.Last = time.Now()
	self.kickFlusher()
	self.Mutex.Unlock()
	return nil
}
コード例 #3
0
ファイル: engine.go プロジェクト: aonx/momonga
func (self *Momonga) SendPublishMessage(msg *codec.PublishMessage, client_id string, is_bridged bool) {
	// Don't pass wrong message here. user should validate the message be ore using this API.
	if len(msg.TopicName) < 1 {
		return
	}
	rpc, jsonObj := stringutils.IsJsonRpc(string(msg.Payload))
	if rpc {
		fmt.Println("******************** message *****************", jsonObj)
		obj := jsonObj.(map[string]interface{})
		method := obj["method"]
		paramsObj := obj["params"].(map[string]interface{})
		// lets pick params and create a query string
		params := url.Values{}
		for k, v := range paramsObj {
			if str, ok := v.(string); ok {
				params.Add(k, str)
			}
		}
		api := gopencils.Api("http://localhost:5080/api")
		resp := new(interface{})

		res := api.Res(strings.Replace(msg.TopicName, "/data/", "", 1), resp)
		res.SetHeader("Authorization", "Bearer 2eEkMA6x7NmNQwzpE3qLvgvQgdILER")

		switch method {
		case "get":
			res.Get()
			fmt.Println(client_id, "GET ", msg.TopicName, params.Encode())
		case "create":
			res.Post(paramsObj)
			fmt.Println(client_id, "POST ", msg.TopicName, params.Encode())
		case "update":
			fmt.Println(client_id, "PUT ", msg.TopicName, params.Encode())
		case "delete":
			fmt.Println(client_id, "DELETE ", msg.TopicName, params.Encode())
		}
		//fmt.Println(*resp)
		result, _ := json.Marshal(*resp)
		fmt.Println(string(result))
		fmt.Println(msg)
		msg.TopicName = "/client/" + client_id
		msg.Payload = result
		fmt.Println(msg)
	}

	// TODO: Have to persist retain message.
	if msg.Retain > 0 {
		if len(msg.Payload) == 0 {
			log.Debug("[DELETE RETAIN: %s]\n%s", msg.TopicName, hex.Dump([]byte(msg.TopicName)))

			err := self.DataStore.Del([]byte(msg.TopicName), []byte(msg.TopicName))
			if err != nil {
				log.Error("Error: %s\n", err)
			}
			// これ配送したらおかしいべ
			log.Debug("Deleted retain: %s", msg.TopicName)
			// あれ、ackとかかえすんだっけ?
			Metrics.System.Broker.Messages.RetainedCount.Add(-1)
			return
		} else {
			buffer := bytes.NewBuffer(nil)
			codec.WriteMessageTo(msg, buffer)
			self.DataStore.Put([]byte(msg.TopicName), buffer.Bytes())
			Metrics.System.Broker.Messages.RetainedCount.Add(1)
		}
	}

	if Mflags["experimental.qos1"] {
		if msg.QosLevel == 1 {
			targets := self.TopicMatcher.Match(msg.TopicName)

			go func(msg *codec.PublishMessage, set []interface{}) {
				p := make(chan string, 1000)
				wg := sync.WaitGroup{}
				wg.Add(3) // bulk sernder, retry kun, receive kun

				mchan := make(chan string, 256)
				term := make(chan bool, 1)
				cnt := len(set)
				mng := make(map[string]*codec.PublishMessage)

				// retry kun。こういう実装だととても楽
				go func(term chan bool, mchan chan string, mng map[string]*codec.PublishMessage) {
					for {
						select {
						case m := <-mchan:
							if msg, ok := mng[m]; ok {
								conn, err := self.GetConnectionByClientId(m)
								if err != nil {
									fmt.Printf("something wrong: %s %s", m, err)
									continue
								}

								if err == nil {
									log.Debug("sending a retry msg: %s", msg)
									conn.WriteMessageQueue(msg)
								} else {
									log.Debug("connection not exist. next retry")
								}
							}
						case <-term:
							log.Debug("  retry finished.")

							wg.Done()
							return
						}
					}
				}(term, mchan, mng)

				// reader
				go func(p chan string, term chan bool, cnt int, mng map[string]*codec.PublishMessage, mchan chan string) {
					limit := time.After(time.Second * 60)
					for {
						select {
						case id := <-p:
							cnt--
							delete(mng, id)
							// これはcallbackでやってもいいようなきもするけど、wait groupとかもろもろ渡すの面倒くさい

							if cnt < 1 {
								log.Debug("  all delivery finished.")
								term <- true

								wg.Done()
								return
							}
						case <-time.After(time.Second * 20):
							// 終わってないやつをなめてリトライさせる
							for cid, m := range mng {
								m.Dupe = true
								mchan <- cid
							}
						case <-limit:
							log.Debug("  gave up retry.")
							term <- true
							wg.Done()
							return
						}
					}
				}(p, term, cnt, mng, mchan)

				// sender. これは勝手に終わる
				go func(msg *codec.PublishMessage, set []interface{}, p chan string, mng map[string]*codec.PublishMessage) {
					dp := make(map[string]bool)
					for i := range targets {
						var tbl *util.MessageTable
						var ok bool

						myset := targets[i].(*SubscribeSet)
						fmt.Printf("myset: %s", myset)

						// NOTE (from interoperability/client_test.py):
						//
						//   overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
						//   the server may send back one message with the highest QoS of any matching subscription, or one message for
						//   each subscription with a matching QoS.
						//
						// Currently, We choose one message for each subscription with a matching QoS.
						//
						if _, ok := dp[myset.ClientId]; ok {
							continue
						}
						dp[myset.ClientId] = true

						x, _ := codec.CopyPublishMessage(msg)
						x.QosLevel = myset.QoS
						conn, err := self.GetConnectionByClientId(myset.ClientId)
						// これは面倒臭い。clean sessionがtrueで再接続した時はもはや別人として扱わなければならない

						if x.QosLevel == 0 {
							// QoS 0にダウングレードしたらそのまま終わらせる
							conn.WriteMessageQueue(x)
							p <- myset.ClientId
							continue
						}

						if tbl, ok = self.InflightTable[myset.ClientId]; !ok {
							self.InflightTable[myset.ClientId] = util.NewMessageTable()
							// callback仕込めるんだよなー。QoS1なら使わなくてもいいかなー。とかおもったり

							tbl = self.InflightTable[myset.ClientId]
						}

						id := tbl.NewId()
						x.PacketIdentifier = id
						x.Opaque = p
						tbl.Register2(id, x, 1, x)

						if err != nil {
							continue
						}
						mng[myset.ClientId] = x
						conn.WriteMessageQueue(x)
					}
					log.Debug("  all fisrt delivery finished.")

					wg.Done()
				}(msg, targets, p, mng)

				wg.Wait()
				close(p)
				close(mchan)
				close(term)
				mng = nil
				log.Debug("  okay, cleanup qos1 sending thread.")
			}(msg, targets)
			return
		}
	}

	// Publishで受け取ったMessageIdのやつのCountをとっておく
	// で、Pubackが帰ってきたらrefcountを下げて0になったらMessageを消す
	//log.Debug("TopicName: %s %s", m.TopicName, m.Payload)
	targets := self.TopicMatcher.Match(msg.TopicName)
	if msg.TopicName[0:1] == "#" {
		// TODO:  [MQTT-4.7.2-1] The Server MUST NOT match Topic Filters starting with a wildcard character
		// (# or +) with Topic Names beginning with a $ character
	}

	// list つくってからとって、だとタイミング的に居ない奴も出てくるんだよな。マジカオス
	// ここで必要なのは, connection(clientId), subscribed qosがわかればあとは投げるだけ
	// なんで、Qlobberがかえすのはであるべきなんだけど。これすっげー消しづらいのよね・・・
	// {
	//    Connection: Connection or client id
	//    QoS:
	// }
	// いやまぁエラーハンドリングちゃんとやってれば問題ない。
	// client idのほうがベターだな。Connectionを無駄に参照つけると後が辛い
	dp := make(map[string]bool)
	count := 0

	for i := range targets {
		var cn Connection
		var ok error

		myset := targets[i].(*SubscribeSet)
		clientId := myset.ClientId
		//clientId := targets[i].(string)

		// NOTE (from interoperability/client_test.py):
		//
		//   overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
		//   the server may send back one message with the highest QoS of any matching subscription, or one message for
		//   each subscription with a matching QoS.
		//
		// Currently, We choose one message for each subscription with a matching QoS.
		//
		if _, ok := dp[clientId]; ok {
			continue
		}
		dp[clientId] = true

		cn, ok = self.GetConnectionByClientId(clientId)
		if ok != nil {
			// どちらかというとClientが悪いと思うよ!
			// リスト拾った瞬間にはいたけど、その後いなくなったんだから配送リストからこれらは無視すべき
			log.Info("(can't fetch %s. already disconnected, or unsubscribed?)", clientId)
			continue
		}

		if cn.IsBridge() && clientId == client_id {
			// Don't send message to same bridge
			continue
		}

		var x *codec.PublishMessage

		if msg.QosLevel == 0 {
			// we don't need to copy message on QoS 0
			x = msg
		} else {
			x = codec.MustCopyPublishMessage(msg)
		}

		subscriberQos := myset.QoS
		// Downgrade QoS
		if subscriberQos < x.QosLevel {
			x.QosLevel = subscriberQos
		}

		if x.QosLevel > 0 {
			// TODO: ClientごとにInflightTableを持つ
			// engineのOutGoingTableなのはとりあえず、ということ
			id := self.OutGoingTable.NewId()
			x.PacketIdentifier = id
			if sender, ok := x.Opaque.(Connection); ok {
				// TODO: ここ(でなにをするつもりだったのか・・w)
				self.OutGoingTable.Register2(x.PacketIdentifier, x, len(targets), sender)
			}
		}

		cn.WriteMessageQueue(x)
		count++
	}

	Metrics.System.Broker.Messages.Sent.Add(int64(count))
}
コード例 #4
0
ファイル: engine_test.go プロジェクト: aonx/momonga
func (s *EngineSuite) TestBasic(c *C) {
	log.SetupLogging("error", "stdout")

	// This test introduce how to setup custom MQTT server

	// 1) You need to setup Momonga engine like this.
	engine := CreateEngine()
	// 2) Running Engine
	go engine.Run()

	// 3) engine expects net.Conn (see MockConnection)
	mock := &MockConnection{}
	conn := NewMyConnection(nil)
	conn.SetMyConnection(mock)
	conn.SetId("debug")

	// 4) setup handler. This handler implementation is example.
	// you can customize behaviour with On method.
	hndr := NewHandler(conn, engine)
	conn.SetOpaque(hndr)

	// 5) Now,
	msg := codec.NewConnectMessage()
	msg.Magic = []byte("MQTT")
	msg.Version = uint8(4)
	msg.Identifier = "debug"
	msg.CleanSession = true
	msg.KeepAlive = uint16(0) // Ping is annoyed at this time

	codec.WriteMessageTo(msg, mock)

	// 6) just call conn.ParseMessage(). then handler will work.
	r, err := conn.ParseMessage()

	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNECT)

	// NOTE: Client turn. don't care this.
	time.Sleep(time.Millisecond)
	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNACK)

	// Subscribe
	sub := codec.NewSubscribeMessage()
	sub.Payload = append(sub.Payload, codec.SubscribePayload{TopicPath: "/debug"})
	sub.PacketIdentifier = 1

	codec.WriteMessageTo(sub, mock)

	// (Server)
	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_SUBSCRIBE)
	rsub := r.(*codec.SubscribeMessage)
	c.Assert(rsub.PacketIdentifier, Equals, uint16(1))
	c.Assert(rsub.Payload[0].TopicPath, Equals, "/debug")

	// (Client) suback
	time.Sleep(time.Millisecond)
	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_SUBACK)

	// (Client) Publish
	pub := codec.NewPublishMessage()
	pub.TopicName = "/debug"
	pub.Payload = []byte("hello")
	codec.WriteMessageTo(pub, mock)

	// (Server)
	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_PUBLISH)
	rpub := r.(*codec.PublishMessage)
	c.Assert(rpub.TopicName, Equals, "/debug")

	// (Client) received publish message
	time.Sleep(time.Millisecond)
	r, err = conn.ParseMessage()
	rp := r.(*codec.PublishMessage)
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_PUBLISH)
	c.Assert(rp.TopicName, Equals, "/debug")
	c.Assert(string(rp.Payload), Equals, "hello")

	// okay, now disconnect from server
	dis := codec.NewDisconnectMessage()
	codec.WriteMessageTo(dis, mock)

	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)
	c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_DISCONNECT)

	// then, receive EOF from server.
	time.Sleep(time.Millisecond)
	r, err = conn.ParseMessage()
	c.Assert(err, Equals, nil)

	// That's it.
	engine.Terminate()
}