├── LICENSE ├── README.md ├── TODO ├── client └── client.go ├── common ├── connection.go ├── myconnection.go └── subscribeset.go ├── config.toml ├── configuration └── configuration.go ├── datastore ├── datastore.go ├── memstore.go └── memstore_test.go ├── docs ├── Makefile ├── conf.py ├── configuration.rst ├── developing.rst ├── index.rst ├── intro.rst ├── make.bat └── quickstart.rst ├── encoding └── mqtt │ ├── connack.go │ ├── connect.go │ ├── constants.go │ ├── disconnect.go │ ├── fixedheader.go │ ├── helper.go │ ├── message.go │ ├── mqtt.go │ ├── parse_test.go │ ├── pingreq.go │ ├── pingresp.go │ ├── puback.go │ ├── pubcomp.go │ ├── publish.go │ ├── pubrec.go │ ├── pubrel.go │ ├── suback.go │ ├── subscribe.go │ ├── unsuback.go │ ├── unsubscribe.go │ └── will.go ├── expvar └── diff.go ├── flags └── flags.go ├── logger └── logger.go ├── momonga └── momonga.go ├── momonga_cli └── momonga_cli.go ├── server ├── application.go ├── authenticator.go ├── authenticator_empty.go ├── constants.go ├── dummyplug.go ├── dummyplug_test.go ├── engine.go ├── engine_test.go ├── handler.go ├── http.go ├── http_listener.go ├── http_server.go ├── listener.go ├── mmux_connection.go ├── mon.go ├── mylistener.go ├── server.go ├── stats.go ├── tcp_server.go ├── tls_server.go ├── topic_matcher.go └── unix_server.go ├── skiplist ├── comparator.go ├── iterator.go └── skiplist.go └── util ├── balancer.go ├── message_table.go ├── murmurhash.go ├── qlobber.go ├── qlobber_test.go └── util.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Shuhei Tanuma 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Momonga MQTT 2 | ============ 3 | 4 | ![momonga gopher](http://i.imgur.com/Jbo9Gl8.png) 5 | 6 | # About 7 | 8 | Momonga is a Golang MQTT library to make MQTT client and Server easily. 9 | 10 | This project has been started as my hobby project, to learn MQTT3.1.1 behaviour. 11 | This might contains some unreliable codes. Yes, contributions are very welcome. 12 | 13 | # Features 14 | 15 | * MQTT 3.1.1 compliant 16 | * QoS 0 supported. 17 | * also QoS 1, 2 are available, although really suck implementation. don't rely it. 18 | 19 | Misc 20 | 21 | * WebSocket Support (might contain bugs. requires go.net/websocket) 22 | * SSL Support 23 | * UnixSocket Support 24 | 25 | # Requirements 26 | 27 | Go 1.3 higher 28 | 29 | # Version 30 | 31 | dev 32 | 33 | # Quick Start 34 | 35 | ``` 36 | #momonga (MQTT Server) 37 | go get -u github.com/chobie/momonga/momonga 38 | momonga -config=config.toml 39 | 40 | #momonga_cli 41 | go get -u github.com/chobie/momonga/momonga_cli 42 | momonga_cli 43 | ``` 44 | 45 | # Development 46 | 47 | NOTE: momonga is still under development. API may change. 48 | 49 | ``` 50 | mkdir momonga && cd momonga 51 | export GOPATH=`pwd` 52 | go get -u github.com/chobie/momonga/momonga 53 | 54 | # server 55 | go run src/github.com/chobie/momonga/momonga/momonga.go -config=src/github.com/chobie/momonga/config.toml 56 | 57 | # cli 58 | go build -o momonga_cli src/github.com/chobie/momonga/momonga_cli/momonga_cli.go 59 | ``` 60 | 61 | # Author 62 | 63 | Shuhei Tanuma 64 | 65 | # Links 66 | 67 | * MQTT 3.1.1 Specification 68 | http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd01/mqtt-v3.1.1-csprd01.html 69 | 70 | * Conformance Test 71 | http://www.eclipse.org/paho/clients/testing/ 72 | 73 | # Conformance Test 74 | 75 | ``` 76 | momonga% python3 interoperability/client_test.py 77 | hostname localhost port 1883 78 | clean up starting 79 | clean up finished 80 | Basic test starting 81 | Basic test succeeded 82 | Retained message test starting 83 | Retained message test succeeded 84 | This server is queueing QoS 0 messages for offline clients 85 | Offline message queueing test succeeded 86 | Will message test succeeded 87 | Overlapping subscriptions test starting 88 | This server is publishing one message for all matching overlapping subscriptions, not one for each. 89 | Overlapping subscriptions test succeeded 90 | Keepalive test starting 91 | Keepalive test succeeded 92 | Redelivery on reconnect test starting 93 | Redelivery on reconnect test succeeded 94 | test suite succeeded 95 | ``` 96 | 97 | # Prebuilt Binaries 98 | 99 | for linux, osx 100 | 101 | https://drone.io/github.com/chobie/momonga/files 102 | 103 | ``` 104 | wget https://drone.io/github.com/chobie/momonga/files/artifacts/bin/linux_arm/momonga 105 | chmod +x momonga 106 | ./momonga 107 | ``` 108 | 109 | # License - MIT License - 110 | 111 | Copyright (c) 2014 Shuhei Tanuma, 112 | 113 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 114 | 115 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 116 | 117 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package client 5 | 6 | import ( 7 | "fmt" 8 | . "github.com/chobie/momonga/common" 9 | codec "github.com/chobie/momonga/encoding/mqtt" 10 | log "github.com/chobie/momonga/logger" 11 | "github.com/chobie/momonga/util" 12 | // "io" 13 | "net" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type Option struct { 19 | TransporterCallback func() (net.Conn, error) 20 | Magic []byte 21 | Version int 22 | Identifier string 23 | Ticker *time.Ticker 24 | TickerCallback func(time.Time, *Client) error 25 | WillTopic string 26 | WillMessage []byte 27 | WillRetain bool 28 | WillQos int 29 | UserName string 30 | Password string 31 | Keepalive int // THIS IS REALLY TROBULESOME 32 | Logger log.Logger 33 | } 34 | 35 | type Client struct { 36 | Connection *MyConnection 37 | PublishCallback func(string, []byte) 38 | Option Option 39 | CleanSession bool 40 | Subscribed map[string]int 41 | Mutex sync.RWMutex 42 | Errors chan error 43 | Kicker *time.Timer 44 | wg sync.WaitGroup 45 | offline []codec.Message 46 | mu sync.RWMutex 47 | once sync.Once 48 | term chan bool 49 | logger log.Logger 50 | reconnect bool 51 | count int 52 | } 53 | 54 | func NewClient(opt Option) *Client { 55 | client := &Client{ 56 | Option: Option{ 57 | Magic: []byte("MQTT"), 58 | Version: 4, 59 | // Memo: User have to set PacketIdentifier themselves 60 | Identifier: "momongacli", 61 | Keepalive: 10, 62 | }, 63 | CleanSession: true, 64 | Subscribed: make(map[string]int), 65 | Mutex: sync.RWMutex{}, 66 | Errors: make(chan error, 128), 67 | offline: make([]codec.Message, 8192), 68 | term: make(chan bool, 256), 69 | } 70 | 71 | if opt.Logger != nil { 72 | client.logger = opt.Logger 73 | } else { 74 | client.logger = log.Global 75 | } 76 | 77 | client.Connection = NewMyConnection(&MyConfig{ 78 | QueueSize: 8192, 79 | OfflineQueueSize: 1024, 80 | Keepalive: 60, 81 | WritePerSec: 10, 82 | Logger: client.logger, 83 | }) 84 | 85 | if len(opt.Magic) < 1 { 86 | opt.Magic = client.Option.Magic 87 | } 88 | if opt.Version == 0 { 89 | opt.Version = client.Option.Version 90 | } 91 | if len(opt.Identifier) < 1 { 92 | // generate random string 93 | suffix := util.GenerateId(23 - (len(client.Option.Identifier) + 1)) 94 | opt.Identifier = fmt.Sprintf("%s-%s", client.Option.Identifier, suffix) 95 | } 96 | 97 | // TODO: should provide defaultOption function. 98 | client.Option = opt 99 | client.Connection.Keepalive = opt.Keepalive 100 | 101 | // required for client 102 | client.Connection.KeepLoop = true 103 | 104 | client.On("connack", func(result uint8) { 105 | if result == 0 { 106 | client.wg.Done() 107 | 108 | // reconnect 109 | if client.reconnect { 110 | for topic, qos := range client.Subscribed { 111 | client.Subscribe(topic, qos) 112 | } 113 | } 114 | } 115 | }) 116 | 117 | client.On("error", func(err error) { 118 | if client.Connection.State == STATE_CONNECTED { 119 | client.Connection.Close() 120 | time.Sleep(time.Second) 121 | client.Connect() 122 | } 123 | }, true) 124 | 125 | go client.Loop() 126 | return client 127 | } 128 | 129 | func (self *Client) Connect() error { 130 | self.mu.Lock() 131 | defer self.mu.Unlock() 132 | 133 | if self.Connection.State == STATE_CONNECTING || self.Connection.State == STATE_CONNECTED { 134 | // 接続中, 試行中なのでなにもしない 135 | return nil 136 | } 137 | 138 | connection, err := self.Option.TransporterCallback() 139 | if err != nil { 140 | return err 141 | } 142 | 143 | if v, ok := connection.(*net.TCPConn); ok { 144 | // should enable keepalive. 145 | v.SetKeepAlive(true) 146 | v.SetNoDelay(true) 147 | } 148 | 149 | self.once = sync.Once{} 150 | self.Connection.SetMyConnection(connection) 151 | self.wg.Add(1) 152 | 153 | // send a connect message to MQTT Server 154 | msg := codec.NewConnectMessage() 155 | msg.Magic = self.Option.Magic 156 | msg.Version = uint8(self.Option.Version) 157 | msg.Identifier = self.Option.Identifier 158 | msg.CleanSession = self.CleanSession 159 | msg.KeepAlive = uint16(self.Option.Keepalive) 160 | 161 | if len(self.Option.WillTopic) > 0 { 162 | msg.Will = &codec.WillMessage{ 163 | Topic: self.Option.WillTopic, 164 | Message: string(self.Option.WillMessage), 165 | Retain: self.Option.WillRetain, 166 | Qos: uint8(self.Option.WillQos), 167 | } 168 | } 169 | 170 | if len(self.Option.UserName) > 0 { 171 | msg.UserName = []byte(self.Option.UserName) 172 | } 173 | 174 | if len(self.Option.Password) > 0 { 175 | msg.Password = []byte(self.Option.Password) 176 | } 177 | 178 | self.Connection.SetState(STATE_CONNECTING) 179 | self.Connection.WriteMessageQueue(msg) 180 | self.count += 1 181 | if self.count > 1 { 182 | self.reconnect = true 183 | } 184 | return nil 185 | } 186 | 187 | func (self *Client) WaitConnection() { 188 | if self.Connection.GetState() == STATE_CONNECTING { 189 | self.wg.Wait() 190 | } 191 | } 192 | 193 | // terminate means loop terminate 194 | func (self *Client) Terminate() { 195 | self.mu.Lock() 196 | defer self.mu.Unlock() 197 | 198 | self.term <- true 199 | } 200 | 201 | func (self *Client) Loop() { 202 | // read loop 203 | // TODO: consider interface. for now, just print it. 204 | for { 205 | select { 206 | case <-self.term: 207 | return 208 | default: 209 | // TODO: move this function to connect (実際にReadするやつ) 210 | switch self.Connection.GetState() { 211 | case STATE_CONNECTED, STATE_CONNECTING: 212 | // ここでのエラーハンドリングは重複してしまうのでやらない。On["error"]に任せる 213 | _, e := self.Connection.ParseMessage() 214 | if e != nil { 215 | time.Sleep(time.Second) 216 | } 217 | case STATE_CLOSED: 218 | // TODO: implement exponential backoff 219 | time.Sleep(time.Second) 220 | self.Connect() 221 | default: 222 | time.Sleep(time.Second) 223 | self.Connect() 224 | } 225 | } 226 | } 227 | } 228 | 229 | func (self *Client) On(event string, callback interface{}, args ...bool) error { 230 | return self.Connection.On(event, callback, args...) 231 | } 232 | 233 | func (self *Client) Publish(TopicName string, Payload []byte, QoSLevel int) { 234 | self.publishCommon(TopicName, Payload, QoSLevel, false, nil) 235 | } 236 | 237 | func (self *Client) publishCommon(TopicName string, Payload []byte, QosLevel int, retain bool, opaque interface{}) { 238 | self.Connection.Publish(TopicName, Payload, QosLevel, retain, opaque) 239 | } 240 | 241 | func (self *Client) PublishWait(TopicName string, Payload []byte, QoSLevel int) error { 242 | if QoSLevel == 0 { 243 | return fmt.Errorf("QoS should be greater than 0.") 244 | } 245 | 246 | b := make(chan bool, 1) 247 | self.publishCommon(TopicName, Payload, QoSLevel, false, b) 248 | <-b 249 | close(b) 250 | 251 | return nil 252 | } 253 | 254 | func (self *Client) PublishWithRetain(TopicName string, Payload []byte, QoSLevel int) { 255 | self.publishCommon(TopicName, Payload, QoSLevel, true, nil) 256 | } 257 | 258 | func (self *Client) Subscribe(topic string, QoS int) error { 259 | self.Subscribed[topic] = QoS 260 | 261 | return self.Connection.Subscribe(topic, QoS) 262 | } 263 | 264 | func (self *Client) Unsubscribe(topic string) { 265 | self.Connection.Unsubscribe(topic) 266 | } 267 | 268 | func (self *Client) Disconnect() { 269 | self.Connection.Disconnect() 270 | } 271 | 272 | func (self *Client) SetRequestPerSecondLimit(limit int) { 273 | self.Connection.SetRequestPerSecondLimit(limit) 274 | } 275 | -------------------------------------------------------------------------------- /common/connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package common 6 | 7 | import ( 8 | "github.com/chobie/momonga/encoding/mqtt" 9 | "github.com/chobie/momonga/util" 10 | ) 11 | 12 | type State int32 13 | 14 | // ConnectionState: Idle, Send, Receive and Handshake? 15 | const ( 16 | STATE_INIT State = iota 17 | STATE_CONNECTING 18 | STATE_CONNECTED 19 | STATE_ACCEPTED 20 | STATE_IDLE 21 | STATE_DETACHED 22 | STATE_SEND 23 | STATE_RECEIVE 24 | STATE_SHUTDOWN 25 | STATE_CLOSED 26 | ) 27 | 28 | type ConnectionError struct { 29 | s string 30 | } 31 | 32 | func (e *ConnectionError) Error() string { 33 | return e.s 34 | } 35 | 36 | type ConnectionResetError struct { 37 | s string 38 | } 39 | 40 | func (e *ConnectionResetError) Error() string { 41 | return e.s 42 | } 43 | 44 | // TODO: あんまり実情にあってないのでみなおそう 45 | type Connection interface { 46 | //WriteMessage(request mqtt.Message) error 47 | WriteMessageQueue(request mqtt.Message) 48 | 49 | Close() error 50 | 51 | SetState(State) 52 | 53 | GetState() State 54 | 55 | ResetState() 56 | 57 | ReadMessage() (mqtt.Message, error) 58 | 59 | IsAlived() bool 60 | 61 | SetWillMessage(mqtt.WillMessage) 62 | 63 | GetWillMessage() *mqtt.WillMessage 64 | 65 | HasWillMessage() bool 66 | 67 | GetOutGoingTable() *util.MessageTable 68 | 69 | GetSubscribedTopics() map[string]*SubscribeSet 70 | 71 | AppendSubscribedTopic(string, *SubscribeSet) 72 | 73 | RemoveSubscribedTopic(string) 74 | 75 | SetKeepaliveInterval(int) 76 | 77 | GetId() string 78 | 79 | GetRealId() string 80 | 81 | SetId(string) 82 | 83 | DisableCleanSession() 84 | 85 | ShouldCleanSession() bool 86 | 87 | IsBridge() bool 88 | } 89 | -------------------------------------------------------------------------------- /common/subscribeset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package common 5 | 6 | import ( 7 | "encoding/json" 8 | ) 9 | 10 | type SubscribeSet struct { 11 | ClientId string `json:"client_id"` 12 | TopicFilter string `json:"topic_filter"` 13 | QoS int `json:"qos"` 14 | } 15 | 16 | func (self *SubscribeSet) String() string { 17 | b, _ := json.Marshal(self) 18 | return string(b) 19 | } 20 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # momonga-mqtt configuration file (draft) 2 | # NOTE: Currently, almost configurations aren't implemented yet. Don't rely these! 3 | 4 | [server] 5 | log_file = "stdout" 6 | log_level = "debug" 7 | pid_file = "" 8 | 9 | user = "momonga" 10 | 11 | bind_address = "localhost" 12 | 13 | port = 1883 14 | 15 | max_connections = 1000 16 | 17 | # unix socket path (default disabled) 18 | socket = "" 19 | connection_timeout = 10 20 | 21 | enable_tls = false 22 | tls_port = 8883 23 | cafile = "" 24 | certfile = "" 25 | keyfile = "" 26 | 27 | httpport = 9000 28 | websocket_mount = "/mqtt" 29 | 30 | # provide http debug features (see server/http.go) 31 | http_debug = true 32 | 33 | [engine] 34 | enable_permission = false 35 | queue_size = 8192 36 | lock_pool_size = 64 37 | enable_sys = true 38 | acceptor_count = "cpu" 39 | fanout_worker_count = "cpu" 40 | 41 | max_inflight_messages = 10000 42 | max_queued_messages = 10000 43 | retry_interval = 20 44 | 45 | # max is 268435455 bytes 46 | message_size_limit = 8192 47 | allow_anonymous = true 48 | 49 | [engine.authenticator] 50 | type = "empty" 51 | -------------------------------------------------------------------------------- /configuration/configuration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package configuration 6 | 7 | import ( 8 | "fmt" 9 | "github.com/BurntSushi/toml" 10 | "io/ioutil" 11 | "runtime" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | type Config struct { 17 | Server Server `toml:"server"` 18 | Engine Engine `toml:"engine"` 19 | Bridge Bridge `toml:"bridge"` 20 | } 21 | 22 | type Engine struct { 23 | QueueSize int `toml:"queue_size"` 24 | AcceptorCount string `toml:"acceptor_count"` 25 | LockPoolSize int `toml:"lock_pool_size"` 26 | EnableSys bool `toml:"enable_sys"` 27 | FanoutWorkerCount string `toml:"fanout_worker_count"` 28 | AllowAnonymous bool `toml:"allow_anonymous"` 29 | Authenticators []Authenticator `toml:"authenticator"` 30 | EnablePermission bool `toml:"enable_permission"` 31 | } 32 | 33 | type Authenticator struct { 34 | Type string `toml:"type"` 35 | } 36 | 37 | type Bridge struct { 38 | Address string `toml:"address"` 39 | Port int `toml:"port"` 40 | CleanSession bool `toml:"clean_session"` 41 | ClientId string `toml:"client_id"` 42 | KeepaliveInterval int `toml:"keepalive_interval"` 43 | Connection string `toml:"connection"` 44 | Type string `toml:"type"` 45 | } 46 | 47 | type Server struct { 48 | User string `toml:"user"` 49 | LogFile string `toml:"log_file"` 50 | LogLevel string `toml:"log_level"` 51 | PidFile string `toml:"pid_file"` 52 | BindAddress string `toml:"bind_address"` 53 | Port int `toml:"port"` 54 | EnableTls bool `toml:"enable_tls"` 55 | TlsPort int `toml:"tls_port"` 56 | Keyfile string `toml:"keyfile"` 57 | Cafile string `toml:"cafile"` 58 | Certfile string `toml:"certfile"` 59 | Socket string `toml:"socket"` 60 | HttpPort int `toml:"http_port"` 61 | WebSocketMount string `toml:"websocket_mount"` 62 | HttpDebug bool `toml:"http_debug"` 63 | MaxInflightMessages int `toml:"max_inflight_messages"` 64 | MaxQueuedMessages int `toml:"max_queued_messages"` 65 | RetryInterval int `toml:"retry_interval"` 66 | MessageSizeLimit int `toml:"message_size_limit"` 67 | } 68 | 69 | func (self *Config) GetQueueSize() int { 70 | return self.Engine.QueueSize 71 | } 72 | 73 | func (self *Config) GetFanoutWorkerCount() int { 74 | if strings.ToLower(self.Engine.FanoutWorkerCount) == "cpu" { 75 | return runtime.NumCPU() 76 | } else { 77 | v, e := strconv.ParseInt(self.Engine.FanoutWorkerCount, 10, 64) 78 | if e != nil { 79 | return 1 80 | } 81 | return int(v) 82 | } 83 | } 84 | 85 | func (self *Config) GetLockPoolSize() int { 86 | return self.Engine.LockPoolSize 87 | } 88 | 89 | func (self *Config) GetAcceptorCount() int { 90 | if strings.ToLower(self.Engine.AcceptorCount) == "cpu" { 91 | return runtime.NumCPU() 92 | } else { 93 | v, e := strconv.ParseInt(self.Engine.AcceptorCount, 10, 64) 94 | if e != nil { 95 | return 1 96 | } 97 | return int(v) 98 | } 99 | } 100 | 101 | func (self *Config) GetListenAddress() string { 102 | if self.Server.Port <= 0 { 103 | return "" 104 | } 105 | return fmt.Sprintf("%s:%d", self.Server.BindAddress, self.Server.Port) 106 | } 107 | 108 | func (self *Config) GetTlsListenAddress() string { 109 | if self.Server.TlsPort <= 0 { 110 | return "" 111 | } 112 | return fmt.Sprintf("%s:%d", self.Server.BindAddress, self.Server.TlsPort) 113 | } 114 | 115 | func (self *Config) GetSSLListenAddress() string { 116 | if self.Server.Port <= 0 { 117 | return "" 118 | } 119 | return fmt.Sprintf("%s:8883", self.Server.BindAddress) 120 | } 121 | 122 | func (self *Config) GetSocketAddress() string { 123 | return self.Server.Socket 124 | } 125 | 126 | func (self *Config) GetAuthenticators() []Authenticator { 127 | return self.Engine.Authenticators 128 | } 129 | 130 | func DefaultConfiguration() *Config { 131 | return &Config{ 132 | Engine: Engine{ 133 | QueueSize: 8192, 134 | AcceptorCount: "cpu", 135 | FanoutWorkerCount: "cpu", 136 | LockPoolSize: 64, 137 | EnableSys: true, 138 | AllowAnonymous: true, 139 | EnablePermission: false, 140 | }, 141 | Server: Server{ 142 | User: "momonga", 143 | LogFile: "stdout", 144 | LogLevel: "debug", 145 | PidFile: "", 146 | BindAddress: "localhost", 147 | Port: 1883, 148 | Socket: "", 149 | HttpPort: 0, 150 | WebSocketMount: "/mqtt", 151 | HttpDebug: true, 152 | MaxInflightMessages: 10000, 153 | MaxQueuedMessages: 10000, 154 | RetryInterval: 20, 155 | MessageSizeLimit: 8192, 156 | EnableTls: false, 157 | TlsPort: 8883, 158 | }, 159 | } 160 | } 161 | 162 | func LoadConfiguration(configFile string) (*Config, error) { 163 | config := DefaultConfiguration() 164 | 165 | data, err := ioutil.ReadFile(configFile) 166 | if err != nil { 167 | return config, err 168 | } 169 | 170 | if _, err2 := toml.Decode(string(data), config); err != nil { 171 | return config, err2 172 | } 173 | 174 | return config, nil 175 | } 176 | 177 | func LoadConfigurationTo(configFile string, to *Config) error { 178 | data, err := ioutil.ReadFile(configFile) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | if _, err2 := toml.Decode(string(data), to); err != nil { 184 | return err2 185 | } 186 | 187 | return nil 188 | } 189 | -------------------------------------------------------------------------------- /datastore/datastore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package datastore 6 | 7 | type Iterator interface { 8 | Seek(key []byte) 9 | Key() []byte 10 | Value() []byte 11 | Next() 12 | Prev() 13 | Valid() bool 14 | Error() error 15 | Close() error 16 | } 17 | 18 | type Datastore interface { 19 | Name() string 20 | Path() string 21 | Put(key, value []byte) error 22 | Get(key []byte) ([]byte, error) 23 | Del(first, last []byte) error 24 | Iterator() Iterator 25 | Close() error 26 | } 27 | -------------------------------------------------------------------------------- /datastore/memstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package datastore 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "github.com/chobie/momonga/skiplist" 11 | "sync" 12 | ) 13 | 14 | type MemstoreIterator struct { 15 | Iterator *skiplist.SkipListIterator 16 | } 17 | 18 | func (self *MemstoreIterator) Seek(key []byte) { 19 | self.Iterator.Seek(key) 20 | } 21 | 22 | func (self *MemstoreIterator) Key() []byte { 23 | r := self.Iterator.Key() 24 | if v, ok := r.([]byte); ok { 25 | return v 26 | } 27 | return nil 28 | } 29 | 30 | func (self *MemstoreIterator) Value() []byte { 31 | r := self.Iterator.Value() 32 | if v, ok := r.([]byte); ok { 33 | return v 34 | } 35 | return nil 36 | } 37 | 38 | func (self *MemstoreIterator) Next() { 39 | self.Iterator.Next() 40 | } 41 | 42 | func (self *MemstoreIterator) Prev() { 43 | panic("prev is not supported yet") 44 | } 45 | 46 | func (self *MemstoreIterator) Valid() bool { 47 | return self.Iterator.Valid() 48 | } 49 | 50 | func (self *MemstoreIterator) Error() error { 51 | return nil 52 | } 53 | 54 | func (self *MemstoreIterator) Close() error { 55 | self.Iterator = nil 56 | return nil 57 | } 58 | 59 | func NewMemstore() *Memstore { 60 | return &Memstore{ 61 | Storage: skiplist.NewSkipList(&skiplist.BytesComparator{}), 62 | Mutex: &sync.RWMutex{}, 63 | } 64 | } 65 | 66 | type Memstore struct { 67 | Storage *skiplist.SkipList 68 | Mutex *sync.RWMutex 69 | } 70 | 71 | func (self *Memstore) Name() string { 72 | return "memstore" 73 | } 74 | 75 | func (self *Memstore) Path() string { 76 | return "inmemory" 77 | } 78 | 79 | func (self *Memstore) Put(key, value []byte) error { 80 | self.Mutex.Lock() 81 | defer func() { 82 | self.Mutex.Unlock() 83 | }() 84 | 85 | self.Storage.Delete(key) 86 | self.Storage.Insert(key, value) 87 | return nil 88 | } 89 | 90 | func (self *Memstore) Get(key []byte) ([]byte, error) { 91 | self.Mutex.RLock() 92 | defer func() { 93 | self.Mutex.RUnlock() 94 | }() 95 | 96 | itr := self.Iterator() 97 | itr.Seek(key) 98 | if itr.Valid() { 99 | return itr.Value(), nil 100 | } 101 | 102 | return nil, errors.New("not found") 103 | } 104 | 105 | func (self *Memstore) Del(first, last []byte) error { 106 | self.Mutex.RLock() 107 | itr := self.Iterator() 108 | 109 | var targets [][]byte 110 | for itr.Seek(first); itr.Valid(); itr.Next() { 111 | key := itr.Key() 112 | // The result will be 0 if a==b, -1 if a < b, and +1 if a > b. 113 | if bytes.Compare(key, last) > 0 { 114 | break 115 | } 116 | targets = append(targets, key) 117 | } 118 | self.Mutex.RUnlock() 119 | 120 | // MEMO: this is more safely. 121 | self.Mutex.Lock() 122 | for i := range targets { 123 | self.Storage.Delete(targets[i]) 124 | } 125 | self.Mutex.Unlock() 126 | 127 | return nil 128 | } 129 | 130 | func (self *Memstore) Iterator() Iterator { 131 | return &MemstoreIterator{ 132 | Iterator: self.Storage.Iterator(), 133 | } 134 | } 135 | 136 | func (self *Memstore) Close() error { 137 | self.Storage = nil 138 | return nil 139 | } 140 | -------------------------------------------------------------------------------- /datastore/memstore_test.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | _ "fmt" 5 | . "gopkg.in/check.v1" 6 | "testing" 7 | ) 8 | 9 | func Test(t *testing.T) { TestingT(t) } 10 | 11 | type DatastoreSuite struct{} 12 | 13 | var _ = Suite(&DatastoreSuite{}) 14 | 15 | func (s *DatastoreSuite) TestMemstore(c *C) { 16 | memstore := NewMemstore() 17 | var err error 18 | 19 | err = memstore.Put([]byte("key1"), []byte("value1")) 20 | c.Assert(err, Equals, nil) 21 | err = memstore.Put([]byte("key2"), []byte("value2")) 22 | c.Assert(err, Equals, nil) 23 | err = memstore.Put([]byte("key3"), []byte("value3")) 24 | c.Assert(err, Equals, nil) 25 | 26 | value, err := memstore.Get([]byte("key1")) 27 | c.Assert(err, Equals, nil) 28 | c.Assert(value, DeepEquals, []byte("value1")) 29 | 30 | memstore.Del([]byte("key1"), []byte("key1")) 31 | //TopicA/C 32 | //TopicA/B 33 | 34 | memstore = NewMemstore() 35 | memstore.Put([]byte("$SYS/broker/broker/version"), []byte("a")) 36 | memstore.Put([]byte("Topic/C"), []byte("a")) 37 | memstore.Put([]byte("TopicA/C"), []byte("a")) 38 | memstore.Put([]byte("TopicA/B"), []byte("a")) 39 | 40 | itr := memstore.Iterator() 41 | var targets []string 42 | for ; itr.Valid(); itr.Next() { 43 | x := itr.Key() 44 | targets = append(targets, string(x)) 45 | } 46 | for _, s := range targets { 47 | memstore.Del([]byte(s), []byte(s)) 48 | } 49 | 50 | _, err = memstore.Get([]byte("Topic/C")) 51 | c.Assert(err.Error(), Equals, "not found") 52 | 53 | // fmt.Printf("\n") 54 | // for itr := memstore.Iterator(); itr.Valid(); itr.Next() { 55 | // fmt.Printf("key: %s\n", itr.Key()) 56 | // } 57 | 58 | // 59 | // value, err = memstore.Get([]byte("key_nothing")) 60 | // c.Assert(err.Error(), Equals, "not found") 61 | // c.Assert(value, DeepEquals, []byte(nil)) 62 | // 63 | } 64 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/momonga.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/momonga.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/momonga" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/momonga" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # momonga documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Sep 29 06:02:46 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | #import sphinx_rtd_theme 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = u'momonga' 48 | copyright = u'2014, Shuhei Tanuma' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = 'dev' 56 | # The full version, including alpha/beta/rc tags. 57 | release = 'dev' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all 74 | # documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | # If true, keep warnings as "system message" paragraphs in the built documents. 95 | #keep_warnings = False 96 | 97 | 98 | # -- Options for HTML output ---------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | #html_theme = "sphinx_rtd_theme" 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | #html_theme_path = [] 111 | #html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 112 | 113 | # The name for this set of Sphinx documents. If None, it defaults to 114 | # " v documentation". 115 | #html_title = None 116 | 117 | # A shorter title for the navigation bar. Default is the same as html_title. 118 | #html_short_title = None 119 | 120 | # The name of an image file (relative to this directory) to place at the top 121 | # of the sidebar. 122 | #html_logo = None 123 | 124 | # The name of an image file (within the static path) to use as favicon of the 125 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 126 | # pixels large. 127 | #html_favicon = None 128 | 129 | # Add any paths that contain custom static files (such as style sheets) here, 130 | # relative to this directory. They are copied after the builtin static files, 131 | # so a file named "default.css" will overwrite the builtin "default.css". 132 | html_static_path = ['_static'] 133 | 134 | # Add any extra paths that contain custom files (such as robots.txt or 135 | # .htaccess) here, relative to this directory. These files are copied 136 | # directly to the root of the documentation. 137 | #html_extra_path = [] 138 | 139 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 140 | # using the given strftime format. 141 | #html_last_updated_fmt = '%b %d, %Y' 142 | 143 | # If true, SmartyPants will be used to convert quotes and dashes to 144 | # typographically correct entities. 145 | #html_use_smartypants = True 146 | 147 | # Custom sidebar templates, maps document names to template names. 148 | #html_sidebars = {} 149 | 150 | # Additional templates that should be rendered to pages, maps page names to 151 | # template names. 152 | #html_additional_pages = {} 153 | 154 | # If false, no module index is generated. 155 | #html_domain_indices = True 156 | 157 | # If false, no index is generated. 158 | #html_use_index = True 159 | 160 | # If true, the index is split into individual pages for each letter. 161 | #html_split_index = False 162 | 163 | # If true, links to the reST sources are added to the pages. 164 | #html_show_sourcelink = True 165 | 166 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 167 | #html_show_sphinx = True 168 | 169 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 170 | #html_show_copyright = True 171 | 172 | # If true, an OpenSearch description file will be output, and all pages will 173 | # contain a tag referring to it. The value of this option must be the 174 | # base URL from which the finished HTML is served. 175 | #html_use_opensearch = '' 176 | 177 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 178 | #html_file_suffix = None 179 | 180 | # Output file base name for HTML help builder. 181 | htmlhelp_basename = 'momongadoc' 182 | 183 | 184 | # -- Options for LaTeX output --------------------------------------------- 185 | 186 | latex_elements = { 187 | # The paper size ('letterpaper' or 'a4paper'). 188 | #'papersize': 'letterpaper', 189 | 190 | # The font size ('10pt', '11pt' or '12pt'). 191 | #'pointsize': '10pt', 192 | 193 | # Additional stuff for the LaTeX preamble. 194 | #'preamble': '', 195 | } 196 | 197 | # Grouping the document tree into LaTeX files. List of tuples 198 | # (source start file, target name, title, 199 | # author, documentclass [howto, manual, or own class]). 200 | latex_documents = [ 201 | ('index', 'momonga.tex', u'momonga Documentation', 202 | u'Shuhei Tanuma', 'manual'), 203 | ] 204 | 205 | # The name of an image file (relative to this directory) to place at the top of 206 | # the title page. 207 | #latex_logo = None 208 | 209 | # For "manual" documents, if this is true, then toplevel headings are parts, 210 | # not chapters. 211 | #latex_use_parts = False 212 | 213 | # If true, show page references after internal links. 214 | #latex_show_pagerefs = False 215 | 216 | # If true, show URL addresses after external links. 217 | #latex_show_urls = False 218 | 219 | # Documents to append as an appendix to all manuals. 220 | #latex_appendices = [] 221 | 222 | # If false, no module index is generated. 223 | #latex_domain_indices = True 224 | 225 | 226 | # -- Options for manual page output --------------------------------------- 227 | 228 | # One entry per manual page. List of tuples 229 | # (source start file, name, description, authors, manual section). 230 | man_pages = [ 231 | ('index', 'momonga', u'momonga Documentation', 232 | [u'Shuhei Tanuma'], 1) 233 | ] 234 | 235 | # If true, show URL addresses after external links. 236 | #man_show_urls = False 237 | 238 | 239 | # -- Options for Texinfo output ------------------------------------------- 240 | 241 | # Grouping the document tree into Texinfo files. List of tuples 242 | # (source start file, target name, title, author, 243 | # dir menu entry, description, category) 244 | texinfo_documents = [ 245 | ('index', 'momonga', u'momonga Documentation', 246 | u'Shuhei Tanuma', 'momonga', 'One line description of project.', 247 | 'Miscellaneous'), 248 | ] 249 | 250 | # Documents to append as an appendix to all manuals. 251 | #texinfo_appendices = [] 252 | 253 | # If false, no module index is generated. 254 | #texinfo_domain_indices = True 255 | 256 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 257 | #texinfo_show_urls = 'footnote' 258 | 259 | # If true, do not generate a @detailmenu in the "Top" node's menu. 260 | #texinfo_no_detailmenu = False 261 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============== 3 | 4 | Momonga uses TOML syntax for configuration. 5 | 6 | Getting the latest configuration 7 | ````````````````````````````````` 8 | You may wish to consult the `config.toml in source control `_ for all of the possible latest values. 9 | 10 | General server 11 | --------------- 12 | 13 | In the [server] section of config.toml, the following settings are tunable: 14 | 15 | log_file 16 | ++++++++ 17 | 18 | like:: 19 | 20 | log_file = "/var/log/momonga.log" 21 | 22 | stdout, stderr are useful when debugging. 23 | 24 | log_level 25 | +++++++++ 26 | 27 | like:: 28 | 29 | log_level = "debug" 30 | 31 | debug, info, warn and error are available 32 | 33 | pid_file 34 | ++++++++ 35 | 36 | like:: 37 | 38 | pid_file = "/var/run/momonga.pid" 39 | 40 | .. toctree:: 41 | :maxdepth: 1 42 | 43 | -------------------------------------------------------------------------------- /docs/developing.rst: -------------------------------------------------------------------------------- 1 | Developer Information 2 | ===================== 3 | 4 | Momonga is still highly under development. for now, this section tells you how to build momonga: 5 | 6 | # Install go 7 | curl -O https://storage.googleapis.com/golang/go1.3.2.linux-amd64.tar.gz 8 | sudo tar -C /usr/local -zxf go1.3.2.linux-amd64.tar.gz 9 | 10 | echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc 11 | source ~/.bashrc 12 | 13 | # Building momonga 14 | mkdir momonga && cd momonga 15 | export GOPATH=`pwd` 16 | go get github.com/chobie/momonga/momonga 17 | go build -o momonga github.com/chobie/momonga/momonga 18 | 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | 23 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Momonga MQTT Documentation 2 | ========================== 3 | 4 | About Momonga 5 | ````````````` 6 | 7 | Welcome to the Momonga documentation! 8 | 9 | Momonga is an MQTT Server. It is supported MQTT3.1.1 specification, and written in Golang. 10 | You can easy to deploy Momonga MQTT Server in any environment. 11 | 12 | Momonga goals are provide durable middleware in Go, extendable MQTT server and fast delivery. 13 | 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | 18 | intro 19 | quickstart 20 | developing 21 | configuration 22 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Momonga is MQTT server written in Golang. MQTT has ability to provide M2M(Machine to machine), IoT(Internet of things) infrastructure easily. 5 | Also, it is useful for just PUBSUB something. 6 | Momonga started as a personal project which learning MQTT specification and how to write Go middleware. 7 | 8 | http://techtalk.chobie.net/practicalgo.slide#42 9 | 10 | .. toctree:: 11 | :maxdepth: 1 12 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\momonga.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\momonga.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | QuickStart 2 | ========== 3 | 4 | We've prepared prebuilt binaries `here `_ 5 | 6 | .. code-block:: bash 7 | 8 | wget https://drone.io/github.com/chobie/momonga/files/artifacts/bin/darwin_amd64/momonga 9 | chmod +x momonga 10 | ./momonga 11 | 12 | Enjoy, and be sure to visit the rest of the documentation to learn more. 13 | -------------------------------------------------------------------------------- /encoding/mqtt/connack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type ConnackMessage struct { 14 | FixedHeader 15 | Reserved uint8 16 | ReturnCode uint8 17 | } 18 | 19 | func (self *ConnackMessage) decode(reader io.Reader) error { 20 | binary.Read(reader, binary.BigEndian, &self.Reserved) 21 | binary.Read(reader, binary.BigEndian, &self.ReturnCode) 22 | 23 | return nil 24 | } 25 | 26 | func (self ConnackMessage) WriteTo(w io.Writer) (int64, error) { 27 | var fsize = 2 28 | size, err := self.FixedHeader.writeTo(fsize, w) 29 | if err != nil { 30 | return 0, err 31 | } 32 | 33 | binary.Write(w, binary.BigEndian, self.Reserved) 34 | binary.Write(w, binary.BigEndian, self.ReturnCode) 35 | 36 | return int64(fsize) + size, nil 37 | } 38 | 39 | func (self *ConnackMessage) String() string { 40 | b, _ := json.Marshal(self) 41 | return string(b) 42 | } 43 | -------------------------------------------------------------------------------- /encoding/mqtt/connect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | ) 14 | 15 | type ConnectMessage struct { 16 | FixedHeader 17 | Magic []byte `json:"magic"` 18 | Version uint8 `json:"version"` 19 | Flag uint8 `json:"flag"` 20 | KeepAlive uint16 `json:"keep_alive"` 21 | Identifier string `json:"identifier"` 22 | Will *WillMessage `json:"will"` 23 | CleanSession bool `json:clean_session` 24 | UserName []byte `json:"user_name"` 25 | Password []byte `json:"password"` 26 | } 27 | 28 | func (self *ConnectMessage) WriteTo(w io.Writer) (int64, error) { 29 | var headerLength uint16 = uint16(len(self.Magic)) 30 | var size int = 0 31 | 32 | if self.CleanSession { 33 | self.Flag |= 0x02 34 | } 35 | if self.Will != nil { 36 | self.Flag |= 0x04 37 | switch self.Will.Qos { 38 | case 1: 39 | self.Flag |= 0x08 40 | case 2: 41 | self.Flag |= 0x18 42 | } 43 | } 44 | if len(self.UserName) > 0 { 45 | self.Flag |= 0x80 46 | } 47 | if len(self.Password) > 0 { 48 | self.Flag |= 0x40 49 | } 50 | 51 | size += 2 + len(self.Magic) 52 | size += 1 + 1 + 2 53 | if self.Identifier != "" { 54 | size += 2 + len(self.Identifier) 55 | } 56 | if (int(self.Flag)&0x04 > 0) && self.Will != nil { 57 | size += self.Will.Size() 58 | } 59 | if int(self.Flag)&0x80 > 0 { 60 | size += 2 + len(self.UserName) 61 | } 62 | if int(self.Flag)&0x40 > 0 { 63 | size += 2 + len(self.Password) 64 | } 65 | 66 | self.FixedHeader.writeTo(size, w) 67 | err := binary.Write(w, binary.BigEndian, headerLength) 68 | if err != nil { 69 | return 0, err 70 | } 71 | 72 | w.Write(self.Magic) 73 | binary.Write(w, binary.BigEndian, self.Version) 74 | binary.Write(w, binary.BigEndian, self.Flag) 75 | binary.Write(w, binary.BigEndian, self.KeepAlive) 76 | 77 | var Length uint16 = 0 78 | 79 | if self.Identifier != "" { 80 | Length = uint16(len(self.Identifier)) 81 | } 82 | binary.Write(w, binary.BigEndian, Length) 83 | if Length > 0 { 84 | w.Write([]byte(self.Identifier)) 85 | } 86 | 87 | if (int(self.Flag)&0x04 > 0) && self.Will != nil { 88 | self.Will.WriteTo(w) 89 | } 90 | 91 | if int(self.Flag)&0x80 > 0 { 92 | Length = uint16(len(self.UserName)) 93 | err = binary.Write(w, binary.BigEndian, Length) 94 | w.Write(self.UserName) 95 | } 96 | if int(self.Flag)&0x40 > 0 { 97 | Length = uint16(len(self.Password)) 98 | err = binary.Write(w, binary.BigEndian, Length) 99 | w.Write(self.Password) 100 | } 101 | return int64(size), nil 102 | } 103 | 104 | func (self *ConnectMessage) decode(reader io.Reader) error { 105 | var Length uint16 106 | 107 | offset := 0 108 | buffer := make([]byte, self.FixedHeader.RemainingLength) 109 | 110 | w := bytes.NewBuffer(buffer) 111 | w.Reset() 112 | //TODO: should check error 113 | io.CopyN(w, reader, int64(self.FixedHeader.RemainingLength)) 114 | 115 | buffer = w.Bytes() 116 | reader = bytes.NewReader(buffer) 117 | 118 | binary.Read(reader, binary.BigEndian, &Length) 119 | offset += 2 120 | 121 | if self.FixedHeader.RemainingLength < offset+int(Length) { 122 | return fmt.Errorf("Length overs buffer size. %d, %d", self.FixedHeader.RemainingLength, offset+int(Length)) 123 | } 124 | 125 | self.Magic = buffer[offset : offset+int(Length)] 126 | offset += int(Length) 127 | 128 | if offset > len(buffer) { 129 | return fmt.Errorf("offset: %d, buffer: %d", offset, len(buffer)) 130 | } 131 | 132 | nr := bytes.NewReader(buffer[offset:]) 133 | binary.Read(nr, binary.BigEndian, &self.Version) 134 | binary.Read(nr, binary.BigEndian, &self.Flag) 135 | binary.Read(nr, binary.BigEndian, &self.KeepAlive) 136 | offset += 1 + 1 + 2 137 | 138 | // order Client ClientIdentifier, Will Topic, Will Message, User Name, Password 139 | var ClientIdentifierLength uint16 140 | binary.Read(nr, binary.BigEndian, &ClientIdentifierLength) 141 | offset += 2 142 | 143 | if ClientIdentifierLength > 0 { 144 | self.Identifier = string(buffer[offset : offset+int(ClientIdentifierLength)]) 145 | offset += int(ClientIdentifierLength) 146 | } 147 | 148 | if int(self.Flag)&0x04 > 0 { 149 | will := &WillMessage{} 150 | 151 | nr := bytes.NewReader(buffer[offset:]) 152 | binary.Read(nr, binary.BigEndian, &ClientIdentifierLength) 153 | offset += 2 154 | will.Topic = string(buffer[offset : offset+int(ClientIdentifierLength)]) 155 | offset += int(ClientIdentifierLength) 156 | 157 | nr = bytes.NewReader(buffer[offset:]) 158 | binary.Read(nr, binary.BigEndian, &ClientIdentifierLength) 159 | offset += 2 160 | will.Message = string(buffer[offset : offset+int(ClientIdentifierLength)]) 161 | offset += int(ClientIdentifierLength) 162 | 163 | if int(self.Flag)&0x32 > 0 { 164 | will.Retain = true 165 | } 166 | 167 | q := (int(self.Flag) >> 3) 168 | 169 | if q&0x02 > 0 { 170 | will.Qos = 2 171 | } else if q&0x01 > 0 { 172 | will.Qos = 1 173 | } 174 | self.Will = will 175 | } 176 | 177 | if int(self.Flag)&0x80 > 0 { 178 | nr := bytes.NewReader(buffer[offset:]) 179 | binary.Read(nr, binary.BigEndian, &Length) 180 | offset += 2 181 | self.UserName = buffer[offset : offset+int(Length)] 182 | offset += int(Length) 183 | } 184 | 185 | if int(self.Flag)&0x40 > 0 { 186 | nr := bytes.NewReader(buffer[offset:]) 187 | offset += 2 188 | binary.Read(nr, binary.BigEndian, &Length) 189 | self.Password = buffer[offset : offset+int(Length)] 190 | offset += int(Length) 191 | } 192 | 193 | if int(self.Flag)&0x02 > 0 { 194 | self.CleanSession = true 195 | } 196 | 197 | return nil 198 | } 199 | 200 | func (self *ConnectMessage) String() string { 201 | b, _ := json.Marshal(self) 202 | return string(b) 203 | } 204 | -------------------------------------------------------------------------------- /encoding/mqtt/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | type PacketType int 8 | 9 | const ( 10 | PACKET_TYPE_RESERVED1 PacketType = 0 11 | PACKET_TYPE_CONNECT PacketType = 1 12 | PACKET_TYPE_CONNACK PacketType = 2 13 | PACKET_TYPE_PUBLISH PacketType = 3 14 | PACKET_TYPE_PUBACK PacketType = 4 15 | PACKET_TYPE_PUBREC PacketType = 5 16 | PACKET_TYPE_PUBREL PacketType = 6 17 | PACKET_TYPE_PUBCOMP PacketType = 7 18 | PACKET_TYPE_SUBSCRIBE PacketType = 8 19 | PACKET_TYPE_SUBACK PacketType = 9 20 | PACKET_TYPE_UNSUBSCRIBE PacketType = 10 21 | PACKET_TYPE_UNSUBACK PacketType = 11 22 | PACKET_TYPE_PINGREQ PacketType = 12 23 | PACKET_TYPE_PINGRESP PacketType = 13 24 | PACKET_TYPE_DISCONNECT PacketType = 14 25 | PACKET_TYPE_RESERVED2 PacketType = 15 26 | ) 27 | 28 | type ReturnCode int 29 | 30 | const ( 31 | CONNECTION_ACCEOTED ReturnCode = 1 32 | CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION ReturnCode = 2 33 | CONNECTION_REFUSED_IDENTIFIER_REJECTED ReturnCode = 3 34 | CONNECTION_REFUSED_SERVER_UNAVAILABLE ReturnCode = 4 35 | CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD ReturnCode = 5 36 | CONNECTION_REFUSED_NOT_AUTHORIZED ReturnCode = 6 37 | ) 38 | -------------------------------------------------------------------------------- /encoding/mqtt/disconnect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | ) 11 | 12 | type DisconnectMessage struct { 13 | FixedHeader 14 | } 15 | 16 | func (self DisconnectMessage) WriteTo(w io.Writer) (int64, error) { 17 | var fsize = 0 18 | size, err := self.FixedHeader.writeTo(fsize, w) 19 | if err != nil { 20 | return 0, err 21 | } 22 | 23 | return int64(fsize) + size, nil 24 | } 25 | 26 | func (self *DisconnectMessage) String() string { 27 | b, _ := json.Marshal(self) 28 | return string(b) 29 | } 30 | -------------------------------------------------------------------------------- /encoding/mqtt/fixedheader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type FixedHeader struct { 14 | Type PacketType 15 | Dupe bool 16 | QosLevel int 17 | Retain int 18 | RemainingLength int 19 | } 20 | 21 | func (self *FixedHeader) GetType() PacketType { 22 | return self.Type 23 | } 24 | 25 | func (self *FixedHeader) GetTypeAsString() string { 26 | switch self.Type { 27 | case PACKET_TYPE_RESERVED1: 28 | return "unknown" 29 | case PACKET_TYPE_CONNECT: 30 | return "connect" 31 | case PACKET_TYPE_CONNACK: 32 | return "connack" 33 | case PACKET_TYPE_PUBLISH: 34 | return "publish" 35 | case PACKET_TYPE_PUBACK: 36 | return "puback" 37 | case PACKET_TYPE_PUBREC: 38 | return "pubrec" 39 | case PACKET_TYPE_PUBREL: 40 | return "pubrel" 41 | case PACKET_TYPE_PUBCOMP: 42 | return "pubcomp" 43 | case PACKET_TYPE_SUBSCRIBE: 44 | return "subscribe" 45 | case PACKET_TYPE_SUBACK: 46 | return "suback" 47 | case PACKET_TYPE_UNSUBSCRIBE: 48 | return "unsubscribe" 49 | case PACKET_TYPE_UNSUBACK: 50 | return "unsuback" 51 | case PACKET_TYPE_PINGREQ: 52 | return "pingreq" 53 | case PACKET_TYPE_PINGRESP: 54 | return "pingresp" 55 | case PACKET_TYPE_DISCONNECT: 56 | return "disconnect" 57 | case PACKET_TYPE_RESERVED2: 58 | return "unknown" 59 | default: 60 | return "unknown" 61 | } 62 | } 63 | 64 | func (self *FixedHeader) writeTo(length int, w io.Writer) (int64, error) { 65 | var flag uint8 = uint8(self.Type << 0x04) 66 | 67 | if self.Retain > 0 { 68 | flag |= 0x01 69 | } 70 | 71 | if self.QosLevel > 0 { 72 | if self.QosLevel == 1 { 73 | flag |= 0x02 74 | } else if self.QosLevel == 2 { 75 | flag |= 0x04 76 | } 77 | } 78 | 79 | if self.Dupe { 80 | flag |= 0x08 81 | } 82 | 83 | err := binary.Write(w, binary.BigEndian, flag) 84 | if err != nil { 85 | return 0, err 86 | } 87 | 88 | _, err = WriteVarint(w, length) 89 | if err != nil { 90 | return 0, err 91 | } 92 | 93 | return int64(2), nil 94 | } 95 | 96 | func (self *FixedHeader) decode(reader io.Reader) error { 97 | var FirstByte uint8 98 | err := binary.Read(reader, binary.BigEndian, &FirstByte) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | mt := FirstByte >> 4 104 | flag := FirstByte & 0x0f 105 | 106 | length, _ := ReadVarint(reader) 107 | 108 | self.Type = PacketType(mt) 109 | self.Dupe = ((flag & 0x08) > 0) 110 | 111 | if (flag & 0x01) > 0 { 112 | self.Retain = 1 113 | } 114 | 115 | if (flag & 0x04) > 0 { 116 | self.QosLevel = 2 117 | } else if (flag & 0x02) > 0 { 118 | self.QosLevel = 1 119 | } 120 | self.RemainingLength = length 121 | return nil 122 | } 123 | 124 | func (self *FixedHeader) String() string { 125 | b, _ := json.Marshal(self) 126 | return string(b) 127 | } 128 | -------------------------------------------------------------------------------- /encoding/mqtt/helper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "errors" 10 | "io" 11 | ) 12 | 13 | var overflow = errors.New("readvarint: varint overflows a 32-bit integer") 14 | 15 | func ReadVarint(reader io.Reader) (int, error) { 16 | var RemainingLength uint8 17 | m := 1 18 | v := 0 19 | 20 | for i := 0; i < 4; i++ { 21 | binary.Read(reader, binary.BigEndian, &RemainingLength) 22 | v += (int(RemainingLength) & 0x7F) * m 23 | 24 | m *= 0x80 25 | if m > 0x200000 { 26 | return 0, overflow 27 | } 28 | if (RemainingLength & 0x80) == 0 { 29 | break 30 | } 31 | } 32 | 33 | return v, nil 34 | } 35 | 36 | func WriteVarint(writer io.Writer, size int) (int, error) { 37 | var encode_byte uint8 38 | x := size 39 | var i int 40 | 41 | for i = 0; x > 0 && i < 4; i++ { 42 | encode_byte = uint8(x % 0x80) 43 | x = x / 0x80 44 | if x > 0 { 45 | encode_byte |= 0x80 46 | } 47 | 48 | binary.Write(writer, binary.BigEndian, encode_byte) 49 | } 50 | 51 | return i, nil 52 | } 53 | -------------------------------------------------------------------------------- /encoding/mqtt/message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | type Message interface { 8 | GetType() PacketType 9 | GetTypeAsString() string 10 | } 11 | -------------------------------------------------------------------------------- /encoding/mqtt/mqtt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | ) 12 | 13 | type ParseError struct { 14 | reason string 15 | } 16 | 17 | func (self ParseError) Error() string { 18 | return self.reason 19 | } 20 | 21 | func NewConnectMessage() *ConnectMessage { 22 | message := &ConnectMessage{ 23 | FixedHeader: FixedHeader{ 24 | Type: PACKET_TYPE_CONNECT, 25 | Dupe: false, 26 | QosLevel: 0, 27 | }, 28 | Magic: []byte("MQTT"), 29 | Version: 4, 30 | } 31 | return message 32 | } 33 | 34 | func NewSubscribeMessage() *SubscribeMessage { 35 | message := &SubscribeMessage{ 36 | FixedHeader: FixedHeader{ 37 | Type: PACKET_TYPE_SUBSCRIBE, 38 | Dupe: false, 39 | QosLevel: 1, // Must be 1 40 | }, 41 | } 42 | return message 43 | } 44 | 45 | func NewUnsubscribeMessage() *UnsubscribeMessage { 46 | message := &UnsubscribeMessage{ 47 | FixedHeader: FixedHeader{ 48 | Type: PACKET_TYPE_UNSUBSCRIBE, 49 | Dupe: false, 50 | QosLevel: 1, // Must be 1 51 | }, 52 | } 53 | return message 54 | } 55 | 56 | func NewUnsubackMessage() *UnsubackMessage { 57 | message := &UnsubackMessage{ 58 | FixedHeader: FixedHeader{ 59 | Type: PACKET_TYPE_UNSUBACK, 60 | Dupe: false, 61 | QosLevel: 0, 62 | }, 63 | } 64 | return message 65 | } 66 | 67 | func NewSubackMessage() *SubackMessage { 68 | message := &SubackMessage{ 69 | FixedHeader: FixedHeader{ 70 | Type: PACKET_TYPE_SUBACK, 71 | Dupe: false, 72 | QosLevel: 0, 73 | }, 74 | } 75 | 76 | return message 77 | } 78 | 79 | func NewPublishMessage() *PublishMessage { 80 | message := &PublishMessage{ 81 | FixedHeader: FixedHeader{ 82 | Type: PACKET_TYPE_PUBLISH, 83 | Dupe: false, 84 | QosLevel: 0, 85 | }, 86 | } 87 | return message 88 | } 89 | 90 | func NewPubackMessage() *PubackMessage { 91 | message := &PubackMessage{ 92 | FixedHeader: FixedHeader{ 93 | Type: PACKET_TYPE_PUBACK, 94 | Dupe: false, 95 | QosLevel: 0, 96 | }, 97 | } 98 | 99 | return message 100 | } 101 | 102 | func NewPubrecMessage() *PubrecMessage { 103 | message := &PubrecMessage{ 104 | FixedHeader: FixedHeader{ 105 | Type: PACKET_TYPE_PUBREC, 106 | Dupe: false, 107 | QosLevel: 0, 108 | }, 109 | } 110 | 111 | return message 112 | } 113 | 114 | func NewPubrelMessage() *PubrelMessage { 115 | message := &PubrelMessage{ 116 | FixedHeader: FixedHeader{ 117 | Type: PACKET_TYPE_PUBREL, 118 | Dupe: false, 119 | QosLevel: 1, // Must be 1 120 | }, 121 | } 122 | 123 | return message 124 | } 125 | 126 | func NewPubcompMessage() *PubcompMessage { 127 | message := &PubcompMessage{ 128 | FixedHeader: FixedHeader{ 129 | Type: PACKET_TYPE_PUBCOMP, 130 | Dupe: false, 131 | QosLevel: 0, 132 | }, 133 | } 134 | 135 | return message 136 | } 137 | 138 | func NewPingreqMessage() *PingreqMessage { 139 | message := &PingreqMessage{ 140 | FixedHeader: FixedHeader{ 141 | Type: PACKET_TYPE_PINGREQ, 142 | }, 143 | } 144 | return message 145 | } 146 | 147 | func NewPingrespMessage() *PingrespMessage { 148 | message := &PingrespMessage{ 149 | FixedHeader: FixedHeader{ 150 | Type: PACKET_TYPE_PINGRESP, 151 | }, 152 | } 153 | return message 154 | } 155 | 156 | func NewDisconnectMessage() *DisconnectMessage { 157 | message := &DisconnectMessage{ 158 | FixedHeader: FixedHeader{ 159 | Type: PACKET_TYPE_DISCONNECT, 160 | }, 161 | } 162 | return message 163 | } 164 | 165 | func NewConnackMessage() *ConnackMessage { 166 | message := &ConnackMessage{ 167 | FixedHeader: FixedHeader{ 168 | Type: PACKET_TYPE_CONNACK, 169 | QosLevel: 0, 170 | }, 171 | } 172 | return message 173 | } 174 | 175 | func MustCopyPublishMessage(msg *PublishMessage) (*PublishMessage) { 176 | v, err := CopyMessage(msg) 177 | if err != nil { 178 | panic("copy publish message failed") 179 | } 180 | 181 | return v.(*PublishMessage) 182 | } 183 | 184 | 185 | func CopyPublishMessage(msg *PublishMessage) (*PublishMessage, error) { 186 | v, err := CopyMessage(msg) 187 | if err != nil { 188 | return nil, err 189 | } 190 | 191 | return v.(*PublishMessage), nil 192 | } 193 | 194 | func CopyMessage(msg Message) (Message, error) { 195 | var result Message 196 | 197 | switch msg.GetType() { 198 | case PACKET_TYPE_PUBLISH: 199 | t := msg.(*PublishMessage) 200 | c := NewPublishMessage() 201 | c.Payload = t.Payload 202 | c.TopicName = t.TopicName 203 | c.PacketIdentifier = t.PacketIdentifier 204 | c.Opaque = t.Opaque 205 | c.FixedHeader.Type = t.FixedHeader.Type 206 | c.FixedHeader.Dupe = t.FixedHeader.Dupe 207 | c.FixedHeader.QosLevel = t.FixedHeader.QosLevel 208 | c.FixedHeader.Retain = t.FixedHeader.Retain 209 | c.FixedHeader.RemainingLength = t.FixedHeader.RemainingLength 210 | result = c 211 | break 212 | default: 213 | return nil, errors.New("hoge") 214 | } 215 | return result, nil 216 | } 217 | 218 | // TODO: このアホっぽい感じどうにかしたいなー 219 | // TODO: 読み込んだサイズ返す 220 | func ParseMessage(reader io.Reader, max_length int) (Message, error) { 221 | var message Message 222 | var err error 223 | header := FixedHeader{} 224 | 225 | err = header.decode(reader) 226 | if err != nil { 227 | return nil, err 228 | } 229 | 230 | if max_length > 0 && header.RemainingLength > max_length { 231 | return nil, errors.New(fmt.Sprintf("Payload exceedes limit. %d bytes", header.RemainingLength)) 232 | } 233 | 234 | switch header.GetType() { 235 | case PACKET_TYPE_CONNECT: 236 | mm := &ConnectMessage{ 237 | FixedHeader: header, 238 | } 239 | err = mm.decode(reader) 240 | message = mm 241 | case PACKET_TYPE_CONNACK: 242 | mm := &ConnackMessage{ 243 | FixedHeader: header, 244 | } 245 | err = mm.decode(reader) 246 | message = mm 247 | case PACKET_TYPE_PUBLISH: 248 | mm := &PublishMessage{ 249 | FixedHeader: header, 250 | } 251 | err = mm.decode(reader) 252 | message = mm 253 | case PACKET_TYPE_DISCONNECT: 254 | mm := &DisconnectMessage{ 255 | FixedHeader: header, 256 | } 257 | message = mm 258 | case PACKET_TYPE_SUBSCRIBE: 259 | mm := &SubscribeMessage{ 260 | FixedHeader: header, 261 | } 262 | err = mm.decode(reader) 263 | message = mm 264 | case PACKET_TYPE_SUBACK: 265 | mm := &SubackMessage{ 266 | FixedHeader: header, 267 | } 268 | err = mm.decode(reader) 269 | message = mm 270 | case PACKET_TYPE_UNSUBSCRIBE: 271 | mm := &UnsubscribeMessage{ 272 | FixedHeader: header, 273 | } 274 | err = mm.decode(reader) 275 | message = mm 276 | case PACKET_TYPE_UNSUBACK: 277 | mm := &UnsubackMessage{ 278 | FixedHeader: header, 279 | } 280 | err = mm.decode(reader) 281 | message = mm 282 | case PACKET_TYPE_PINGRESP: 283 | mm := &PingrespMessage{ 284 | FixedHeader: header, 285 | } 286 | message = mm 287 | case PACKET_TYPE_PINGREQ: 288 | mm := &PingreqMessage{ 289 | FixedHeader: header, 290 | } 291 | message = mm 292 | case PACKET_TYPE_PUBACK: 293 | mm := &PubackMessage{ 294 | FixedHeader: header, 295 | } 296 | err = mm.decode(reader) 297 | message = mm 298 | case PACKET_TYPE_PUBREC: 299 | mm := &PubrecMessage{ 300 | FixedHeader: header, 301 | } 302 | err = mm.decode(reader) 303 | message = mm 304 | case PACKET_TYPE_PUBREL: 305 | mm := &PubrelMessage{ 306 | FixedHeader: header, 307 | } 308 | err = mm.decode(reader) 309 | message = mm 310 | case PACKET_TYPE_PUBCOMP: 311 | mm := &PubcompMessage{ 312 | FixedHeader: header, 313 | } 314 | err = mm.decode(reader) 315 | message = mm 316 | default: 317 | return nil, &ParseError{fmt.Sprintf("Not supported: %d\n", header.GetType())} 318 | } 319 | 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | return message, nil 325 | } 326 | 327 | func WriteMessageTo(message Message, w io.Writer) (int64, error) { 328 | var written int64 329 | var e error 330 | 331 | switch message.GetType() { 332 | case PACKET_TYPE_CONNECT: 333 | m := message.(*ConnectMessage) 334 | written, e = m.WriteTo(w) 335 | case PACKET_TYPE_CONNACK: 336 | m := message.(*ConnackMessage) 337 | written, e = m.WriteTo(w) 338 | case PACKET_TYPE_PUBLISH: 339 | m := message.(*PublishMessage) 340 | written, e = m.WriteTo(w) 341 | case PACKET_TYPE_SUBSCRIBE: 342 | m := message.(*SubscribeMessage) 343 | written, e = m.WriteTo(w) 344 | case PACKET_TYPE_SUBACK: 345 | m := message.(*SubackMessage) 346 | written, e = m.WriteTo(w) 347 | case PACKET_TYPE_UNSUBSCRIBE: 348 | m := message.(*UnsubscribeMessage) 349 | written, e = m.WriteTo(w) 350 | case PACKET_TYPE_DISCONNECT: 351 | m := message.(*DisconnectMessage) 352 | written, e = m.WriteTo(w) 353 | case PACKET_TYPE_UNSUBACK: 354 | m := message.(*UnsubackMessage) 355 | written, e = m.WriteTo(w) 356 | case PACKET_TYPE_PUBACK: 357 | m := message.(*PubackMessage) 358 | written, e = m.WriteTo(w) 359 | case PACKET_TYPE_PUBREC: 360 | m := message.(*PubrecMessage) 361 | written, e = m.WriteTo(w) 362 | case PACKET_TYPE_PUBREL: 363 | m := message.(*PubrelMessage) 364 | written, e = m.WriteTo(w) 365 | case PACKET_TYPE_PUBCOMP: 366 | m := message.(*PubcompMessage) 367 | written, e = m.WriteTo(w) 368 | case PACKET_TYPE_PINGREQ: 369 | m := message.(*PingreqMessage) 370 | written, e = m.WriteTo(w) 371 | case PACKET_TYPE_PINGRESP: 372 | m := message.(*PingrespMessage) 373 | written, e = m.WriteTo(w) 374 | default: 375 | fmt.Printf("Not supported message") 376 | } 377 | 378 | return written, e 379 | } 380 | -------------------------------------------------------------------------------- /encoding/mqtt/parse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "bytes" 9 | . "gopkg.in/check.v1" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func Test(t *testing.T) { TestingT(t) } 15 | 16 | type MySuite struct{} 17 | 18 | var _ = Suite(&MySuite{}) 19 | 20 | func (s *MySuite) TestDecodePublishMessage(c *C) { 21 | m := NewPublishMessage() 22 | m.TopicName = "/debug" 23 | m.Payload = []byte("Hello World") 24 | 25 | buf := bytes.NewBuffer(nil) 26 | WriteMessageTo(m, buf) 27 | 28 | ParseMessage(bytes.NewReader(buf.Bytes()), 0) 29 | } 30 | 31 | func (s *MySuite) TestPublishMessage(c *C) { 32 | m := NewPublishMessage() 33 | m.TopicName = "/debug" 34 | m.Payload = []byte("Hello World") 35 | buf := bytes.NewBuffer(nil) 36 | _, err := WriteMessageTo(m, buf) 37 | 38 | c.Assert(err, Equals, nil) 39 | } 40 | 41 | func (s *MySuite) TestPublishMessage2(c *C) { 42 | m := NewPublishMessage() 43 | m.TopicName = "/debug" 44 | m.Payload = []byte("Hello World") 45 | buf := bytes.NewBuffer(nil) 46 | WriteMessageTo(m, buf) 47 | 48 | buffer := bytes.NewBuffer(nil) 49 | m.WriteTo(buffer) 50 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 51 | } 52 | 53 | func (s *MySuite) BenchmarkWriteToPublishMessage(c *C) { 54 | m := NewPublishMessage() 55 | m.TopicName = "/debug" 56 | m.Payload = []byte("Hello World") 57 | 58 | b := make([]byte, 256) 59 | buffer := bytes.NewBuffer(b) 60 | for i := 0; i < c.N; i++ { 61 | buffer.Reset() 62 | m.WriteTo(buffer) 63 | } 64 | } 65 | 66 | func (s *MySuite) TestPubrecMessage(c *C) { 67 | m := NewPubrecMessage() 68 | buf := bytes.NewBuffer(nil) 69 | WriteMessageTo(m, buf) 70 | 71 | buffer := bytes.NewBuffer(nil) 72 | m.WriteTo(buffer) 73 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 74 | } 75 | 76 | func (s *MySuite) BenchmarkPubrecMessage(c *C) { 77 | m := NewPubrecMessage() 78 | 79 | buffer := bytes.NewBuffer(nil) 80 | m.WriteTo(buffer) 81 | 82 | for i := 0; i < c.N; i++ { 83 | buffer.Reset() 84 | m.WriteTo(buffer) 85 | } 86 | } 87 | 88 | func (s *MySuite) TestPubrelMessage(c *C) { 89 | m := NewPubrelMessage() 90 | buf := bytes.NewBuffer(nil) 91 | WriteMessageTo(m, buf) 92 | 93 | buffer := bytes.NewBuffer(nil) 94 | m.WriteTo(buffer) 95 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 96 | } 97 | 98 | func (s *MySuite) TestSubackMessage(c *C) { 99 | m := NewSubackMessage() 100 | buf := bytes.NewBuffer(nil) 101 | WriteMessageTo(m, buf) 102 | 103 | buffer := bytes.NewBuffer(nil) 104 | m.WriteTo(buffer) 105 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 106 | } 107 | 108 | func (s *MySuite) TestUnsubackMessage(c *C) { 109 | m := NewUnsubackMessage() 110 | buf := bytes.NewBuffer(nil) 111 | WriteMessageTo(m, buf) 112 | 113 | buffer := bytes.NewBuffer(nil) 114 | m.WriteTo(buffer) 115 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 116 | } 117 | 118 | func (s *MySuite) TestSubscribeMessage(c *C) { 119 | m := NewSubscribeMessage() 120 | m.Payload = append(m.Payload, SubscribePayload{TopicPath: "/debug", RequestedQos: 2}) 121 | buf := bytes.NewBuffer(nil) 122 | WriteMessageTo(m, buf) 123 | 124 | buffer := bytes.NewBuffer(nil) 125 | m.WriteTo(buffer) 126 | 127 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 128 | } 129 | 130 | func (s *MySuite) TestConnackMessage(c *C) { 131 | m := NewConnackMessage() 132 | buf := bytes.NewBuffer(nil) 133 | WriteMessageTo(m, buf) 134 | 135 | buffer := bytes.NewBuffer(nil) 136 | m.WriteTo(buffer) 137 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 138 | } 139 | 140 | func (s *MySuite) TestUnsubscribeMessage(c *C) { 141 | m := NewUnsubscribeMessage() 142 | m.Payload = append(m.Payload, SubscribePayload{TopicPath: "/debug", RequestedQos: 2}) 143 | buf := bytes.NewBuffer(nil) 144 | WriteMessageTo(m, buf) 145 | 146 | buffer := bytes.NewBuffer(nil) 147 | m.WriteTo(buffer) 148 | 149 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 150 | } 151 | 152 | func (s *MySuite) TestConnectMessage(c *C) { 153 | msg := NewConnectMessage() 154 | msg.Magic = []byte("MQTT") 155 | msg.Version = uint8(4) 156 | msg.Identifier = "debug" 157 | msg.CleanSession = true 158 | msg.KeepAlive = uint16(10) 159 | 160 | buf := bytes.NewBuffer(nil) 161 | WriteMessageTo(msg, buf) 162 | 163 | buffer := bytes.NewBuffer(nil) 164 | msg.WriteTo(buffer) 165 | c.Assert(bytes.Compare(buf.Bytes(), buffer.Bytes()), Equals, 0) 166 | } 167 | 168 | func (s *MySuite) TestConnectWillMessage(c *C) { 169 | msg := NewConnectMessage() 170 | msg.Magic = []byte("MQTT") 171 | msg.Version = uint8(4) 172 | msg.Identifier = "debug" 173 | msg.CleanSession = true 174 | msg.KeepAlive = uint16(10) 175 | msg.Will = &WillMessage{ 176 | Topic: "/debug", 177 | Message: "Dead", 178 | Retain: false, 179 | Qos: 1, 180 | } 181 | 182 | buf := bytes.NewBuffer(nil) 183 | WriteMessageTo(msg, buf) 184 | 185 | ParseMessage(bytes.NewReader(buf.Bytes()), 0) 186 | } 187 | 188 | func (s *MySuite) BenchmarkConnectMessage(c *C) { 189 | msg := NewConnectMessage() 190 | msg.Magic = []byte("MQTT") 191 | msg.Version = uint8(4) 192 | msg.Identifier = "debug" 193 | msg.CleanSession = true 194 | msg.KeepAlive = uint16(10) 195 | 196 | buffer := bytes.NewBuffer(nil) 197 | for i := 0; i < c.N; i++ { 198 | buffer.Reset() 199 | msg.WriteTo(buffer) 200 | } 201 | } 202 | 203 | func (s *MySuite) TestEncodeLargePublishMessage(c *C) { 204 | m := NewPublishMessage() 205 | m.TopicName = "/debug" 206 | m.Payload = []byte(strings.Repeat("a", 1024)) 207 | 208 | buf := bytes.NewBuffer(nil) 209 | _, e := WriteMessageTo(m, buf) 210 | c.Assert(e, Equals, nil) 211 | 212 | _, e = ParseMessage(bytes.NewReader(buf.Bytes()), 0) 213 | c.Assert(e, Equals, nil) 214 | } 215 | 216 | func (s *MySuite) BenchmarkEncode(c *C) { 217 | m := NewPublishMessage() 218 | m.TopicName = "/debug" 219 | m.Payload = []byte("Hello World") 220 | buf := bytes.NewBuffer(nil) 221 | 222 | for i := 0; i < c.N; i++ { 223 | buf.Reset() 224 | WriteMessageTo(m, buf) 225 | } 226 | } 227 | 228 | func (s *MySuite) BenchmarkParsePublishMessage(c *C) { 229 | m := NewPublishMessage() 230 | m.TopicName = "/debug" 231 | m.Payload = []byte("Hello World") 232 | 233 | for i := 0; i < c.N; i++ { 234 | ParseMessage(bytes.NewReader(nil), 0) 235 | } 236 | } 237 | 238 | func (s *MySuite) BenchmarkParseSubscribe(c *C) { 239 | m := NewSubscribeMessage() 240 | m.Payload = append(m.Payload, SubscribePayload{TopicPath: "/debug"}) 241 | m.PacketIdentifier = 1 242 | buf := bytes.NewBuffer(nil) 243 | WriteMessageTo(m, buf) 244 | 245 | for i := 0; i < c.N; i++ { 246 | ParseMessage(bytes.NewReader(buf.Bytes()), 0) 247 | } 248 | } 249 | 250 | func (s *MySuite) TestParseConnectMessage(c *C) { 251 | msg := NewConnectMessage() 252 | msg.Magic = []byte("MQTT") 253 | msg.Version = uint8(4) 254 | msg.UserName = []byte("hoge") 255 | msg.Password = []byte("huga") 256 | msg.Identifier = "debug" 257 | msg.CleanSession = true 258 | msg.KeepAlive = uint16(10) 259 | msg.Will = &WillMessage{ 260 | Topic: "debug", 261 | Message: "he", 262 | } 263 | buf := bytes.NewBuffer(nil) 264 | WriteMessageTo(msg, buf) 265 | 266 | ParseMessage(bytes.NewReader(buf.Bytes()), 0) 267 | 268 | //c.Assert(bytes.Compare(a, buffer.Bytes()), Equals, 0) 269 | } 270 | 271 | func (s *MySuite) BenchmarkParseConnectMessage(c *C) { 272 | msg := NewConnectMessage() 273 | msg.Magic = []byte("MQTT") 274 | msg.Version = uint8(4) 275 | msg.UserName = []byte("hoge") 276 | msg.Password = []byte("huga") 277 | msg.Identifier = "debug" 278 | msg.CleanSession = true 279 | msg.KeepAlive = uint16(10) 280 | msg.Will = &WillMessage{ 281 | Topic: "debug", 282 | Message: "he", 283 | } 284 | buf := bytes.NewBuffer(nil) 285 | WriteMessageTo(msg, buf) 286 | 287 | for i := 0; i < c.N; i++ { 288 | ParseMessage(bytes.NewReader(buf.Bytes()), 0) 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /encoding/mqtt/pingreq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | ) 11 | 12 | type PingreqMessage struct { 13 | FixedHeader 14 | } 15 | 16 | func (self *PingreqMessage) WriteTo(w io.Writer) (int64, error) { 17 | var fsize = 0 18 | size, err := self.FixedHeader.writeTo(fsize, w) 19 | if err != nil { 20 | return 0, err 21 | } 22 | 23 | return int64(fsize) + size, nil 24 | } 25 | 26 | func (self *PingreqMessage) String() string { 27 | b, _ := json.Marshal(self) 28 | return string(b) 29 | } 30 | -------------------------------------------------------------------------------- /encoding/mqtt/pingresp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | ) 11 | 12 | type PingrespMessage struct { 13 | FixedHeader 14 | } 15 | 16 | func (self PingrespMessage) WriteTo(w io.Writer) (int64, error) { 17 | var fsize = 0 18 | size, err := self.FixedHeader.writeTo(fsize, w) 19 | if err != nil { 20 | return 0, err 21 | } 22 | 23 | return int64(fsize) + size, nil 24 | } 25 | 26 | func (self *PingrespMessage) String() string { 27 | b, _ := json.Marshal(self) 28 | return string(b) 29 | } 30 | -------------------------------------------------------------------------------- /encoding/mqtt/puback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type PubackMessage struct { 14 | FixedHeader 15 | PacketIdentifier uint16 16 | } 17 | 18 | func (self *PubackMessage) decode(reader io.Reader) error { 19 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 20 | return nil 21 | } 22 | 23 | func (self *PubackMessage) WriteTo(w io.Writer) (int64, error) { 24 | var fsize = 2 25 | size, err := self.FixedHeader.writeTo(fsize, w) 26 | if err != nil { 27 | return 0, err 28 | } 29 | 30 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 31 | return int64(size) + int64(fsize), nil 32 | } 33 | 34 | func (self *PubackMessage) String() string { 35 | b, _ := json.Marshal(self) 36 | return string(b) 37 | } 38 | -------------------------------------------------------------------------------- /encoding/mqtt/pubcomp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type PubcompMessage struct { 14 | FixedHeader 15 | PacketIdentifier uint16 16 | } 17 | 18 | func (self *PubcompMessage) decode(reader io.Reader) error { 19 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 20 | return nil 21 | } 22 | 23 | func (self *PubcompMessage) WriteTo(w io.Writer) (int64, error) { 24 | var fsize = 2 25 | size, err := self.FixedHeader.writeTo(fsize, w) 26 | if err != nil { 27 | return 0, err 28 | } 29 | 30 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 31 | return int64(size) + int64(fsize), nil 32 | } 33 | 34 | func (self *PubcompMessage) String() string { 35 | b, _ := json.Marshal(self) 36 | return string(b) 37 | } 38 | -------------------------------------------------------------------------------- /encoding/mqtt/publish.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | ) 14 | 15 | type PublishMessage struct { 16 | FixedHeader `json:"header"` 17 | TopicName string `json:"topic_name"` 18 | PacketIdentifier uint16 `json:"identifier"` 19 | Payload []byte `json:"payload"` 20 | Opaque interface{} `json:"-"` 21 | } 22 | 23 | func (self *PublishMessage) decode(reader io.Reader) error { 24 | var length uint16 25 | remaining := self.FixedHeader.RemainingLength 26 | binary.Read(reader, binary.BigEndian, &length) 27 | remaining -= 2 28 | 29 | if remaining < 1 { 30 | return fmt.Errorf("something wrong. (probably crouppted data?)") 31 | } 32 | 33 | buffer := make([]byte, remaining) 34 | offset := 0 35 | for offset < remaining { 36 | if offset > remaining { 37 | panic("something went to wrong(offset overs remianing length)") 38 | } 39 | 40 | i, err := reader.Read(buffer[offset:]) 41 | offset += i 42 | if err != nil && offset < remaining { 43 | // if we read whole size of message, ignore error at this time. 44 | return err 45 | } 46 | } 47 | 48 | if int(length) > len(buffer) { 49 | return fmt.Errorf("publish length: %d, buffer: %d", length, len(buffer)) 50 | } 51 | 52 | self.TopicName = string(buffer[0:length]) 53 | payload_offset := length 54 | if self.FixedHeader.QosLevel > 0 { 55 | binary.Read(bytes.NewReader(buffer[length:]), binary.BigEndian, &self.PacketIdentifier) 56 | payload_offset += 2 57 | } 58 | self.Payload = buffer[payload_offset:] 59 | return nil 60 | } 61 | 62 | func (self *PublishMessage) WriteTo(w io.Writer) (int64, error) { 63 | var size uint16 = uint16(len(self.TopicName)) 64 | total := 2 + int(size) 65 | if self.QosLevel > 0 { 66 | total += 2 67 | } 68 | total += len(self.Payload) 69 | 70 | header_len, e := self.FixedHeader.writeTo(total, w) 71 | if e != nil { 72 | return 0, e 73 | } 74 | total += int(size) 75 | 76 | e = binary.Write(w, binary.BigEndian, size) 77 | if e != nil { 78 | return 0, e 79 | } 80 | w.Write([]byte(self.TopicName)) 81 | if self.QosLevel > 0 { 82 | e = binary.Write(w, binary.BigEndian, self.PacketIdentifier) 83 | } 84 | if e != nil { 85 | return 0, e 86 | } 87 | _, e = w.Write(self.Payload) 88 | if e != nil { 89 | return 0, e 90 | } 91 | 92 | return int64(int(total) + int(header_len)), nil 93 | } 94 | 95 | func (self *PublishMessage) String() string { 96 | b, _ := json.Marshal(self) 97 | return string(b) 98 | } 99 | -------------------------------------------------------------------------------- /encoding/mqtt/pubrec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type PubrecMessage struct { 14 | FixedHeader 15 | PacketIdentifier uint16 16 | } 17 | 18 | func (self *PubrecMessage) WriteTo(w io.Writer) (int64, error) { 19 | var fsize = 2 20 | size, err := self.FixedHeader.writeTo(fsize, w) 21 | if err != nil { 22 | return 0, err 23 | } 24 | 25 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 26 | return int64(size) + int64(fsize), nil 27 | } 28 | 29 | func (self *PubrecMessage) decode(reader io.Reader) error { 30 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 31 | return nil 32 | } 33 | 34 | func (self *PubrecMessage) String() string { 35 | b, _ := json.Marshal(self) 36 | return string(b) 37 | } 38 | -------------------------------------------------------------------------------- /encoding/mqtt/pubrel.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type PubrelMessage struct { 14 | FixedHeader 15 | PacketIdentifier uint16 16 | } 17 | 18 | func (self *PubrelMessage) WriteTo(w io.Writer) (int64, error) { 19 | var fsize = 2 20 | size, err := self.FixedHeader.writeTo(fsize, w) 21 | if err != nil { 22 | return 0, err 23 | } 24 | 25 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 26 | return int64(size) + int64(fsize), nil 27 | } 28 | 29 | func (self *PubrelMessage) decode(reader io.Reader) error { 30 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 31 | return nil 32 | } 33 | 34 | func (self *PubrelMessage) String() string { 35 | b, _ := json.Marshal(self) 36 | return string(b) 37 | } 38 | -------------------------------------------------------------------------------- /encoding/mqtt/suback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "encoding/json" 11 | "io" 12 | ) 13 | 14 | type SubackMessage struct { 15 | FixedHeader 16 | PacketIdentifier uint16 17 | Qos []byte 18 | } 19 | 20 | func (self *SubackMessage) WriteTo(w io.Writer) (int64, error) { 21 | var fsize = 2 + len(self.Qos) 22 | 23 | size, err := self.FixedHeader.writeTo(fsize, w) 24 | if err != nil { 25 | return 0, err 26 | } 27 | 28 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 29 | io.Copy(w, bytes.NewReader(self.Qos)) 30 | 31 | return int64(size) + int64(fsize), nil 32 | } 33 | 34 | func (self *SubackMessage) decode(reader io.Reader) error { 35 | var remaining uint8 36 | remaining = uint8(self.FixedHeader.RemainingLength) 37 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 38 | 39 | remaining -= 2 40 | buffer := bytes.NewBuffer(nil) 41 | for i := 0; i <= int(remaining); i++ { 42 | var value uint8 = 0 43 | binary.Read(reader, binary.BigEndian, &value) 44 | binary.Write(buffer, binary.BigEndian, value) 45 | remaining -= 1 46 | } 47 | 48 | self.Qos = buffer.Bytes() 49 | return nil 50 | } 51 | 52 | func (self *SubackMessage) String() string { 53 | b, _ := json.Marshal(self) 54 | return string(b) 55 | } 56 | -------------------------------------------------------------------------------- /encoding/mqtt/subscribe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "encoding/json" 11 | "io" 12 | ) 13 | 14 | type SubscribePayload struct { 15 | TopicPath string 16 | RequestedQos uint8 17 | } 18 | 19 | type SubscribeMessage struct { 20 | FixedHeader 21 | PacketIdentifier uint16 22 | Payload []SubscribePayload 23 | } 24 | 25 | func (self *SubscribeMessage) WriteTo(w io.Writer) (int64, error) { 26 | var total int = 0 27 | total += 2 28 | 29 | for i := 0; i < len(self.Payload); i++ { 30 | var length uint16 = uint16(len(self.Payload[i].TopicPath)) 31 | total += 2 + int(length) + 1 32 | } 33 | 34 | header_len, _ := self.FixedHeader.writeTo(total, w) 35 | total += total + int(header_len) 36 | 37 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 38 | for i := 0; i < len(self.Payload); i++ { 39 | var length uint16 = uint16(len(self.Payload[i].TopicPath)) 40 | binary.Write(w, binary.BigEndian, length) 41 | w.Write([]byte(self.Payload[i].TopicPath)) 42 | binary.Write(w, binary.BigEndian, self.Payload[i].RequestedQos) 43 | } 44 | 45 | return int64(total), nil 46 | } 47 | 48 | func (self *SubscribeMessage) decode(reader io.Reader) error { 49 | remaining := self.RemainingLength 50 | 51 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 52 | remaining -= int(2) 53 | 54 | buffer := bytes.NewBuffer(nil) 55 | for remaining > 0 { 56 | var length uint16 57 | 58 | m := SubscribePayload{} 59 | binary.Read(reader, binary.BigEndian, &length) 60 | 61 | _, _ = io.CopyN(buffer, reader, int64(length)) 62 | 63 | m.TopicPath = string(buffer.Bytes()) 64 | binary.Read(reader, binary.BigEndian, &m.RequestedQos) 65 | self.Payload = append(self.Payload, m) 66 | 67 | buffer.Reset() 68 | remaining -= (int(length) + 1 + 2) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func (self *SubscribeMessage) String() string { 75 | b, _ := json.Marshal(self) 76 | return string(b) 77 | } 78 | -------------------------------------------------------------------------------- /encoding/mqtt/unsuback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type UnsubackMessage struct { 14 | FixedHeader 15 | PacketIdentifier uint16 16 | } 17 | 18 | func (self *UnsubackMessage) WriteTo(w io.Writer) (int64, error) { 19 | var fsize = 2 20 | size, err := self.FixedHeader.writeTo(fsize, w) 21 | if err != nil { 22 | return 0, err 23 | } 24 | 25 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 26 | return int64(size) + int64(fsize), nil 27 | } 28 | 29 | func (self *UnsubackMessage) decode(reader io.Reader) error { 30 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 31 | return nil 32 | } 33 | 34 | func (self *UnsubackMessage) String() string { 35 | b, _ := json.Marshal(self) 36 | return string(b) 37 | } 38 | -------------------------------------------------------------------------------- /encoding/mqtt/unsubscribe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "encoding/json" 11 | "io" 12 | ) 13 | 14 | type UnsubscribeMessage struct { 15 | FixedHeader 16 | TopicName string 17 | PacketIdentifier uint16 18 | Payload []SubscribePayload 19 | } 20 | 21 | func (self *UnsubscribeMessage) decode(reader io.Reader) error { 22 | remaining := self.RemainingLength 23 | 24 | binary.Read(reader, binary.BigEndian, &self.PacketIdentifier) 25 | remaining -= int(2) 26 | 27 | buffer := bytes.NewBuffer(nil) 28 | for remaining > 0 { 29 | var length uint16 = 0 30 | 31 | m := SubscribePayload{} 32 | binary.Read(reader, binary.BigEndian, &length) 33 | _, _ = io.CopyN(buffer, reader, int64(length)) 34 | m.TopicPath = string(buffer.Bytes()) 35 | buffer.Reset() 36 | self.Payload = append(self.Payload, m) 37 | remaining -= (int(length) + 1 + 2) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (self *UnsubscribeMessage) WriteTo(w io.Writer) (int64, error) { 44 | var total = 2 45 | for i := 0; i < len(self.Payload); i++ { 46 | length := uint16(len(self.Payload[i].TopicPath)) 47 | total += 2 + int(length) 48 | } 49 | 50 | header_len, _ := self.FixedHeader.writeTo(total, w) 51 | total += total + int(header_len) 52 | 53 | binary.Write(w, binary.BigEndian, self.PacketIdentifier) 54 | for i := 0; i < len(self.Payload); i++ { 55 | var length uint16 = 0 56 | length = uint16(len(self.Payload[i].TopicPath)) 57 | binary.Write(w, binary.BigEndian, length) 58 | w.Write([]byte(self.Payload[i].TopicPath)) 59 | } 60 | 61 | return int64(total), nil 62 | } 63 | 64 | func (self *UnsubscribeMessage) String() string { 65 | b, _ := json.Marshal(self) 66 | return string(b) 67 | } 68 | -------------------------------------------------------------------------------- /encoding/mqtt/will.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package mqtt 6 | 7 | import ( 8 | "encoding/binary" 9 | "encoding/json" 10 | "io" 11 | ) 12 | 13 | type WillMessage struct { 14 | Qos uint8 `json:"qos"` 15 | Topic string `json:"topic"` 16 | Message string `json:"message"` 17 | Retain bool `json:retain` 18 | } 19 | 20 | func (self *WillMessage) WriteTo(w io.Writer) (int64, error) { 21 | var topic_length uint16 22 | var size int = 0 23 | 24 | topic_length = uint16(len(self.Topic)) 25 | err := binary.Write(w, binary.BigEndian, topic_length) 26 | w.Write([]byte(self.Topic)) 27 | size += 2 + int(topic_length) 28 | 29 | message_length := uint16(len(self.Message)) 30 | err = binary.Write(w, binary.BigEndian, message_length) 31 | w.Write([]byte(self.Message)) 32 | size += 2 + int(message_length) 33 | 34 | return int64(size), err 35 | } 36 | 37 | func (self *WillMessage) Size() int { 38 | var size int = 0 39 | 40 | topic_length := uint16(len(self.Topic)) 41 | size += 2 + int(topic_length) 42 | 43 | message_length := uint16(len(self.Message)) 44 | size += 2 + int(message_length) 45 | 46 | return size 47 | } 48 | 49 | func (self *WillMessage) String() string { 50 | b, _ := json.Marshal(self) 51 | return string(b) 52 | } 53 | -------------------------------------------------------------------------------- /expvar/diff.go: -------------------------------------------------------------------------------- 1 | package expvar 2 | 3 | import ( 4 | origin "expvar" 5 | "strconv" 6 | "sync" 7 | ) 8 | 9 | type DiffInt struct { 10 | mu sync.RWMutex 11 | i int64 12 | l int64 13 | c int 14 | b bool 15 | } 16 | 17 | func (v *DiffInt) String() string { 18 | v.mu.RLock() 19 | defer v.mu.RUnlock() 20 | 21 | if v.c > 2 { 22 | return strconv.FormatInt(v.i-v.l, 10) 23 | } else { 24 | return "0" 25 | } 26 | } 27 | 28 | func (v *DiffInt) Set(delta int64) { 29 | v.mu.Lock() 30 | defer v.mu.Unlock() 31 | v.l = v.i 32 | v.i = delta 33 | v.c += 1 34 | if !v.b && v.c > 1 { 35 | v.b = true 36 | } 37 | } 38 | 39 | func NewDiffInt(name string) *DiffInt { 40 | v := new(DiffInt) 41 | origin.Publish(name, v) 42 | return v 43 | } 44 | 45 | type DiffFloat struct { 46 | mu sync.RWMutex 47 | f float64 48 | l float64 49 | c int 50 | b bool 51 | } 52 | 53 | func (v *DiffFloat) String() string { 54 | v.mu.RLock() 55 | defer v.mu.RUnlock() 56 | 57 | if v.b { 58 | return strconv.FormatFloat(v.f-v.l, 'g', -1, 64) 59 | } else { 60 | return "0.0" 61 | } 62 | } 63 | 64 | func (v *DiffFloat) Set(delta float64) { 65 | v.mu.Lock() 66 | defer v.mu.Unlock() 67 | v.l = v.f 68 | v.f = delta 69 | v.c += 1 70 | if !v.b && v.c > 1 { 71 | v.b = true 72 | } 73 | } 74 | 75 | func NewDiffFloat(name string) *DiffFloat { 76 | v := new(DiffFloat) 77 | origin.Publish(name, v) 78 | return v 79 | } 80 | -------------------------------------------------------------------------------- /flags/flags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package flags 5 | 6 | import ( 7 | // log "github.com/chobie/momonga/logger" 8 | ) 9 | 10 | var ( 11 | Mflags map[string]bool 12 | ) 13 | 14 | func init() { 15 | Mflags = make(map[string]bool) 16 | // リトライできるQoS1をゆうこうにする 17 | // これがまたつくりのおかげで色々な問題があるんだわー。 18 | // newidもonにしないと動きません 19 | // 20 | // このモデル(配送制御をgoroutineでやる)はスケールしないのでダメ 21 | Mflags["experimental.qos1"] = false 22 | 23 | // log.Debug("=============EXPERIMENTAL FLAGS=============") 24 | // for k, v := range Mflags { 25 | // log.Debug("%s: %t", k, v) 26 | // } 27 | // log.Debug("============================================") 28 | } 29 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package logger 6 | 7 | import ( 8 | log "code.google.com/p/log4go" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | type Logger interface { 14 | Info(m interface{}, args ...interface{}) 15 | Error(m interface{}, args ...interface{}) error 16 | Debug(m interface{}, args ...interface{}) 17 | } 18 | 19 | var Global Logger 20 | 21 | func init() { 22 | Global = log.Global 23 | } 24 | 25 | func SetupLogging(loggingLevel, logFile string) { 26 | level := log.DEBUG 27 | switch loggingLevel { 28 | case "info": 29 | level = log.INFO 30 | case "warn": 31 | level = log.WARNING 32 | case "error": 33 | level = log.ERROR 34 | } 35 | 36 | log.Global = make(map[string]*log.Filter) 37 | Global = log.Global 38 | if logFile == "stdout" || logFile == "" { 39 | flw := log.NewConsoleLogWriter() 40 | log.AddFilter("stdout", level, flw) 41 | } else if logFile == "stderr" || logFile == "" { 42 | flw := log.NewConsoleLogWriter() 43 | log.AddFilter("stderr", level, flw) 44 | } else { 45 | logFileDir := filepath.Dir(logFile) 46 | os.MkdirAll(logFileDir, 0744) 47 | 48 | flw := log.NewFileLogWriter(logFile, false) 49 | log.AddFilter("file", level, flw) 50 | 51 | flw.SetFormat("[%D %T] [%L] (%S) %M") 52 | flw.SetRotate(true) 53 | flw.SetRotateSize(0) 54 | flw.SetRotateLines(0) 55 | flw.SetRotateDaily(true) 56 | } 57 | 58 | Global.Info("Redirectoring logging to %s %s", logFile, level) 59 | } 60 | 61 | func Info(message string, args ...interface{}) { 62 | Global.Info(message, args...) 63 | } 64 | 65 | func Error(message string, args ...interface{}) { 66 | Global.Error(message, args...) 67 | } 68 | 69 | func Debug(message string, args ...interface{}) { 70 | Global.Debug(message, args...) 71 | } 72 | -------------------------------------------------------------------------------- /momonga/momonga.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | log "github.com/chobie/momonga/logger" 10 | "github.com/chobie/momonga/server" 11 | "github.com/chobie/momonga/util" 12 | "os" 13 | "path/filepath" 14 | "runtime" 15 | // "runtime/pprof" 16 | ) 17 | 18 | func main() { 19 | foreGround := flag.Bool("foreground", true, "run as foreground") 20 | configFile := flag.String("config", "config.toml", "the config file") 21 | 22 | flag.Parse() 23 | 24 | // f, _ := os.Create("profiler") 25 | // pprof.StartCPUProfile(f) 26 | // defer func() { 27 | // pprof.StopCPUProfile() 28 | // os.Exit(0) 29 | // }() 30 | 31 | if !*foreGround { 32 | err := util.Daemonize(0, 0) 33 | if err != 0 { 34 | log.Info("daemonize failed") 35 | os.Exit(-1) 36 | } 37 | } 38 | 39 | pid := os.Getpid() 40 | log.Info("Server pid: %d started", pid) 41 | 42 | confpath, _ := filepath.Abs(*configFile) 43 | runtime.GOMAXPROCS(runtime.NumCPU()) 44 | app := server.NewApplication(confpath) 45 | app.Start() 46 | app.Loop() 47 | 48 | log.Info("Server pid: %d finished", pid) 49 | } 50 | -------------------------------------------------------------------------------- /momonga_cli/momonga_cli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bufio" 9 | "code.google.com/p/go.net/websocket" 10 | "fmt" 11 | "github.com/chobie/momonga/client" 12 | codec "github.com/chobie/momonga/encoding/mqtt" 13 | "github.com/chobie/momonga/logger" 14 | "github.com/codegangsta/cli" 15 | "net" 16 | "os" 17 | ) 18 | 19 | func setupLog(ctx *cli.Context) { 20 | if ctx.Bool("v") { 21 | logger.SetupLogging("debug", "stderr") 22 | } else { 23 | logger.SetupLogging("info", "stderr") 24 | } 25 | } 26 | 27 | func getClient(ctx *cli.Context) *client.Client { 28 | opt := client.Option{ 29 | TransporterCallback: func() (net.Conn, error) { 30 | var conn net.Conn 31 | var err error 32 | 33 | if ctx.Bool("websocket") { 34 | origin := ctx.String("origin") 35 | conn, err = websocket.Dial(ctx.String("url"), "", origin) 36 | } else { 37 | conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", ctx.String("host"), ctx.Int("port"))) 38 | } 39 | return conn, err 40 | }, 41 | Keepalive: 0, 42 | Magic: []byte("MQTT"), 43 | Version: 4, 44 | } 45 | 46 | opt.UserName = ctx.String("u,user") 47 | opt.Password = ctx.String("P,password") 48 | 49 | return client.NewClient(opt) 50 | } 51 | 52 | func publish(ctx *cli.Context) { 53 | setupLog(ctx) 54 | c := getClient(ctx) 55 | 56 | qos := ctx.Int("q") 57 | topic := ctx.String("t") 58 | if topic == "" { 59 | fmt.Printf("Topic required\n") 60 | os.Exit(1) 61 | return 62 | } 63 | 64 | c.Connect() 65 | c.WaitConnection() 66 | //retain := c.Bool("r") 67 | 68 | if ctx.Bool("s") { 69 | // Read from Stdin 70 | scanner := bufio.NewScanner(os.Stdin) 71 | for scanner.Scan() { 72 | c.Publish(topic, []byte(scanner.Text()), qos) 73 | } 74 | } else { 75 | payload := ctx.String("m") 76 | c.PublishWait(topic, []byte(payload), qos) 77 | } 78 | } 79 | 80 | func subscribe(ctx *cli.Context) { 81 | setupLog(ctx) 82 | c := getClient(ctx) 83 | 84 | qos := ctx.Int("q") 85 | topic := ctx.String("t") 86 | 87 | if topic == "" { 88 | fmt.Printf("Topic required\n") 89 | return 90 | } 91 | 92 | c.Connect() 93 | c.WaitConnection() 94 | c.On("publish", func(message *codec.PublishMessage) { 95 | fmt.Printf("%s\t%s\n", message.TopicName, message.Payload) 96 | }) 97 | 98 | c.Subscribe(topic, qos) 99 | select {} 100 | } 101 | 102 | func main() { 103 | app := cli.NewApp() 104 | app.Name = "momonga_cli" 105 | app.Usage = `Usage momonga_cli -h host -p port 106 | subscribe path 107 | ` 108 | 109 | commonFlags := []cli.Flag{ 110 | cli.StringFlag{ 111 | Name: "host", 112 | Value: "localhost", 113 | Usage: "mqtt host to connect to. Defaults to localhost", 114 | EnvVar: "MQTT_HOST", 115 | }, 116 | cli.IntFlag{ 117 | Name: "p, port", 118 | Value: 1883, 119 | Usage: "network port to connect to. Defaults to 1883", 120 | EnvVar: "MQTT_PORT", 121 | }, 122 | cli.StringFlag{ 123 | Name: "u,user", 124 | Value: "", 125 | Usage: "provide a username", 126 | EnvVar: "MQTT_USERNAME", 127 | }, 128 | cli.StringFlag{ 129 | Name: "P,password", 130 | Value: "", 131 | Usage: "provide a password", 132 | EnvVar: "MQTT_PASSWORD", 133 | }, 134 | cli.StringFlag{"t", "", "mqtt topic to publish to.", ""}, 135 | cli.IntFlag{"q", 0, "QoS", ""}, 136 | cli.StringFlag{"cafile", "", "CA file", ""}, 137 | cli.StringFlag{"i", "", "ClientiId. Defaults random.", ""}, 138 | cli.StringFlag{"m", "test message", "Message body", ""}, 139 | cli.BoolFlag{"r", "message should be retained.", ""}, 140 | cli.BoolFlag{"d", "enable debug messages", ""}, 141 | cli.BoolFlag{"insecure", "do not check that the server certificate", ""}, 142 | cli.BoolFlag{"websocket", "use websocket", ""}, 143 | cli.StringFlag{"origin", "", "websocket origin", ""}, 144 | cli.StringFlag{"url", "", "websocket url (ws://localhost:8888/mqtt)", ""}, 145 | cli.BoolFlag{"v", "verbose flag.", ""}, 146 | } 147 | 148 | subFlags := commonFlags 149 | pubFlags := append(commonFlags, 150 | cli.BoolFlag{"s", "read message from stdin, sending line by line as a message", ""}, 151 | ) 152 | app.Action = func(c *cli.Context) { 153 | println(app.Usage) 154 | } 155 | 156 | app.Commands = []cli.Command{ 157 | { 158 | Name: "pub", 159 | Usage: "publish", 160 | Flags: pubFlags, 161 | Action: publish, 162 | }, 163 | { 164 | Name: "sub", 165 | Usage: "subscribe", 166 | Flags: subFlags, 167 | Action: subscribe, 168 | }, 169 | } 170 | 171 | app.Run(os.Args) 172 | } 173 | -------------------------------------------------------------------------------- /server/application.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | codec "github.com/chobie/momonga/encoding/mqtt" 8 | "github.com/chobie/momonga/client" 9 | "github.com/chobie/momonga/configuration" 10 | log "github.com/chobie/momonga/logger" 11 | "github.com/chobie/momonga/util" 12 | "io/ioutil" 13 | "os" 14 | "os/exec" 15 | "os/signal" 16 | "runtime/pprof" 17 | "strconv" 18 | "sync" 19 | "syscall" 20 | "time" 21 | "fmt" 22 | "net" 23 | ) 24 | 25 | // 26 | // Application manages start / stop, singnal handling and listeners. 27 | // 28 | // +-----------+ 29 | // |APPLICATION| start / stop, signal handling 30 | // +-----------+ 31 | // | LISTENER | listen and accept 32 | // +-----------+ 33 | // | HANDLER | parse and execute commands 34 | // +-----------+ 35 | // | ENGINE | implements commands api 36 | // +-----------+ 37 | // 38 | type Application struct { 39 | Engine *Momonga 40 | Servers []Server 41 | wg sync.WaitGroup 42 | configPath string 43 | config *configuration.Config 44 | execPath string 45 | workingDir string 46 | mu sync.Mutex 47 | } 48 | 49 | func NewApplication(configPath string) *Application { 50 | conf, err := configuration.LoadConfiguration(configPath) 51 | if err != nil { 52 | log.Error("Can't read config.toml. use default setting.: %s", err) 53 | } 54 | log.SetupLogging(conf.Server.LogLevel, conf.Server.LogFile) 55 | pid := strconv.Itoa(os.Getpid()) 56 | if conf.Server.PidFile != "" { 57 | if err := ioutil.WriteFile(conf.Server.PidFile, []byte(pid), 0644); err != nil { 58 | panic(err) 59 | } 60 | util.WritePid(conf.Server.PidFile) 61 | } 62 | 63 | // NOTE: INHERIT=TRUE means the process invoked from os.StartProess 64 | inherit := false 65 | if os.Getenv("INHERIT") == "TRUE" { 66 | inherit = true 67 | } 68 | 69 | log.Info("Momonga started pid: %s (inherit:%t)", pid, inherit) 70 | engine := NewMomonga(conf) 71 | app := &Application{ 72 | Engine: engine, 73 | Servers: []Server{}, 74 | configPath: configPath, 75 | config: conf, 76 | } 77 | 78 | // TODO: improve this block 79 | if conf.Server.Port > 0 { 80 | t := NewTcpServer(engine, conf, inherit) 81 | t.wg = &app.wg 82 | app.RegisterServer(t) 83 | } 84 | if conf.Server.Socket != "" { 85 | u := NewUnixServer(engine, conf, inherit) 86 | u.wg = &app.wg 87 | app.RegisterServer(u) 88 | } 89 | if conf.Server.HttpPort > 0 { 90 | h := NewHttpServer(engine, conf, inherit) 91 | h.wg = &app.wg 92 | app.RegisterServer(h) 93 | } 94 | 95 | if conf.Server.EnableTls && conf.Server.TlsPort > 0 { 96 | h := NewTlsServer(engine, conf, inherit) 97 | h.wg = &app.wg 98 | app.RegisterServer(h) 99 | } 100 | 101 | // memonized application path 102 | app.execPath, err = exec.LookPath(os.Args[0]) 103 | if err != nil { 104 | log.Error("Error: %s", err) 105 | return app 106 | } 107 | app.workingDir, err = os.Getwd() 108 | if err != nil { 109 | log.Error("Error: %s", err) 110 | return app 111 | } 112 | 113 | if conf.Bridge.Address != "" { 114 | //TODO: Bridgeは複数指定できる 115 | //TODO: Bridgeは途中で制御できる 116 | // /api/bridge/list 117 | // /api/bridge/connection/stop 118 | // /api/bridge/connection/status 119 | // /api/bridge/connection/start 120 | // /api/bridge/connection/delete 121 | // /api/bridge/connection/new?address=&port=&type=both&topic[]= 122 | // /api/bridge/config 123 | go func() { 124 | flag := 0 125 | switch conf.Bridge.Type { 126 | case "both": 127 | flag = 3 128 | case "out": 129 | flag = 1 130 | case "in": 131 | flag = 2 132 | default: 133 | panic(fmt.Sprintf("%s does not support.", conf.Bridge.Type)) 134 | } 135 | 136 | // in 137 | addr := fmt.Sprintf("%s:%d", conf.Bridge.Address, conf.Bridge.Port) 138 | c := client.NewClient(client.Option{ 139 | TransporterCallback: func() (net.Conn, error) { 140 | conn, err := net.Dial("tcp", addr) 141 | return conn, err 142 | }, 143 | Identifier: fmt.Sprintf(conf.Bridge.ClientId), 144 | Magic: []byte("MQTT"), 145 | Version: 4 | 0x80, 146 | Keepalive: 0, 147 | }) 148 | 149 | c.Connect() 150 | c.WaitConnection() 151 | if flag == 1 || flag == 3 { 152 | c.Subscribe("#", 2) 153 | c.SetRequestPerSecondLimit(-1) 154 | c.On("publish", func(msg *codec.PublishMessage) { 155 | engine.SendPublishMessage(msg, conf.Bridge.Connection, true) 156 | }) 157 | } 158 | 159 | //out 160 | if flag == 2 || flag == 3 { 161 | addr2 := fmt.Sprintf("%s:%d", conf.Server.BindAddress, conf.Server.Port) 162 | c2 := client.NewClient(client.Option{ 163 | TransporterCallback: func() (net.Conn, error) { 164 | conn, err := net.Dial("tcp", addr2) 165 | return conn, err 166 | }, 167 | Identifier: fmt.Sprintf(conf.Bridge.ClientId), 168 | Magic: []byte("MQTT"), 169 | Version: 4 | 0x80, 170 | Keepalive: 0, 171 | }) 172 | 173 | c2.Connect() 174 | c2.WaitConnection() 175 | c2.Subscribe("#", 2) 176 | c2.SetRequestPerSecondLimit(-1) 177 | c2.On("publish", func(msg *codec.PublishMessage) { 178 | c.Publish(msg.TopicName, msg.Payload, msg.QosLevel) 179 | }) 180 | } 181 | select{} 182 | }() 183 | } 184 | 185 | return app 186 | } 187 | 188 | func (self *Application) Start() { 189 | self.wg.Add(1) 190 | 191 | ch := make(chan os.Signal, 8) 192 | // TODO: windows can't use signal. split this block into another file. 193 | signals := []os.Signal{syscall.SIGINT, syscall.SIGHUP, syscall.SIGUSR2, syscall.SIGQUIT} 194 | signal.Notify(ch, signals...) 195 | 196 | go func(ch chan os.Signal) { 197 | for { 198 | select { 199 | case x := <-ch: 200 | switch x { 201 | case syscall.SIGINT: 202 | self.Stop() 203 | case syscall.SIGQUIT: 204 | // TODO: like sigdump feature. should change file descriptor 205 | pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) 206 | pprof.Lookup("heap").WriteTo(os.Stdout, 1) 207 | pprof.Lookup("block").WriteTo(os.Stdout, 1) 208 | 209 | case syscall.SIGHUP: 210 | // reload config 211 | log.Info("reload configuration from %s", self.configPath) 212 | configuration.LoadConfigurationTo(self.configPath, self.config) 213 | 214 | case syscall.SIGUSR2: 215 | // graceful restart 216 | self.mu.Lock() 217 | var env []string 218 | for _, v := range os.Environ() { 219 | env = append(env, v) 220 | } 221 | 222 | discriptors := append([]*os.File{ 223 | os.Stdin, 224 | os.Stdout, 225 | os.Stderr, 226 | }) 227 | 228 | for i := 0; i < len(self.Servers); i++ { 229 | svr := self.Servers[i] 230 | f, e := svr.Listener().File() 231 | 232 | if e != nil { 233 | log.Error("Error: %s", e) 234 | self.mu.Unlock() 235 | continue 236 | } 237 | 238 | fd, _ := syscall.Dup(int(f.Fd())) 239 | fx := os.NewFile(uintptr(fd), "sock") 240 | discriptors = append(discriptors, []*os.File{fx}...) 241 | } 242 | 243 | env = append(env, "INHERIT=TRUE") 244 | p, err := os.StartProcess(self.execPath, os.Args, &os.ProcAttr{ 245 | Dir: self.workingDir, 246 | Env: env, 247 | Files: discriptors, 248 | }) 249 | 250 | if err != nil { 251 | log.Error("Error: %s, stop gracefull restart.", err) 252 | p.Kill() 253 | self.mu.Unlock() 254 | continue 255 | } 256 | 257 | // maybe, new server is alive in 3 seconds 258 | time.Sleep(time.Second * 3) 259 | for i := 0; i < len(self.Servers); i++ { 260 | svr := self.Servers[i] 261 | svr.Graceful() 262 | } 263 | self.wg.Done() 264 | 265 | // Kill current connection in N seconds. 266 | self.Engine.Doom() 267 | self.mu.Unlock() 268 | return 269 | } 270 | } 271 | } 272 | }(ch) 273 | 274 | go self.Engine.Run() 275 | for i := 0; i < len(self.Servers); i++ { 276 | svr := self.Servers[i] 277 | self.wg.Add(1) 278 | go svr.ListenAndServe() 279 | } 280 | } 281 | 282 | func (self *Application) Stop() { 283 | for i := 0; i < len(self.Servers); i++ { 284 | svr := self.Servers[i] 285 | svr.Stop() 286 | } 287 | self.Engine.Terminate() 288 | 289 | self.wg.Done() 290 | } 291 | 292 | func (self *Application) Loop() { 293 | self.wg.Wait() 294 | } 295 | 296 | func (self *Application) RegisterServer(svr Server) { 297 | self.Servers = append(self.Servers, svr) 298 | } 299 | -------------------------------------------------------------------------------- /server/authenticator.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "github.com/chobie/momonga/configuration" 4 | 5 | //type User struct { 6 | // Name []byte 7 | //} 8 | 9 | type Authenticator interface { 10 | Init(config *configuration.Config) 11 | Authenticate(user_id, password []byte) (bool, error) 12 | Shutdown() 13 | } 14 | -------------------------------------------------------------------------------- /server/authenticator_empty.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/chobie/momonga/configuration" 5 | ) 6 | 7 | // EmptyAuthenticator allows anything. 8 | type EmptyAuthenticator struct { 9 | } 10 | 11 | func (self *EmptyAuthenticator) Init(config *configuration.Config) { 12 | } 13 | 14 | func (self *EmptyAuthenticator) Authenticate(user_id, password []byte) (bool, error) { 15 | return true, nil 16 | } 17 | 18 | func (self *EmptyAuthenticator) Shutdown() { 19 | } 20 | -------------------------------------------------------------------------------- /server/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | const KILOBYTE = 1024 7 | const MEGABYTE = 1024 * KILOBYTE 8 | const MAX_REQUEST_SIZE = MEGABYTE * 2 9 | -------------------------------------------------------------------------------- /server/dummyplug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "fmt" 8 | . "github.com/chobie/momonga/common" 9 | "github.com/chobie/momonga/encoding/mqtt" 10 | log "github.com/chobie/momonga/logger" 11 | "github.com/chobie/momonga/util" 12 | "sync" 13 | ) 14 | 15 | type DummyPlug struct { 16 | Identity string 17 | Switch chan bool 18 | message chan mqtt.Message 19 | Running bool 20 | stop chan bool 21 | mutex sync.RWMutex 22 | engine *Momonga 23 | } 24 | 25 | func NewDummyPlug(engine *Momonga) *DummyPlug { 26 | d := &DummyPlug{ 27 | Identity: "dummy", 28 | Switch: make(chan bool), 29 | message: make(chan mqtt.Message, 256), 30 | stop: make(chan bool, 1), 31 | engine: engine, 32 | } 33 | 34 | go d.Run() 35 | return d 36 | } 37 | 38 | func (self *DummyPlug) Stop() { 39 | self.stop <- true 40 | } 41 | 42 | func (self *DummyPlug) Run() { 43 | for { 44 | select { 45 | case <-self.stop: 46 | return 47 | case b := <-self.Switch: 48 | self.Running = b 49 | case m := <-self.message: 50 | if self.Running { 51 | // メッセージが来たらengineのAPIをたたけばOK 52 | switch m.GetType() { 53 | case mqtt.PACKET_TYPE_CONNECT: 54 | case mqtt.PACKET_TYPE_CONNACK: 55 | case mqtt.PACKET_TYPE_PUBLISH: 56 | // TODO: 57 | if p, ok := m.(*mqtt.PublishMessage); ok { 58 | fmt.Printf("%s\n", p) 59 | switch p.QosLevel { 60 | case 1: 61 | log.Debug("[DUMMY] Received Publish Message from [%d]. reply puback", p.PacketIdentifier) 62 | self.engine.OutGoingTable.Unref(p.PacketIdentifier) 63 | case 2: 64 | log.Debug("[DUMMY] Received Publish Message from [%d]. reply pubrec", p.PacketIdentifier) 65 | self.engine.OutGoingTable.Unref(p.PacketIdentifier) 66 | rel := mqtt.NewPubrelMessage() 67 | rel.PacketIdentifier = p.PacketIdentifier 68 | // NOTE: client have to reply pubrec to the server 69 | self.message <- rel 70 | } 71 | } 72 | case mqtt.PACKET_TYPE_DISCONNECT: 73 | // TODO: なの? 74 | case mqtt.PACKET_TYPE_SUBSCRIBE: 75 | case mqtt.PACKET_TYPE_SUBACK: 76 | case mqtt.PACKET_TYPE_UNSUBSCRIBE: 77 | case mqtt.PACKET_TYPE_UNSUBACK: 78 | case mqtt.PACKET_TYPE_PINGRESP: 79 | case mqtt.PACKET_TYPE_PINGREQ: 80 | case mqtt.PACKET_TYPE_PUBACK: 81 | // TODO: (nothign to do) 82 | case mqtt.PACKET_TYPE_PUBREC: 83 | // TODO: (nothign to do) 84 | case mqtt.PACKET_TYPE_PUBREL: 85 | // TODO: 86 | if p, ok := m.(*mqtt.PubrelMessage); ok { 87 | log.Debug("[DUMMY] Received Pubrel Message from [%d]. send pubcomp", p.PacketIdentifier) 88 | self.engine.OutGoingTable.Unref(p.PacketIdentifier) 89 | 90 | cmp := mqtt.NewPubcompMessage() 91 | cmp.PacketIdentifier = p.PacketIdentifier 92 | // NOTE: client have to reply pubcomp to the server 93 | self.message <- cmp 94 | } 95 | case mqtt.PACKET_TYPE_PUBCOMP: 96 | // TODO: (nothing) 97 | default: 98 | return 99 | } 100 | } else { 101 | // discards message 102 | } 103 | } 104 | } 105 | } 106 | 107 | func (self *DummyPlug) WriteMessageQueue(request mqtt.Message) { 108 | switch request.GetType() { 109 | case mqtt.PACKET_TYPE_CONNECT: 110 | case mqtt.PACKET_TYPE_CONNACK: 111 | case mqtt.PACKET_TYPE_PUBLISH: 112 | self.message <- request 113 | case mqtt.PACKET_TYPE_DISCONNECT: 114 | self.message <- request 115 | case mqtt.PACKET_TYPE_SUBSCRIBE: 116 | case mqtt.PACKET_TYPE_SUBACK: 117 | case mqtt.PACKET_TYPE_UNSUBSCRIBE: 118 | case mqtt.PACKET_TYPE_UNSUBACK: 119 | case mqtt.PACKET_TYPE_PINGRESP: 120 | case mqtt.PACKET_TYPE_PINGREQ: 121 | case mqtt.PACKET_TYPE_PUBACK: 122 | self.message <- request 123 | case mqtt.PACKET_TYPE_PUBREC: 124 | self.message <- request 125 | case mqtt.PACKET_TYPE_PUBREL: 126 | self.message <- request 127 | case mqtt.PACKET_TYPE_PUBCOMP: 128 | self.message <- request 129 | default: 130 | return 131 | } 132 | } 133 | 134 | func (self *DummyPlug) WriteMessageQueue2(msg []byte) { 135 | return 136 | } 137 | 138 | func (self *DummyPlug) Close() error { 139 | self.Stop() 140 | return nil 141 | } 142 | 143 | func (self *DummyPlug) SetState(State) { 144 | } 145 | 146 | func (self *DummyPlug) GetState() State { 147 | return STATE_CONNECTED 148 | } 149 | 150 | func (self *DummyPlug) ResetState() { 151 | } 152 | 153 | func (self *DummyPlug) ReadMessage() (mqtt.Message, error) { 154 | return nil, nil 155 | } 156 | 157 | func (self *DummyPlug) IsAlived() bool { 158 | return true 159 | } 160 | 161 | func (self *DummyPlug) SetWillMessage(mqtt.WillMessage) { 162 | panic("strange state") 163 | } 164 | 165 | func (self *DummyPlug) GetWillMessage() *mqtt.WillMessage { 166 | panic("strange state") 167 | return nil 168 | } 169 | 170 | func (self *DummyPlug) HasWillMessage() bool { 171 | panic("strange state") 172 | return false 173 | } 174 | 175 | func (self *DummyPlug) GetOutGoingTable() *util.MessageTable { 176 | return nil 177 | } 178 | 179 | func (self *DummyPlug) GetSubscribedTopics() map[string]*SubscribeSet { 180 | panic("strange state") 181 | return nil 182 | } 183 | 184 | func (self *DummyPlug) AppendSubscribedTopic(string, *SubscribeSet) { 185 | panic("strange state") 186 | return 187 | } 188 | func (self *DummyPlug) RemoveSubscribedTopic(string) { 189 | panic("strange state") 190 | return 191 | } 192 | 193 | func (self *DummyPlug) SetKeepaliveInterval(int) { 194 | return 195 | } 196 | 197 | func (self *DummyPlug) GetId() string { 198 | return self.Identity 199 | } 200 | func (self *DummyPlug) GetRealId() string { 201 | return self.Identity 202 | } 203 | 204 | func (self *DummyPlug) SetId(id string) { 205 | self.Identity = id 206 | } 207 | 208 | func (self *DummyPlug) DisableCleanSession() { 209 | return 210 | } 211 | 212 | func (self *DummyPlug) ShouldCleanSession() bool { 213 | return false 214 | } 215 | 216 | func (self *DummyPlug) IsBridge() bool { 217 | return false 218 | } 219 | -------------------------------------------------------------------------------- /server/dummyplug_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | . "github.com/chobie/momonga/common" 5 | "github.com/chobie/momonga/encoding/mqtt" 6 | . "gopkg.in/check.v1" 7 | ) 8 | 9 | type DummyPlugSuite struct{} 10 | 11 | var _ = Suite(&DummyPlugSuite{}) 12 | 13 | func (s *DummyPlugSuite) TestDummyPlug(c *C) { 14 | engine := CreateEngine() 15 | // 2) Running Engine 16 | go engine.Run() 17 | 18 | p := NewDummyPlug(engine) 19 | p.Switch <- true 20 | mux := NewMmuxConnection() 21 | 22 | p.SetState(STATE_CONNECTED) 23 | mux.DisableCleanSession() 24 | mux.SetId(p.GetId()) 25 | mux.Attach(p) 26 | // Memo: Normally, engine has correct relation ship between mux and iteself. this is only need for test 27 | engine.Connections[p.GetId()] = mux 28 | 29 | sub := mqtt.NewSubscribeMessage() 30 | sub.Payload = append(sub.Payload, mqtt.SubscribePayload{ 31 | TopicPath: "/debug/1", 32 | RequestedQos: uint8(1), 33 | }) 34 | sub.Payload = append(sub.Payload, mqtt.SubscribePayload{ 35 | TopicPath: "/debug/2", 36 | RequestedQos: uint8(2), 37 | }) 38 | 39 | sub.PacketIdentifier = 1 40 | engine.Subscribe(sub, mux) 41 | 42 | pub := mqtt.NewPublishMessage() 43 | pub.TopicName = "/debug/1" 44 | pub.Payload = []byte("hello") 45 | pub.QosLevel = 1 46 | engine.SendPublishMessage(pub, "dummy", false) 47 | 48 | pub = mqtt.NewPublishMessage() 49 | pub.TopicName = "/debug/2" 50 | pub.Payload = []byte("hello") 51 | pub.QosLevel = 2 52 | 53 | engine.SendPublishMessage(pub, "dummy", false) 54 | 55 | // TODO: How do i test this? 56 | } 57 | -------------------------------------------------------------------------------- /server/engine_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | . "github.com/chobie/momonga/common" 6 | "github.com/chobie/momonga/configuration" 7 | codec "github.com/chobie/momonga/encoding/mqtt" 8 | log "github.com/chobie/momonga/logger" 9 | . "gopkg.in/check.v1" 10 | "net" 11 | "os" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | // mock 17 | type MockConnection struct { 18 | bytes.Buffer 19 | Closed bool 20 | Local mockAddr 21 | Remote mockAddr 22 | Type int 23 | } 24 | 25 | type mockAddr struct { 26 | } 27 | 28 | func (m *mockAddr) Network() string { 29 | return "debug" 30 | } 31 | 32 | func (m *mockAddr) String() string { 33 | return "debug" 34 | } 35 | 36 | func (m *MockConnection) Close() error { 37 | m.Closed = true 38 | return nil 39 | } 40 | 41 | func (m *MockConnection) LocalAddr() net.Addr { 42 | return &m.Local 43 | } 44 | 45 | func (m *MockConnection) RemoteAddr() net.Addr { 46 | return &m.Remote 47 | } 48 | 49 | func (m *MockConnection) SetDeadline(t time.Time) error { 50 | return nil 51 | } 52 | 53 | func (m *MockConnection) SetReadDeadline(t time.Time) error { 54 | return nil 55 | } 56 | 57 | func (m *MockConnection) SetWriteDeadline(t time.Time) error { 58 | return nil 59 | } 60 | 61 | func CreateEngine() *Momonga { 62 | return NewMomonga(configuration.DefaultConfiguration()) 63 | } 64 | 65 | func Test(t *testing.T) { TestingT(t) } 66 | 67 | type EngineSuite struct{} 68 | 69 | var _ = Suite(&EngineSuite{}) 70 | 71 | func (s *EngineSuite) SetUpSuite(c *C) { 72 | os.Remove("/Users/chobie/src/momonga/socket") 73 | } 74 | 75 | func (s *EngineSuite) TearDownSuite(c *C) { 76 | } 77 | 78 | func (s *EngineSuite) TestBasic(c *C) { 79 | log.SetupLogging("error", "stdout") 80 | 81 | // This test introduce how to setup custom MQTT server 82 | 83 | // 1) You need to setup Momonga engine like this. 84 | engine := CreateEngine() 85 | // 2) Running Engine 86 | go engine.Run() 87 | 88 | // 3) engine expects net.Conn (see MockConnection) 89 | mock := &MockConnection{} 90 | conn := NewMyConnection(nil) 91 | conn.SetMyConnection(mock) 92 | conn.SetId("debug") 93 | 94 | // 4) setup handler. This handler implementation is example. 95 | // you can customize behaviour with On method. 96 | hndr := NewHandler(conn, engine) 97 | conn.SetOpaque(hndr) 98 | 99 | // 5) Now, 100 | msg := codec.NewConnectMessage() 101 | msg.Magic = []byte("MQTT") 102 | msg.Version = uint8(4) 103 | msg.Identifier = "debug" 104 | msg.CleanSession = true 105 | msg.KeepAlive = uint16(0) // Ping is annoyed at this time 106 | 107 | codec.WriteMessageTo(msg, mock) 108 | 109 | // 6) just call conn.ParseMessage(). then handler will work. 110 | r, err := conn.ParseMessage() 111 | 112 | c.Assert(err, Equals, nil) 113 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNECT) 114 | 115 | // NOTE: Client turn. don't care this. 116 | time.Sleep(time.Millisecond) 117 | r, err = conn.ParseMessage() 118 | c.Assert(err, Equals, nil) 119 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNACK) 120 | 121 | // Subscribe 122 | sub := codec.NewSubscribeMessage() 123 | sub.Payload = append(sub.Payload, codec.SubscribePayload{TopicPath: "/debug"}) 124 | sub.PacketIdentifier = 1 125 | 126 | codec.WriteMessageTo(sub, mock) 127 | 128 | // (Server) 129 | r, err = conn.ParseMessage() 130 | c.Assert(err, Equals, nil) 131 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_SUBSCRIBE) 132 | rsub := r.(*codec.SubscribeMessage) 133 | c.Assert(rsub.PacketIdentifier, Equals, uint16(1)) 134 | c.Assert(rsub.Payload[0].TopicPath, Equals, "/debug") 135 | 136 | // (Client) suback 137 | time.Sleep(time.Millisecond) 138 | r, err = conn.ParseMessage() 139 | c.Assert(err, Equals, nil) 140 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_SUBACK) 141 | 142 | // (Client) Publish 143 | pub := codec.NewPublishMessage() 144 | pub.TopicName = "/debug" 145 | pub.Payload = []byte("hello") 146 | codec.WriteMessageTo(pub, mock) 147 | 148 | // (Server) 149 | r, err = conn.ParseMessage() 150 | c.Assert(err, Equals, nil) 151 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_PUBLISH) 152 | rpub := r.(*codec.PublishMessage) 153 | c.Assert(rpub.TopicName, Equals, "/debug") 154 | 155 | // (Client) received publish message 156 | time.Sleep(time.Millisecond) 157 | r, err = conn.ParseMessage() 158 | rp := r.(*codec.PublishMessage) 159 | c.Assert(err, Equals, nil) 160 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_PUBLISH) 161 | c.Assert(rp.TopicName, Equals, "/debug") 162 | c.Assert(string(rp.Payload), Equals, "hello") 163 | 164 | // okay, now disconnect from server 165 | dis := codec.NewDisconnectMessage() 166 | codec.WriteMessageTo(dis, mock) 167 | 168 | r, err = conn.ParseMessage() 169 | c.Assert(err, Equals, nil) 170 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_DISCONNECT) 171 | 172 | // then, receive EOF from server. 173 | time.Sleep(time.Millisecond) 174 | r, err = conn.ParseMessage() 175 | c.Assert(err, Equals, nil) 176 | 177 | // That's it. 178 | engine.Terminate() 179 | } 180 | 181 | func (s *EngineSuite) BenchmarkSimple(c *C) { 182 | log.SetupLogging("error", "stdout") 183 | 184 | engine := CreateEngine() 185 | go engine.Run() 186 | 187 | mock := &MockConnection{} 188 | conn := NewMyConnection(nil) 189 | conn.SetMyConnection(mock) 190 | conn.SetId("debug") 191 | 192 | hndr := NewHandler(conn, engine) 193 | conn.SetOpaque(hndr) 194 | 195 | msg := codec.NewConnectMessage() 196 | msg.Magic = []byte("MQTT") 197 | msg.Version = uint8(4) 198 | msg.Identifier = "debug" 199 | msg.CleanSession = true 200 | msg.KeepAlive = uint16(0) // Ping is annoyed at this time 201 | codec.WriteMessageTo(msg, mock) 202 | 203 | // 6) just call conn.ParseMessage(). then handler will work. 204 | r, err := conn.ParseMessage() 205 | 206 | c.Assert(err, Equals, nil) 207 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNECT) 208 | 209 | // NOTE: Client turn. don't care this. 210 | time.Sleep(time.Millisecond) 211 | r, err = conn.ParseMessage() 212 | c.Assert(err, Equals, nil) 213 | c.Assert(r.GetType(), Equals, codec.PACKET_TYPE_CONNACK) 214 | 215 | for i := 0; i < c.N; i++ { 216 | // (Client) Publish 217 | pub := codec.NewPublishMessage() 218 | pub.TopicName = "/debug" 219 | pub.Payload = []byte("hello") 220 | codec.WriteMessageTo(pub, mock) 221 | 222 | // (Server) 223 | conn.ParseMessage() 224 | } 225 | 226 | // That's it. 227 | engine.Terminate() 228 | } 229 | -------------------------------------------------------------------------------- /server/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | . "github.com/chobie/momonga/common" 9 | codec "github.com/chobie/momonga/encoding/mqtt" 10 | log "github.com/chobie/momonga/logger" 11 | ) 12 | 13 | // Handler dispatches messages which sent by client. 14 | // this struct will be use client library soon. 15 | // 16 | // とかいいつつ、ackとかはhandlerで返してねーとか立ち位置分かりづらい 17 | // Engine側でMQTTの基本機能を全部やれればいいんだけど、そうすると 18 | // client library別にしないと無理なんだよなー。 19 | // 目指すところとしては、基本部分はデフォルトのHandlerで動くから 20 | // それで動かないところだけうわがいてね!って所。 21 | // Handler自体は受け渡ししかやらんのでlockしなくて大丈夫なはず 22 | type Handler struct { 23 | Engine *Momonga 24 | Connection Connection 25 | } 26 | 27 | func NewHandler(conn Connection, engine *Momonga) *Handler { 28 | hndr := &Handler{ 29 | Engine: engine, 30 | Connection: conn, 31 | } 32 | 33 | if cn, ok := conn.(*MyConnection); ok { 34 | // Defaultの動作ではなんともいえないから上書きが必要なもの 35 | cn.On("parsed", hndr.Parsed, true) 36 | cn.On("connect", hndr.HandshakeInternal, true) 37 | cn.On("disconnect", hndr.Disconnect, true) 38 | 39 | cn.On("publish", hndr.Publish, true) 40 | cn.On("subscribe", hndr.Subscribe, true) 41 | cn.On("unsubscribe", hndr.Unsubscribe, true) 42 | 43 | cn.On("pingreq", hndr.Pingreq, true) 44 | 45 | // Defaultの動作で大丈夫なもの(念のため) 46 | cn.On("puback", hndr.Puback, true) 47 | cn.On("pubrec", hndr.Pubrec, true) 48 | cn.On("pubrel", hndr.Pubrel, true) 49 | cn.On("pubcomp", hndr.Pubcomp, true) 50 | } 51 | 52 | return hndr 53 | } 54 | 55 | func (self *Handler) Close() { 56 | self.Engine = nil 57 | self.Connection = nil 58 | } 59 | 60 | func (self *Handler) Parsed() { 61 | Metrics.System.Broker.Messages.Received.Add(1) 62 | } 63 | 64 | func (self *Handler) Pubcomp(messageId uint16) { 65 | //pubcompを受け取る、ということはserverがsender 66 | log.Debug("Received Pubcomp Message from %s", self.Connection.GetId()) 67 | 68 | self.Engine.OutGoingTable.Unref(messageId) 69 | self.Connection.GetOutGoingTable().Unref(messageId) 70 | } 71 | 72 | func (self *Handler) Pubrel(messageId uint16) { 73 | ack := codec.NewPubcompMessage() 74 | ack.PacketIdentifier = messageId 75 | self.Connection.WriteMessageQueue(ack) 76 | log.Debug("Send pubcomp message to sender. [%s: %d]", self.Connection.GetId(), messageId) 77 | } 78 | 79 | func (self *Handler) Pubrec(messageId uint16) { 80 | ack := codec.NewPubrelMessage() 81 | ack.PacketIdentifier = messageId 82 | 83 | self.Connection.WriteMessageQueue(ack) 84 | self.Connection.GetOutGoingTable().Unref(messageId) 85 | } 86 | 87 | func (self *Handler) Puback(messageId uint16) { 88 | log.Debug("Received Puback Message from [%s: %d]", self.Connection.GetId(), messageId) 89 | 90 | if tbl, ok := self.Engine.InflightTable[self.Connection.GetId()]; ok { 91 | p, _ := tbl.Get(messageId) 92 | if msg, ok := p.(*codec.PublishMessage); ok { 93 | // TODO: やっぱclose済みのチャンネルにおくっちゃうよねー 94 | msg.Opaque.(chan string) <- self.Connection.GetId() 95 | } 96 | if t, ok := self.Engine.InflightTable[self.Connection.GetId()]; ok { 97 | t.Unref(messageId) 98 | } 99 | } 100 | 101 | // TODO: これのIDは内部的なの? 102 | self.Engine.OutGoingTable.Unref(messageId) 103 | self.Connection.GetOutGoingTable().Unref(messageId) 104 | } 105 | 106 | func (self *Handler) Unsubscribe(messageId uint16, granted int, payloads []codec.SubscribePayload) { 107 | log.Debug("Received unsubscribe from [%s]: %s\n", self.Connection.GetId(), messageId) 108 | self.Engine.Unsubscribe(messageId, granted, payloads, self.Connection) 109 | } 110 | 111 | func (self *Handler) Disconnect() { 112 | log.Debug("Received disconnect from %s", self.Connection.GetId()) 113 | if cn, ok := self.Connection.(*MyConnection); ok { 114 | cn.Disconnect() 115 | } 116 | 117 | Metrics.System.Broker.Clients.Connected.Add(-1) 118 | //return &DisconnectError{} 119 | } 120 | 121 | func (self *Handler) Pingreq() { 122 | r := codec.NewPingrespMessage() 123 | self.Connection.WriteMessageQueue(r) 124 | } 125 | 126 | func (self *Handler) Publish(p *codec.PublishMessage) { 127 | //log.Info("Received Publish Message: %s: %+v", p.PacketIdentifier, p) 128 | conn := self.Connection 129 | 130 | // TODO: check permission. 131 | 132 | // TODO: この部分はengine側にあるべき機能なので治す(というか下のconnectionがやる所?) 133 | if p.QosLevel == 1 { 134 | ack := codec.NewPubackMessage() 135 | ack.PacketIdentifier = p.PacketIdentifier 136 | conn.WriteMessageQueue(ack) 137 | log.Debug("Send puback message to sender. [%s: %d]", conn.GetId(), ack.PacketIdentifier) 138 | } else if p.QosLevel == 2 { 139 | ack := codec.NewPubrecMessage() 140 | ack.PacketIdentifier = p.PacketIdentifier 141 | conn.WriteMessageQueue(ack) 142 | log.Debug("Send pubrec message to sender. [%s: %d]", conn.GetId(), ack.PacketIdentifier) 143 | } 144 | 145 | // TODO: QoSによっては適切なMessageIDを追加する 146 | // Server / ClientはそれぞれMessageTableが違う 147 | if p.QosLevel > 0 { 148 | // TODO: と、いうことはメッセージの deep コピーが簡単にできるようにしないとだめ 149 | // 色々考えると面倒だけど、ひとまずはフルコピーでやっとこう 150 | // id := conn.GetOutGoingTable().NewId() 151 | // p.PacketIdentifier = id 152 | conn.GetOutGoingTable().Register(p.PacketIdentifier, p, conn) 153 | p.Opaque = conn 154 | } 155 | 156 | // NOTE: We don't block here. currently use goroutine but should pass message to background worker. 157 | go self.Engine.SendPublishMessage(p, conn.GetId(), conn.IsBridge()) 158 | } 159 | 160 | func (self *Handler) Subscribe(p *codec.SubscribeMessage) { 161 | self.Engine.Subscribe(p, self.Connection) 162 | } 163 | 164 | func (self *Handler) HandshakeInternal(p *codec.ConnectMessage) { 165 | var conn *MyConnection 166 | var ok bool 167 | 168 | if conn, ok = self.Connection.(*MyConnection); !ok { 169 | log.Debug("wrong sequence.") 170 | self.Connection.Close() 171 | return 172 | } 173 | 174 | mux := self.Engine.Handshake(p, conn) 175 | if mux != nil { 176 | self.Connection = mux 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /server/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "code.google.com/p/go.net/websocket" 8 | "expvar" 9 | "fmt" 10 | "github.com/BurntSushi/toml" 11 | . "github.com/chobie/momonga/common" 12 | "github.com/chobie/momonga/util" 13 | "io" 14 | "io/ioutil" 15 | "net/http" 16 | httpprof "net/http/pprof" 17 | "net/url" 18 | "runtime" 19 | "strconv" 20 | //log "github.com/chobie/momonga/logger" 21 | ) 22 | 23 | func init() { 24 | runtime.SetBlockProfileRate(1) 25 | } 26 | 27 | type MyHttpServer struct { 28 | Engine *Momonga 29 | WebSocketMount string 30 | } 31 | 32 | func (self *MyHttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 33 | err := self.apiRouter(w, req) 34 | if err == nil { 35 | return 36 | } 37 | 38 | err = self.debugRouter(w, req) 39 | if err == nil { 40 | return 41 | } 42 | } 43 | 44 | func (self *MyHttpServer) debugRouter(w http.ResponseWriter, req *http.Request) error { 45 | switch req.URL.Path { 46 | case "/debug/vars": 47 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 48 | fmt.Fprintf(w, "{\n") 49 | first := true 50 | expvar.Do(func(kv expvar.KeyValue) { 51 | if kv.Key == "cmdline" || kv.Key == "memstats" { 52 | return 53 | } 54 | if !first { 55 | fmt.Fprintf(w, ",\n") 56 | } 57 | first = false 58 | fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) 59 | }) 60 | fmt.Fprintf(w, "\n}\n") 61 | case "/debug/pprof": 62 | httpprof.Index(w, req) 63 | case "/debug/pprof/cmdline": 64 | httpprof.Cmdline(w, req) 65 | case "/debug/pprof/symbol": 66 | httpprof.Symbol(w, req) 67 | case "/debug/pprof/heap": 68 | httpprof.Handler("heap").ServeHTTP(w, req) 69 | case "/debug/pprof/goroutine": 70 | httpprof.Handler("goroutine").ServeHTTP(w, req) 71 | case "/debug/pprof/profile": 72 | httpprof.Profile(w, req) 73 | case "/debug/pprof/block": 74 | httpprof.Handler("block").ServeHTTP(w, req) 75 | case "/debug/pprof/threadcreate": 76 | httpprof.Handler("threadcreate").ServeHTTP(w, req) 77 | case "/debug/retain": 78 | itr := self.Engine.DataStore.Iterator() 79 | for ; itr.Valid(); itr.Next() { 80 | k := string(itr.Key()) 81 | fmt.Fprintf(w, "
key: %s
", k) 82 | } 83 | case "/debug/retain/clear": 84 | itr := self.Engine.DataStore.Iterator() 85 | var targets []string 86 | for ; itr.Valid(); itr.Next() { 87 | x := itr.Key() 88 | targets = append(targets, string(x)) 89 | } 90 | for _, s := range targets { 91 | self.Engine.DataStore.Del([]byte(s), []byte(s)) 92 | } 93 | fmt.Fprintf(w, "", self.Engine.DataStore) 94 | case "/debug/connections": 95 | for _, v := range self.Engine.Connections { 96 | fmt.Fprintf(w, "
%#v
", v) 97 | } 98 | case "/debug/connections/clear": 99 | self.Engine.Connections = make(map[uint32]map[string]*MmuxConnection) 100 | fmt.Fprintf(w, "cleared") 101 | case "/debug/qlobber/clear": 102 | self.Engine.TopicMatcher = util.NewQlobber() 103 | fmt.Fprintf(w, "cleared") 104 | case "/debug/qlobber/dump": 105 | fmt.Fprintf(w, "qlobber:\n") 106 | self.Engine.TopicMatcher.Dump(w) 107 | case "/debug/config/dump": 108 | e := toml.NewEncoder(w) 109 | e.Encode(self.Engine.Config()) 110 | 111 | default: 112 | return fmt.Errorf("404 %s", req.URL.Path) 113 | } 114 | return nil 115 | } 116 | 117 | func (self *MyHttpServer) apiRouter(w http.ResponseWriter, req *http.Request) error { 118 | switch req.URL.Path { 119 | case "/": 120 | fmt.Fprintf(w, "HELO MOMONGA WORLD") 121 | case "/pub": 122 | reqParams, err := url.ParseQuery(req.URL.RawQuery) 123 | if err != nil { 124 | return nil 125 | } 126 | 127 | var topic string 128 | var qos string 129 | if topics, ok := reqParams["topic"]; ok { 130 | topic = topics[0] 131 | } 132 | if qoss, ok := reqParams["qos"]; ok { 133 | qos = qoss[0] 134 | } 135 | 136 | if qos == "" { 137 | qos = "0" 138 | } 139 | 140 | readMax := int64(8192) 141 | body, _ := ioutil.ReadAll(io.LimitReader(req.Body, readMax)) 142 | if len(body) < 1 { 143 | return fmt.Errorf("body required") 144 | } 145 | 146 | rqos, _ := strconv.ParseInt(qos, 10, 32) 147 | self.Engine.SendMessage(topic, []byte(body), int(rqos)) 148 | w.Write([]byte(fmt.Sprintf("OK"))) 149 | return nil 150 | case "/stats": 151 | return nil 152 | case self.WebSocketMount: 153 | s := websocket.Server{ 154 | Handler: websocket.Handler(func(ws *websocket.Conn) { 155 | // need for binary frame 156 | ws.PayloadType = 0x02 157 | 158 | myconf := GetDefaultMyConfig() 159 | myconf.MaxMessageSize = self.Engine.Config().Server.MessageSizeLimit 160 | conn := NewMyConnection(myconf) 161 | conn.SetMyConnection(ws) 162 | conn.SetId(ws.RemoteAddr().String()) 163 | self.Engine.HandleConnection(conn) 164 | }), 165 | Handshake: func (config *websocket.Config, req *http.Request) (err error) { 166 | config.Origin, err = websocket.Origin(config, req) 167 | if err == nil && config.Origin == nil { 168 | return fmt.Errorf("null origin") 169 | } 170 | 171 | if len(config.Protocol) > 1 { 172 | config.Protocol = []string{"mqttv3.1"} 173 | } 174 | 175 | // これどっしよっかなー。もうちょっと楽に選択させたい 176 | v := 0 177 | for i := 0; i < len(config.Protocol); i++ { 178 | switch config.Protocol[i] { 179 | case "mqtt": 180 | if v == 0 { 181 | v = 1 182 | } 183 | case "mqttv3.1": 184 | v = 2 185 | default: 186 | return fmt.Errorf("unsupported protocol") 187 | } 188 | } 189 | 190 | switch v { 191 | case 1: 192 | config.Protocol = []string{"mqtt"} 193 | case 2: 194 | config.Protocol = []string{"mqttv3.1"} 195 | } 196 | 197 | return err 198 | }, 199 | } 200 | s.ServeHTTP(w, req) 201 | default: 202 | return fmt.Errorf("404 %s", req.URL.Path) 203 | } 204 | return nil 205 | } 206 | 207 | func (self *MyHttpServer) getTopicFromQuery(req *http.Request) (url.Values, string, error) { 208 | reqParams, err := url.ParseQuery(req.URL.RawQuery) 209 | if err != nil { 210 | return nil, "", err 211 | } 212 | 213 | topicNames, _ := reqParams["topic"] 214 | topicName := topicNames[0] 215 | 216 | return reqParams, topicName, nil 217 | } 218 | -------------------------------------------------------------------------------- /server/http_listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | log "github.com/chobie/momonga/logger" 8 | "net" 9 | "os" 10 | "sync" 11 | ) 12 | 13 | type HttpListener struct { 14 | net.Listener 15 | WebSocketMount string 16 | mutex sync.RWMutex 17 | wg sync.WaitGroup 18 | close chan bool 19 | } 20 | 21 | func NewHttpListener(listener net.Listener) *HttpListener { 22 | l := &HttpListener{ 23 | Listener: listener, 24 | } 25 | 26 | return l 27 | } 28 | 29 | func (self *HttpListener) Accept() (c net.Conn, err error) { 30 | c, err = self.Listener.Accept() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | self.wg.Add(1) 36 | return &MyConn{Conn: c, wg: &self.wg}, nil 37 | } 38 | 39 | func (self *HttpListener) Close() error { 40 | return self.Listener.Close() 41 | } 42 | 43 | func (self *HttpListener) Addr() net.Addr { 44 | return self.Listener.Addr() 45 | } 46 | 47 | func (self *HttpListener) File() (f *os.File, err error) { 48 | if tl, ok := self.Listener.(*net.TCPListener); ok { 49 | file, _ := tl.File() 50 | return file, nil 51 | } 52 | log.Info("HttpListener Failed to convert file: %T", self.Listener) 53 | 54 | return nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /server/http_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "fmt" 8 | "github.com/chobie/momonga/configuration" 9 | log "github.com/chobie/momonga/logger" 10 | "net" 11 | "net/http" 12 | "os" 13 | "sync" 14 | ) 15 | 16 | type HttpServer struct { 17 | http.Server 18 | Engine *Momonga 19 | Address string 20 | stop chan bool 21 | listener Listener 22 | inherit bool 23 | once sync.Once 24 | wg *sync.WaitGroup 25 | } 26 | 27 | func NewHttpServer(engine *Momonga, config *configuration.Config, inherit bool) *HttpServer { 28 | t := &HttpServer{ 29 | Server: http.Server{ 30 | Handler: &MyHttpServer{ 31 | Engine: engine, 32 | WebSocketMount: config.Server.WebSocketMount, 33 | }, 34 | }, 35 | Engine: engine, 36 | Address: fmt.Sprintf(":%d", config.Server.HttpPort), 37 | stop: make(chan bool, 1), 38 | inherit: inherit, 39 | } 40 | 41 | return t 42 | } 43 | 44 | func (self *HttpServer) ListenAndServe() error { 45 | if self.inherit { 46 | file := os.NewFile(uintptr(5), "sock") 47 | tmp, err := net.FileListener(file) 48 | file.Close() 49 | if err != nil { 50 | log.Error("HttpServer: %s", err) 51 | return nil 52 | } 53 | listener := tmp.(*net.TCPListener) 54 | self.listener = NewHttpListener(listener) 55 | } else { 56 | addr, err := net.ResolveTCPAddr("tcp4", self.Address) 57 | base, err := net.ListenTCP("tcp", addr) 58 | 59 | listener := NewHttpListener(base) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | self.listener = listener 65 | } 66 | return self.Serve(self.listener) 67 | } 68 | 69 | func (self *HttpServer) Serve(l net.Listener) error { 70 | log.Info("momonga_http: started http server: %s", l.Addr().String()) 71 | 72 | // TODO: how do I stop this? 73 | go self.Server.Serve(l) 74 | return nil 75 | } 76 | 77 | func (self *HttpServer) Stop() { 78 | close(self.stop) 79 | 80 | self.once.Do(func() { 81 | self.listener.(*HttpListener).wg.Wait() 82 | log.Info("Finished HTTP Listener") 83 | self.wg.Done() 84 | }) 85 | } 86 | 87 | func (self *HttpServer) Graceful() { 88 | self.Stop() 89 | } 90 | 91 | func (self *HttpServer) Listener() Listener { 92 | return self.listener 93 | } 94 | -------------------------------------------------------------------------------- /server/listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "net" 8 | "os" 9 | ) 10 | 11 | type Listener interface { 12 | Accept() (c net.Conn, err error) 13 | 14 | Close() error 15 | 16 | Addr() net.Addr 17 | 18 | File() (f *os.File, err error) 19 | } 20 | -------------------------------------------------------------------------------- /server/mmux_connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | . "github.com/chobie/momonga/common" 9 | "github.com/chobie/momonga/encoding/mqtt" 10 | log "github.com/chobie/momonga/logger" 11 | "github.com/chobie/momonga/util" 12 | "sync" 13 | "time" 14 | "unsafe" 15 | "sync/atomic" 16 | ) 17 | 18 | // MQTT Multiplexer Connection 19 | // 20 | // Multiplexer、というかなんだろ。EngineとConnectionとの仲介でおいとくやつ。 21 | // Sessionがあるのでこういうふうにしとくと楽かな、と 22 | type MmuxConnection struct { 23 | // Primary 24 | Connection *Connection 25 | OfflineQueue []mqtt.Message 26 | MaxOfflineQueue int 27 | Identifier string 28 | CleanSession bool 29 | OutGoingTable *util.MessageTable 30 | SubscribeMap map[string]bool 31 | Created time.Time 32 | Hash uint32 33 | Mutex sync.RWMutex 34 | SubscribedTopics map[string]*SubscribeSet 35 | } 36 | 37 | func NewMmuxConnection() *MmuxConnection { 38 | conn := &MmuxConnection{ 39 | OutGoingTable: util.NewMessageTable(), 40 | SubscribeMap: map[string]bool{}, 41 | MaxOfflineQueue: 1000, 42 | Created: time.Now(), 43 | Identifier: "", 44 | SubscribedTopics: make(map[string]*SubscribeSet), 45 | CleanSession: true, 46 | } 47 | 48 | return conn 49 | } 50 | 51 | func (self *MmuxConnection) GetId() string { 52 | return self.Identifier 53 | } 54 | 55 | func (self *MmuxConnection) Attach(conn Connection) { 56 | self.Mutex.Lock() 57 | defer self.Mutex.Unlock() 58 | 59 | var container Connection 60 | container = conn 61 | old := atomic.SwapPointer((*unsafe.Pointer)((unsafe.Pointer)(&self.Connection)), unsafe.Pointer(&container)) 62 | if old != nil { 63 | // 1.If the ClientId represents a Client already connected to the Server 64 | // then the Server MUST disconnect the existing Client [MQTT-3.1.4-2]. 65 | (*(*Connection)(old)).Close() 66 | log.Debug("close existing connection") 67 | } 68 | 69 | self.CleanSession = conn.ShouldCleanSession() 70 | if conn.ShouldCleanSession() { 71 | self.OfflineQueue = self.OfflineQueue[:0] 72 | self.SubscribeMap = make(map[string]bool) 73 | self.SubscribedTopics = make(map[string]*SubscribeSet) 74 | 75 | // Should I remove remaining QoS1, QoS2 message at this time? 76 | self.OutGoingTable.Clean() 77 | } else { 78 | if len(self.OfflineQueue) > 0 { 79 | log.Info("Process Offline Queue: Playback: %d", len(self.OfflineQueue)) 80 | for i := 0; i < len(self.OfflineQueue); i++ { 81 | self.writeMessageQueue(self.OfflineQueue[i]) 82 | } 83 | self.OfflineQueue = self.OfflineQueue[:0] 84 | } 85 | } 86 | } 87 | 88 | func (self *MmuxConnection) GetRealId() string { 89 | return self.Identifier 90 | } 91 | 92 | func (self *MmuxConnection) Detach(conn Connection, dummy *DummyPlug) { 93 | var container Connection 94 | container = dummy 95 | 96 | atomic.SwapPointer((*unsafe.Pointer)((unsafe.Pointer)(&self.Connection)), unsafe.Pointer(&container)) 97 | } 98 | 99 | func (self *MmuxConnection) WriteMessageQueue(request mqtt.Message) { 100 | self.writeMessageQueue(request) 101 | } 102 | 103 | // Without lock 104 | func (self *MmuxConnection) writeMessageQueue(request mqtt.Message) { 105 | // TODO: これそもそもmuxがあったらもうdummyか普通のか、ぐらいなような気が 106 | _, is_dummy := (*self.Connection).(*DummyPlug) 107 | if self.Connection == nil { 108 | // already disconnected 109 | return 110 | } else if is_dummy { 111 | if request.GetType() == mqtt.PACKET_TYPE_PUBLISH { 112 | self.OfflineQueue = append(self.OfflineQueue, request) 113 | } 114 | } 115 | 116 | (*self.Connection).WriteMessageQueue(request) 117 | } 118 | 119 | func (self *MmuxConnection) Close() error { 120 | return (*self.Connection).Close() 121 | } 122 | 123 | func (self *MmuxConnection) SetState(state State) { 124 | (*self.Connection).SetState(state) 125 | } 126 | 127 | func (self *MmuxConnection) GetState() State { 128 | return (*self.Connection).GetState() 129 | } 130 | 131 | func (self *MmuxConnection) ResetState() { 132 | (*self.Connection).ResetState() 133 | } 134 | 135 | func (self *MmuxConnection) ReadMessage() (mqtt.Message, error) { 136 | return (*self.Connection).ReadMessage() 137 | } 138 | 139 | func (self *MmuxConnection) IsAlived() bool { 140 | return (*self.Connection).IsAlived() 141 | } 142 | 143 | func (self *MmuxConnection) SetWillMessage(msg mqtt.WillMessage) { 144 | self.Mutex.Lock() 145 | defer self.Mutex.Unlock() 146 | 147 | (*self.Connection).SetWillMessage(msg) 148 | } 149 | 150 | func (self *MmuxConnection) GetWillMessage() *mqtt.WillMessage { 151 | self.Mutex.RLock() 152 | defer self.Mutex.RUnlock() 153 | 154 | return (*self.Connection).GetWillMessage() 155 | } 156 | 157 | func (self *MmuxConnection) HasWillMessage() bool { 158 | self.Mutex.RLock() 159 | defer self.Mutex.RUnlock() 160 | 161 | return (*self.Connection).HasWillMessage() 162 | } 163 | 164 | func (self *MmuxConnection) GetOutGoingTable() *util.MessageTable { 165 | self.Mutex.RLock() 166 | defer self.Mutex.RUnlock() 167 | 168 | return self.OutGoingTable 169 | } 170 | 171 | func (self *MmuxConnection) GetSubscribedTopics() map[string]*SubscribeSet { 172 | self.Mutex.RLock() 173 | defer self.Mutex.RUnlock() 174 | 175 | return self.SubscribedTopics 176 | } 177 | 178 | func (self *MmuxConnection) AppendSubscribedTopic(topic string, set *SubscribeSet) { 179 | self.Mutex.Lock() 180 | defer self.Mutex.Unlock() 181 | 182 | self.SubscribedTopics[topic] = set 183 | self.SubscribeMap[topic] = true 184 | } 185 | 186 | func (self *MmuxConnection) IsSubscribed(topic string) bool { 187 | self.Mutex.RLock() 188 | defer self.Mutex.RUnlock() 189 | 190 | if _, ok := self.SubscribeMap[topic]; ok { 191 | return true 192 | } 193 | return false 194 | } 195 | 196 | func (self *MmuxConnection) RemoveSubscribedTopic(topic string) { 197 | self.Mutex.Lock() 198 | defer self.Mutex.Unlock() 199 | 200 | if _, ok := self.SubscribedTopics[topic]; ok { 201 | delete(self.SubscribedTopics, topic) 202 | delete(self.SubscribeMap, topic) 203 | } 204 | } 205 | 206 | func (self *MmuxConnection) SetKeepaliveInterval(interval int) { 207 | self.Mutex.Lock() 208 | defer self.Mutex.Unlock() 209 | 210 | (*self.Connection).SetKeepaliveInterval(interval) 211 | } 212 | 213 | func (self *MmuxConnection) DisableCleanSession() { 214 | } 215 | 216 | func (self *MmuxConnection) ShouldCleanSession() bool { 217 | return (*self.Connection).ShouldCleanSession() 218 | } 219 | 220 | func (self *MmuxConnection) GetHash() uint32 { 221 | return self.Hash 222 | } 223 | 224 | func (self *MmuxConnection) SetId(id string) { 225 | self.Mutex.Lock() 226 | defer self.Mutex.Unlock() 227 | 228 | self.Identifier = id 229 | self.Hash = util.MurmurHash([]byte(id)) 230 | } 231 | 232 | func (self *MmuxConnection) IsBridge() bool { 233 | if self.Connection == nil { 234 | return false 235 | } 236 | 237 | return (*self.Connection).IsBridge() 238 | } 239 | 240 | -------------------------------------------------------------------------------- /server/mon.go: -------------------------------------------------------------------------------- 1 | // +build profile 2 | 3 | package server 4 | 5 | import ( 6 | "expvar" 7 | "fmt" 8 | myexpvar "github.com/chobie/momonga/expvar" 9 | "github.com/cloudfoundry/gosigar" 10 | "github.com/influxdb/influxdb/client" 11 | "github.com/chobie/momonga/util" 12 | "reflect" 13 | "runtime" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | var result map[string]interface{} 20 | var curkey string 21 | var ignore []string = []string{"memstats.PauseNs", "memstats.BySize"} 22 | 23 | func flatten(xv reflect.Value, key string, r interface{}) { 24 | for i := 0; i < len(ignore); i++ { 25 | if strings.Contains(key, ignore[i]) { 26 | return 27 | } 28 | } 29 | 30 | switch xv.Kind() { 31 | case reflect.Map: 32 | result[key] = r 33 | case reflect.Slice, reflect.Array: 34 | num := xv.Len() 35 | for i := 0; i < num; i++ { 36 | c := xv.Index(i) 37 | if c.Kind() == reflect.Struct { 38 | flatten(c, fmt.Sprintf("%s.%d", key, i), c.Interface()) 39 | } else { 40 | result[fmt.Sprintf("%s.%d", key, i)] = c.String() 41 | } 42 | } 43 | case reflect.Struct: 44 | num := xv.NumField() 45 | ty := xv.Type() 46 | for i := 0; i < num; i++ { 47 | y := xv.Field(i) 48 | if y.Kind() == reflect.Struct { 49 | flatten(y, fmt.Sprintf("%s.%s", key, ty.Field(i).Name), y.Interface()) 50 | } else if y.Kind() == reflect.Slice { 51 | flatten(y, fmt.Sprintf("%s.%s", key, ty.Field(i).Name), y.Interface()) 52 | } else if y.Kind() == reflect.Array { 53 | flatten(y, fmt.Sprintf("%s.%s", key, ty.Field(i).Name), y.Interface()) 54 | } else { 55 | result[fmt.Sprintf("%s.%s", key, ty.Field(i).Name)] = y.Interface() 56 | } 57 | } 58 | default: 59 | panic(fmt.Sprintf("%d not supported\n", xv.Kind())) 60 | } 61 | } 62 | 63 | func cb(kv expvar.KeyValue) { 64 | m := reflect.ValueOf(kv.Value).MethodByName("Do") 65 | if m.IsValid() { 66 | curkey = kv.Key 67 | m.Call([]reflect.Value{reflect.ValueOf(cb)}) 68 | curkey = "" 69 | } else { 70 | var key string 71 | 72 | if curkey != "" { 73 | key = fmt.Sprintf("%s.%s", curkey, kv.Key) 74 | } else { 75 | key = kv.Key 76 | } 77 | if f, ok := kv.Value.(expvar.Func); ok { 78 | r := f() 79 | xv := reflect.ValueOf(r) 80 | flatten(xv, key, r) 81 | } else if f, ok := kv.Value.(*expvar.Int); ok { 82 | a, _ := strconv.ParseInt(f.String(), 10, 64) 83 | result[key] = a 84 | } else if f, ok := kv.Value.(*expvar.Float); ok { 85 | a, _ := strconv.ParseFloat(f.String(), 64) 86 | result[key] = a 87 | } else if f, ok := kv.Value.(*myexpvar.DiffFloat); ok { 88 | a, _ := strconv.ParseFloat(f.String(), 64) 89 | result[key] = a 90 | } else if f, ok := kv.Value.(*myexpvar.DiffInt); ok { 91 | a, _ := strconv.ParseInt(f.String(), 10, 64) 92 | result[key] = a 93 | } else { 94 | result[key] = kv.Value.String() 95 | } 96 | } 97 | } 98 | 99 | func init() { 100 | result = make(map[string]interface{}) 101 | 102 | go func() { 103 | lastCpu := sigar.Cpu{} 104 | cpu := sigar.Cpu{} 105 | lastCpu.Get() 106 | 107 | c, _ := client.NewClient(&client.ClientConfig{ 108 | Database: "test", 109 | }) 110 | 111 | for { 112 | Metrics.NumGoroutine.Set(int64(runtime.NumGoroutine())) 113 | Metrics.NumCgoCall.Set(int64(runtime.NumGoroutine())) 114 | Metrics.Uptime.Set(time.Now().Unix()) 115 | 116 | Metrics.MessageSentPerSec.Set(util.GetIntValue(Metrics.System.Broker.Messages.Sent)) 117 | if util.GetIntValue(Metrics.System.Broker.Clients.Connected) > 0 { 118 | Metrics.GoroutinePerConn.Set(float64(util.GetIntValue(Metrics.NumGoroutine) / util.GetIntValue(Metrics.System.Broker.Clients.Connected))) 119 | } 120 | 121 | mem := sigar.Mem{} 122 | mem.Get() 123 | 124 | Metrics.MemFree.Set(int64(mem.Free)) 125 | Metrics.MemUsed.Set(int64(mem.Used)) 126 | Metrics.MemActualFree.Set(int64(mem.ActualFree)) 127 | Metrics.MemActualUsed.Set(int64(mem.ActualUsed)) 128 | Metrics.MemTotal.Set(int64(mem.Total)) 129 | 130 | load := sigar.LoadAverage{} 131 | load.Get() 132 | Metrics.LoadOne.Set(float64(load.One)) 133 | Metrics.LoadFive.Set(float64(load.Five)) 134 | Metrics.LoadFifteen.Set(float64(load.Fifteen)) 135 | 136 | cpu.Get() 137 | 138 | Metrics.CpuUser.Set(float64(cpu.User - lastCpu.User)) 139 | Metrics.CpuNice.Set(float64(cpu.Nice - lastCpu.Nice)) 140 | Metrics.CpuSys.Set(float64(cpu.Sys - lastCpu.Sys)) 141 | Metrics.CpuIdle.Set(float64(cpu.Idle - lastCpu.Idle)) 142 | Metrics.CpuWait.Set(float64(cpu.Wait - lastCpu.Wait)) 143 | Metrics.CpuIrq.Set(float64(cpu.Irq - lastCpu.Irq)) 144 | Metrics.CpuSoftIrq.Set(float64(cpu.SoftIrq - lastCpu.SoftIrq)) 145 | Metrics.CpuStolen.Set(float64(cpu.Stolen - lastCpu.Stolen)) 146 | Metrics.CpuTotal.Set(float64(cpu.Total() - lastCpu.Total())) 147 | 148 | expvar.Do(cb) 149 | w := &client.Series{ 150 | Name: "test", 151 | } 152 | p := []interface{}{} 153 | for k, v := range result { 154 | w.Columns = append(w.Columns, k) 155 | p = append(p, v) 156 | } 157 | w.Points = [][]interface{}{p} 158 | e := c.WriteSeries([]*client.Series{w}) 159 | 160 | if e != nil { 161 | fmt.Printf("error: %s", e) 162 | } 163 | 164 | lastCpu = cpu 165 | time.Sleep(time.Second) 166 | } 167 | }() 168 | } 169 | -------------------------------------------------------------------------------- /server/mylistener.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | log "github.com/chobie/momonga/logger" 5 | "net" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | type MyListener struct { 11 | net.Listener 12 | mutex sync.RWMutex 13 | wg sync.WaitGroup 14 | close chan bool 15 | } 16 | 17 | func (self *MyListener) Accept() (net.Conn, error) { 18 | var c net.Conn 19 | 20 | c, err := self.Listener.Accept() 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | self.wg.Add(1) 26 | return &MyConn{Conn: c, wg: &self.wg}, nil 27 | } 28 | 29 | func (self *MyListener) File() (f *os.File, err error) { 30 | if tl, ok := self.Listener.(*net.TCPListener); ok { 31 | file, _ := tl.File() 32 | return file, nil 33 | } 34 | log.Info("MyConn Failed to convert file: %T", self.Listener) 35 | 36 | return nil, nil 37 | } 38 | 39 | type MyConn struct { 40 | net.Conn 41 | wg *sync.WaitGroup 42 | once sync.Once 43 | } 44 | 45 | func (self *MyConn) Close() error { 46 | err := self.Conn.Close() 47 | self.once.Do(func() { 48 | self.wg.Done() 49 | }) 50 | return err 51 | } 52 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "net" 8 | ) 9 | 10 | type Server interface { 11 | ListenAndServe() error 12 | //ListenAndServeTLS(certFile, keyFile string) error 13 | 14 | Serve(l net.Listener) error 15 | 16 | Graceful() 17 | 18 | Stop() 19 | 20 | Listener() Listener 21 | 22 | // Restart() 23 | } 24 | -------------------------------------------------------------------------------- /server/stats.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "expvar" 9 | myexpvar "github.com/chobie/momonga/expvar" 10 | ) 11 | 12 | type MyBroker struct { 13 | Clients MyClients 14 | Messages MyMessages 15 | Load MyLoad 16 | SubscriptionsCount *expvar.Int 17 | Uptime *expvar.Int 18 | } 19 | 20 | type MyMessages struct { 21 | Received *expvar.Int 22 | Sent *expvar.Int 23 | Stored *expvar.Int 24 | PublishDropped *expvar.Int 25 | RetainedCount *expvar.Int 26 | } 27 | 28 | type MyClients struct { 29 | Connected *expvar.Int 30 | Total *expvar.Int 31 | Maximum *expvar.Int 32 | Disconnected *expvar.Int 33 | } 34 | 35 | type MyLoad struct { 36 | BytesSend *expvar.Int 37 | BytesReceived *expvar.Int 38 | } 39 | 40 | type MySystem struct { 41 | Broker MyBroker 42 | } 43 | 44 | type MyMetrics struct { 45 | System MySystem 46 | 47 | NumGoroutine *expvar.Int 48 | NumCgoCall *expvar.Int 49 | Uptime *expvar.Int 50 | MemFree *expvar.Int 51 | MemUsed *expvar.Int 52 | MemActualFree *expvar.Int 53 | MemActualUsed *expvar.Int 54 | MemTotal *expvar.Int 55 | LoadOne *expvar.Float 56 | LoadFive *expvar.Float 57 | LoadFifteen *expvar.Float 58 | CpuUser *expvar.Float 59 | CpuNice *expvar.Float 60 | CpuSys *expvar.Float 61 | CpuIdle *expvar.Float 62 | CpuWait *expvar.Float 63 | CpuIrq *expvar.Float 64 | CpuSoftIrq *expvar.Float 65 | CpuStolen *expvar.Float 66 | CpuTotal *expvar.Float 67 | MessageSentPerSec *myexpvar.DiffInt 68 | ConnectPerSec *myexpvar.DiffInt 69 | GoroutinePerConn *expvar.Float 70 | } 71 | 72 | // TODO: should not use expvar as we can't hold multiple MyMetrics metrics. 73 | var Metrics *MyMetrics = &MyMetrics{ 74 | System: MySystem{ 75 | Broker: MyBroker{ 76 | Clients: MyClients{ 77 | Connected: expvar.NewInt("sys.broker.clients.connected"), 78 | Total: expvar.NewInt("sys.broker.clients.total"), 79 | Maximum: expvar.NewInt("sys.broker.clients.maximum"), 80 | Disconnected: expvar.NewInt("sys.broker.clients.disconnected"), 81 | }, 82 | Uptime: expvar.NewInt("sys.broker.uptime"), 83 | Messages: MyMessages{ 84 | Received: expvar.NewInt("sys.broker.messages.received"), 85 | Sent: expvar.NewInt("sys.broker.messages.sent"), 86 | Stored: expvar.NewInt("sys.broker.messages.stored"), 87 | PublishDropped: expvar.NewInt("sys.broker.messages.publish.dropped"), 88 | RetainedCount: expvar.NewInt("sys.broker.messages.retained.count"), 89 | }, 90 | Load: MyLoad{ 91 | BytesSend: expvar.NewInt("sys.broker.load.bytes_send"), 92 | BytesReceived: expvar.NewInt("sys.broker.load.bytes_received"), 93 | }, 94 | SubscriptionsCount: expvar.NewInt("sys.broker.subscriptions.count"), 95 | }, 96 | }, 97 | 98 | // for debug 99 | NumGoroutine: expvar.NewInt("numgoroutine"), 100 | NumCgoCall: expvar.NewInt("numcgocall"), 101 | Uptime: expvar.NewInt("uptime"), 102 | MemFree: expvar.NewInt("memfree"), 103 | MemUsed: expvar.NewInt("memused"), 104 | MemActualFree: expvar.NewInt("memactualfree"), 105 | MemActualUsed: expvar.NewInt("memactualused"), 106 | MemTotal: expvar.NewInt("memtotal"), 107 | LoadOne: expvar.NewFloat("loadone"), 108 | LoadFive: expvar.NewFloat("loadfive"), 109 | LoadFifteen: expvar.NewFloat("loadfifteen"), 110 | CpuUser: expvar.NewFloat("cpuuser"), 111 | CpuNice: expvar.NewFloat("cpunice"), 112 | CpuSys: expvar.NewFloat("cpusys"), 113 | CpuIdle: expvar.NewFloat("cpuidle"), 114 | CpuWait: expvar.NewFloat("cpuwait"), 115 | CpuIrq: expvar.NewFloat("cpuirq"), 116 | CpuSoftIrq: expvar.NewFloat("cpusoftirq"), 117 | CpuStolen: expvar.NewFloat("cpustolen"), 118 | CpuTotal: expvar.NewFloat("cputotal"), 119 | 120 | MessageSentPerSec: myexpvar.NewDiffInt("msg_sent_per_sec"), 121 | ConnectPerSec: myexpvar.NewDiffInt("connect_per_sec"), 122 | GoroutinePerConn: expvar.NewFloat("goroutine_per_conn"), 123 | } 124 | -------------------------------------------------------------------------------- /server/tcp_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "fmt" 8 | . "github.com/chobie/momonga/common" 9 | "github.com/chobie/momonga/configuration" 10 | log "github.com/chobie/momonga/logger" 11 | "net" 12 | "os" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type TcpServer struct { 19 | ListenAddress string 20 | Engine *Momonga 21 | config *configuration.Config 22 | stop chan bool 23 | listener Listener 24 | inherit bool 25 | wg *sync.WaitGroup 26 | once sync.Once 27 | } 28 | 29 | func NewTcpServer(engine *Momonga, config *configuration.Config, inherit bool) *TcpServer { 30 | t := &TcpServer{ 31 | Engine: engine, 32 | ListenAddress: config.GetListenAddress(), 33 | config: config, 34 | stop: make(chan bool, 1), 35 | inherit: inherit, 36 | } 37 | 38 | return t 39 | } 40 | 41 | func (self *TcpServer) ListenAndServe() error { 42 | if self.inherit { 43 | file := os.NewFile(uintptr(3), "sock") 44 | tmp, err := net.FileListener(file) 45 | file.Close() 46 | if err != nil { 47 | log.Error("Error: %s", err) 48 | return nil 49 | } 50 | 51 | listener := tmp.(*net.TCPListener) 52 | self.listener = &MyListener{Listener: listener} 53 | } else { 54 | addr, err := net.ResolveTCPAddr("tcp4", self.ListenAddress) 55 | listener, err := net.ListenTCP("tcp", addr) 56 | 57 | if err != nil { 58 | panic(fmt.Sprintf("Error: %s", err)) 59 | return err 60 | } 61 | 62 | self.listener = &MyListener{Listener: listener} 63 | } 64 | 65 | log.Info("momonga_tcp: started tcp server: %s", self.listener.Addr().String()) 66 | for i := 0; i < self.config.GetAcceptorCount(); i++ { 67 | go self.Serve(self.listener) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (self *TcpServer) Serve(l net.Listener) error { 74 | defer func() { 75 | l.Close() 76 | }() 77 | 78 | var tempDelay time.Duration // how long to sleep on accept failure 79 | 80 | Accept: 81 | for { 82 | select { 83 | case <-self.stop: 84 | break Accept 85 | default: 86 | client, err := l.Accept() 87 | if err != nil { 88 | if v, ok := client.(*net.TCPConn); ok { 89 | v.SetNoDelay(true) 90 | v.SetKeepAlive(true) 91 | } 92 | 93 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 94 | if tempDelay == 0 { 95 | tempDelay = 5 * time.Millisecond 96 | } else { 97 | tempDelay *= 2 98 | } 99 | 100 | if max := 1 * time.Second; tempDelay > max { 101 | tempDelay = max 102 | } 103 | 104 | log.Info("momonga: Accept error: %v; retrying in %v", err, tempDelay) 105 | time.Sleep(tempDelay) 106 | continue 107 | } 108 | 109 | if strings.Contains(err.Error(), "use of closed network connection") { 110 | log.Error("Accept Failed: %s", err) 111 | continue 112 | } 113 | 114 | log.Error("Accept Error: %s", err) 115 | return err 116 | } 117 | tempDelay = 0 118 | 119 | 120 | myconf := GetDefaultMyConfig() 121 | myconf.MaxMessageSize = self.Engine.Config().Server.MessageSizeLimit 122 | conn := NewMyConnection(myconf) 123 | conn.SetMyConnection(client) 124 | conn.SetId(client.RemoteAddr().String()) 125 | 126 | log.Debug("Accepted: %s", conn.GetId()) 127 | go self.Engine.HandleConnection(conn) 128 | } 129 | } 130 | 131 | self.listener.(*MyListener).wg.Wait() 132 | self.once.Do(func() { 133 | self.wg.Done() 134 | }) 135 | return nil 136 | } 137 | 138 | func (self *TcpServer) Stop() { 139 | close(self.stop) 140 | self.listener.Close() 141 | } 142 | 143 | func (self *TcpServer) Graceful() { 144 | log.Info("stop new accepting") 145 | close(self.stop) 146 | self.listener.Close() 147 | 148 | } 149 | 150 | func (self *TcpServer) Listener() Listener { 151 | log.Info("LIS: %#v\n", self.listener) 152 | return self.listener 153 | } 154 | -------------------------------------------------------------------------------- /server/tls_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "fmt" 8 | . "github.com/chobie/momonga/common" 9 | "github.com/chobie/momonga/configuration" 10 | log "github.com/chobie/momonga/logger" 11 | "net" 12 | "os" 13 | "strings" 14 | "sync" 15 | "time" 16 | "crypto/tls" 17 | ) 18 | 19 | type TlsServer struct { 20 | ListenAddress string 21 | Engine *Momonga 22 | config *configuration.Config 23 | stop chan bool 24 | listener Listener 25 | inherit bool 26 | wg *sync.WaitGroup 27 | once sync.Once 28 | tlsConfig *tls.Config 29 | } 30 | 31 | func NewTlsServer(engine *Momonga, config *configuration.Config, inherit bool) *TlsServer { 32 | t := &TlsServer{ 33 | Engine: engine, 34 | ListenAddress: config.GetTlsListenAddress(), 35 | config: config, 36 | stop: make(chan bool, 1), 37 | inherit: inherit, 38 | } 39 | 40 | return t 41 | } 42 | 43 | func (self *TlsServer) ListenAndServe() error { 44 | if self.inherit { 45 | file := os.NewFile(uintptr(3), "sock") 46 | tmp, err := net.FileListener(file) 47 | file.Close() 48 | if err != nil { 49 | log.Error("Error: %s", err) 50 | return nil 51 | } 52 | 53 | cert, err := tls.LoadX509KeyPair(self.config.Server.Certfile, self.config.Server.Keyfile) 54 | if err != nil { 55 | panic(fmt.Sprintf("LoadX509KeyPair error: %s", err)) 56 | } 57 | 58 | config := &tls.Config{Certificates: []tls.Certificate{cert}} 59 | self.tlsConfig = config 60 | // TODO: IS THIS CORRECT? 61 | listener := tmp.(net.Listener) 62 | self.listener = &MyListener{Listener: listener} 63 | } else { 64 | cert, err := tls.LoadX509KeyPair(self.config.Server.Certfile, self.config.Server.Keyfile) 65 | if err != nil { 66 | panic(fmt.Sprintf("LoadX509KeyPair error: %s", err)) 67 | } 68 | 69 | config := &tls.Config{Certificates: []tls.Certificate{cert}} 70 | listener, err := tls.Listen("tcp", self.ListenAddress, config) 71 | self.tlsConfig = config 72 | 73 | if err != nil { 74 | panic(fmt.Sprintf("Error: %s", err)) 75 | } 76 | 77 | self.listener = &MyListener{Listener: listener} 78 | } 79 | 80 | log.Info("momonga_tls: started tls server: %s", self.listener.Addr().String()) 81 | for i := 0; i < self.config.GetAcceptorCount(); i++ { 82 | go self.Serve(self.listener) 83 | } 84 | 85 | return nil 86 | } 87 | 88 | func (self *TlsServer) Serve(l net.Listener) error { 89 | defer func() { 90 | l.Close() 91 | }() 92 | 93 | var tempDelay time.Duration // how long to sleep on accept failure 94 | 95 | Accept: 96 | for { 97 | select { 98 | case <-self.stop: 99 | break Accept 100 | default: 101 | client, err := l.Accept() 102 | if err != nil { 103 | if v, ok := client.(*net.TCPConn); ok { 104 | v.SetNoDelay(true) 105 | v.SetKeepAlive(true) 106 | } 107 | 108 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 109 | if tempDelay == 0 { 110 | tempDelay = 5 * time.Millisecond 111 | } else { 112 | tempDelay *= 2 113 | } 114 | 115 | if max := 1 * time.Second; tempDelay > max { 116 | tempDelay = max 117 | } 118 | 119 | log.Info("momonga: Accept error: %v; retrying in %v", err, tempDelay) 120 | time.Sleep(tempDelay) 121 | continue 122 | } 123 | 124 | if strings.Contains(err.Error(), "use of closed network connection") { 125 | log.Error("Accept Failed: %s", err) 126 | continue 127 | } 128 | 129 | log.Error("Accept Error: %s", err) 130 | return err 131 | } 132 | tempDelay = 0 133 | 134 | myconf := GetDefaultMyConfig() 135 | myconf.MaxMessageSize = self.Engine.Config().Server.MessageSizeLimit 136 | conn := NewMyConnection(myconf) 137 | conn.SetMyConnection(client) 138 | conn.SetId(client.RemoteAddr().String()) 139 | 140 | log.Debug("Accepted: %s", conn.GetId()) 141 | go self.Engine.HandleConnection(conn) 142 | } 143 | } 144 | 145 | self.listener.(*MyListener).wg.Wait() 146 | self.once.Do(func() { 147 | self.wg.Done() 148 | }) 149 | return nil 150 | } 151 | 152 | func (self *TlsServer) Stop() { 153 | close(self.stop) 154 | self.listener.Close() 155 | } 156 | 157 | func (self *TlsServer) Graceful() { 158 | log.Info("stop new accepting") 159 | close(self.stop) 160 | self.listener.Close() 161 | 162 | } 163 | 164 | func (self *TlsServer) Listener() Listener { 165 | log.Info("LIS: %#v\n", self.listener) 166 | return self.listener 167 | } 168 | -------------------------------------------------------------------------------- /server/topic_matcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "io" 8 | ) 9 | 10 | type TopicMatcher interface { 11 | // TODO: should force []*SubscribeSet 12 | Match(Topic string) []interface{} 13 | 14 | Add(Topic string, Value interface{}) 15 | 16 | Remove(Topic string, val interface{}) 17 | 18 | Dump(writer io.Writer) 19 | } 20 | -------------------------------------------------------------------------------- /server/unix_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | package server 5 | 6 | import ( 7 | "fmt" 8 | . "github.com/chobie/momonga/common" 9 | "github.com/chobie/momonga/configuration" 10 | log "github.com/chobie/momonga/logger" 11 | "net" 12 | "os" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type UnixServer struct { 19 | Engine *Momonga 20 | Address string 21 | stop chan bool 22 | listener Listener 23 | inherit bool 24 | wg *sync.WaitGroup 25 | once sync.Once 26 | } 27 | 28 | func NewUnixServer(engine *Momonga, config *configuration.Config, inherit bool) *UnixServer { 29 | t := &UnixServer{ 30 | Engine: engine, 31 | Address: config.GetSocketAddress(), 32 | stop: make(chan bool, 1), 33 | inherit: inherit, 34 | } 35 | 36 | return t 37 | } 38 | 39 | func (self *UnixServer) ListenAndServe() error { 40 | if self.inherit { 41 | file := os.NewFile(uintptr(4), "sock") 42 | tmp, err := net.FileListener(file) 43 | file.Close() 44 | if err != nil { 45 | log.Error("UnixServer: %s", err) 46 | return nil 47 | } 48 | 49 | listener := tmp.(*net.TCPListener) 50 | self.listener = &MyListener{Listener: listener} 51 | } else { 52 | listener, err := net.Listen("unix", self.Address) 53 | 54 | if err != nil { 55 | panic(fmt.Sprintf("Error: %s", err)) 56 | return err 57 | } 58 | self.listener = &MyListener{Listener: listener} 59 | } 60 | go self.Serve(self.listener) 61 | return nil 62 | } 63 | 64 | func (self *UnixServer) Serve(l net.Listener) error { 65 | log.Info("momonga_unix: started server") 66 | defer func() { 67 | l.Close() 68 | }() 69 | 70 | var tempDelay time.Duration // how long to sleep on accept failure 71 | 72 | Accept: 73 | for { 74 | select { 75 | case <-self.stop: 76 | break Accept 77 | default: 78 | client, err := l.Accept() 79 | if err != nil { 80 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 81 | if tempDelay == 0 { 82 | tempDelay = 5 * time.Millisecond 83 | } else { 84 | tempDelay *= 2 85 | } 86 | 87 | if max := 1 * time.Second; tempDelay > max { 88 | tempDelay = max 89 | } 90 | 91 | log.Info("momonga: Accept error: %v; retrying in %v", err, tempDelay) 92 | time.Sleep(tempDelay) 93 | continue 94 | } 95 | if strings.Contains(err.Error(), "use of closed network connection") { 96 | log.Error("Accept Failed: %s", err) 97 | continue 98 | } 99 | 100 | return err 101 | } 102 | tempDelay = 0 103 | 104 | myconf := GetDefaultMyConfig() 105 | myconf.MaxMessageSize = self.Engine.Config().Server.MessageSizeLimit 106 | conn := NewMyConnection(myconf) 107 | conn.SetMyConnection(client) 108 | conn.SetId(client.RemoteAddr().String()) 109 | 110 | log.Debug("Accepted: %s", conn.GetId()) 111 | go self.Engine.HandleConnection(conn) 112 | } 113 | } 114 | 115 | self.listener.(*MyListener).wg.Wait() 116 | self.once.Do(func() { 117 | self.wg.Done() 118 | }) 119 | return nil 120 | } 121 | 122 | func (self *UnixServer) Stop() { 123 | close(self.stop) 124 | self.listener.Close() 125 | } 126 | 127 | func (self *UnixServer) Graceful() { 128 | log.Info("stop new accepting") 129 | close(self.stop) 130 | self.listener.Close() 131 | } 132 | 133 | func (self *UnixServer) Listener() Listener { 134 | return self.listener 135 | } 136 | -------------------------------------------------------------------------------- /skiplist/comparator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package skiplist 6 | 7 | import ( 8 | "bytes" 9 | ) 10 | 11 | type IntComparator struct { 12 | } 13 | 14 | func (self *IntComparator) Compare(a, b interface{}) int { 15 | if ax, ok := a.(int); ok { 16 | if bx, ok := b.(int); ok { 17 | if ax == bx { 18 | return 0 19 | } 20 | if ax < bx { 21 | return -1 22 | } else { 23 | return 1 24 | } 25 | } 26 | } 27 | 28 | return -1 29 | } 30 | 31 | type BytesComparator struct { 32 | } 33 | 34 | func (self *BytesComparator) Compare(a, b interface{}) int { 35 | if ax, ok := a.([]byte); ok { 36 | if bx, ok := b.([]byte); ok { 37 | return bytes.Compare(ax, bx) 38 | } 39 | } 40 | 41 | return -1 42 | } 43 | 44 | type StringComparator struct { 45 | } 46 | 47 | func (self *StringComparator) Compare(a, b interface{}) int { 48 | if ax, ok := a.(string); ok { 49 | if bx, ok := b.(string); ok { 50 | if ax == bx { 51 | return 0 52 | } 53 | if ax < bx { 54 | return -1 55 | } else { 56 | return 1 57 | } 58 | } 59 | } 60 | 61 | return -1 62 | } 63 | -------------------------------------------------------------------------------- /skiplist/iterator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package skiplist 6 | 7 | func (self *SkipList) Iterator() *SkipListIterator { 8 | return &SkipListIterator{ 9 | Node: self.Header.Level[0].Forward, 10 | Parent: self, 11 | } 12 | } 13 | 14 | type SkipListIterator struct { 15 | Node *SkipListNode 16 | Parent *SkipList 17 | } 18 | 19 | func (self *SkipListIterator) Seek(score interface{}) { 20 | node := self.Parent.Header 21 | 22 | for i := self.Parent.Level - 1; i >= 0; i-- { 23 | for node.Level[i].Forward != nil && 24 | self.Parent.Comparator.Compare(node.Level[i].Forward.Score, score) == -1 { 25 | node = node.Level[i].Forward 26 | } 27 | } 28 | 29 | self.Node = node.Level[0].Forward 30 | } 31 | 32 | func (self *SkipListIterator) Key() interface{} { 33 | if self.Node == nil { 34 | return nil 35 | } 36 | 37 | return self.Node.Score 38 | } 39 | 40 | func (self *SkipListIterator) Value() interface{} { 41 | if self.Node == nil { 42 | return nil 43 | } 44 | 45 | return self.Node.Data 46 | } 47 | 48 | func (self *SkipListIterator) Valid() bool { 49 | if self.Node != nil { 50 | return true 51 | } else { 52 | return false 53 | } 54 | } 55 | 56 | func (self *SkipListIterator) Next() { 57 | if v, ok := self.Node.Level[0]; ok { 58 | self.Node = v.Forward 59 | } else { 60 | self.Node = nil 61 | } 62 | } 63 | 64 | func (self *SkipListIterator) Rewind() { 65 | self.Node = self.Parent.Header.Level[0].Forward 66 | } 67 | -------------------------------------------------------------------------------- /skiplist/skiplist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | // 5 | // This skiplist implementation is almost re implementation of the redis's 6 | // Skiplist implementation. As to understanding Skiplist algorithm and easy to re use. 7 | // 8 | // see also redis/src/t_zset.c 9 | // Copyright (c) 2009-2012, Salvatore Sanfilippo 10 | // Copyright (c) 2009-2012, Pieter Noordhuis 11 | // All rights reserved. 12 | // 13 | 14 | package skiplist 15 | 16 | import ( 17 | "fmt" 18 | "math/rand" 19 | ) 20 | 21 | const ( 22 | MAX_LEVEL = 32 23 | ) 24 | 25 | type Comparator interface { 26 | // NOTE: skiplist will be ascending order if a < b, otherwise descending 27 | Compare(interface{}, interface{}) int 28 | } 29 | 30 | type SkipListLevel struct { 31 | Forward *SkipListNode 32 | Span int 33 | } 34 | 35 | type SkipListNode struct { 36 | Score interface{} 37 | Data interface{} 38 | Level map[int]*SkipListLevel 39 | } 40 | 41 | type SkipList struct { 42 | Header *SkipListNode 43 | Tail *SkipListNode 44 | Length int 45 | MaxLevel int 46 | Level int 47 | Comparator Comparator 48 | } 49 | 50 | func CreateSkiplistNode(level int, score interface{}, data interface{}) (*SkipListNode, error) { 51 | node := &SkipListNode{ 52 | Level: map[int]*SkipListLevel{}, 53 | } 54 | 55 | for i := 0; i < level; i++ { 56 | node.Level[i] = &SkipListLevel{ 57 | Forward: nil, 58 | Span: 0, 59 | } 60 | } 61 | 62 | node.Score = score 63 | node.Data = data 64 | 65 | return node, nil 66 | } 67 | 68 | func NewSkipList(c Comparator) *SkipList { 69 | list := &SkipList{ 70 | MaxLevel: MAX_LEVEL, 71 | Level: 1, 72 | } 73 | list.Header, _ = CreateSkiplistNode(MAX_LEVEL, 0, 0) 74 | list.Comparator = c 75 | 76 | return list 77 | } 78 | 79 | func (self *SkipList) Insert(score, data interface{}) { 80 | update := make(map[int]*SkipListNode) 81 | rank := make(map[int]int) 82 | 83 | node := self.Header 84 | for i := self.Level - 1; i >= 0; i-- { 85 | for node.Level[i].Forward != nil && 86 | self.Comparator.Compare(node.Level[i].Forward.Score, score) == -1 { 87 | rank[i] += node.Level[i].Span 88 | node = node.Level[i].Forward 89 | } 90 | 91 | update[i] = node 92 | } 93 | 94 | level := self.getRandomLevel() 95 | if level > self.Level { 96 | for i := self.Level; i < level; i++ { 97 | rank[i] = 0 98 | update[i] = self.Header 99 | update[i].Level[i].Span = self.Length 100 | } 101 | self.Level = level 102 | } 103 | 104 | add, err := CreateSkiplistNode(level, score, data) 105 | if err != nil { 106 | panic(fmt.Sprintf("Error: %s", err)) 107 | } 108 | 109 | for i := 0; i < level; i++ { 110 | add.Level[i].Forward = update[i].Level[i].Forward 111 | update[i].Level[i].Forward = add 112 | add.Level[i].Span = update[i].Level[i].Span - (rank[i] - rank[i]) 113 | update[i].Level[i].Span = (rank[i] - rank[i]) + 1 114 | } 115 | 116 | for i := level; i < self.Level; i++ { 117 | update[i].Level[i].Span++ 118 | } 119 | self.Length++ 120 | } 121 | 122 | func (self *SkipList) Delete(score interface{}) { 123 | update := make(map[int]*SkipListNode) 124 | 125 | node := self.Header 126 | for i := self.Level - 1; i >= 0; i-- { 127 | for node.Level[i].Forward != nil && 128 | self.Comparator.Compare(node.Level[i].Forward.Score, score) == -1 { 129 | node = node.Level[i].Forward 130 | } 131 | update[i] = node 132 | } 133 | 134 | node = node.Level[0].Forward 135 | if node != nil && self.Comparator.Compare(score, node.Score) == 0 { 136 | self.deleteNode(node, score, update) 137 | freeSkipListNode(node) 138 | } 139 | } 140 | 141 | func (self *SkipList) deleteNode(node *SkipListNode, score interface{}, update map[int]*SkipListNode) { 142 | for i := 0; i < self.Level; i++ { 143 | if update[i].Level[i].Forward == node { 144 | update[i].Level[i].Span += node.Level[i].Span - 1 145 | update[i].Level[i].Forward = node.Level[i].Forward 146 | } else { 147 | update[i].Level[i].Span -= 1 148 | } 149 | } 150 | 151 | for self.Level > 1 && self.Header.Level[self.Level-1].Forward == nil { 152 | self.Level-- 153 | } 154 | self.Length-- 155 | } 156 | 157 | func (self *SkipList) getRandomLevel() int { 158 | level := 1 159 | 160 | for { 161 | v := (rand.Int() & 0xFFFF) 162 | if float64(v) < (0.25 * 0xFFFF) { 163 | level++ 164 | } else { 165 | break 166 | } 167 | } 168 | 169 | if level < MAX_LEVEL { 170 | return level 171 | } else { 172 | return MAX_LEVEL 173 | } 174 | } 175 | 176 | func freeSkipListNode(node *SkipListNode) { 177 | node.Score = nil 178 | node.Data = nil 179 | node.Level = nil 180 | } 181 | -------------------------------------------------------------------------------- /util/balancer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package util 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | type Balancer struct { 12 | Prior int64 13 | Start int64 14 | Total float64 15 | ElapsedUseconds int64 16 | SleepUsec int64 17 | PerSec int 18 | } 19 | 20 | func (self *Balancer) Execute(callback func()) { 21 | self.Start = time.Now().UnixNano() / 1e3 22 | if self.Prior > 0 { 23 | self.ElapsedUseconds = self.Start - self.Prior 24 | 25 | exec := float64(self.ElapsedUseconds) * float64(self.PerSec/1000000) 26 | self.Total -= exec 27 | if self.Total < 0.0 { 28 | self.Total = 0.0 29 | } 30 | } 31 | self.Total += 1 32 | 33 | callback() 34 | 35 | self.SleepUsec = int64(float64(self.Total) * float64(1000000/self.PerSec)) 36 | if self.SleepUsec < (10000 / 10) { 37 | // go ahead! 38 | self.Prior = self.Start 39 | return 40 | } 41 | 42 | // hey, let's take a rest. 43 | time.Sleep(time.Duration(self.SleepUsec * 1000)) 44 | self.Prior = time.Now().UnixNano() / 1e3 45 | self.ElapsedUseconds = self.Prior - self.Start 46 | self.Total = float64(self.SleepUsec-self.ElapsedUseconds) * float64(self.PerSec) / float64(1000000) 47 | } 48 | -------------------------------------------------------------------------------- /util/message_table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package util 6 | 7 | // TODO: goroutine safe 8 | import ( 9 | "errors" 10 | codec "github.com/chobie/momonga/encoding/mqtt" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type MessageContainer struct { 16 | Message codec.Message 17 | Refcount int 18 | Created time.Time 19 | Updated time.Time 20 | Opaque interface{} 21 | } 22 | 23 | type MessageTable struct { 24 | sync.RWMutex 25 | Id uint16 26 | Hash map[uint16]*MessageContainer 27 | OnFinish func(uint16, codec.Message, interface{}) 28 | used map[uint16]bool 29 | } 30 | 31 | func NewMessageTable() *MessageTable { 32 | return &MessageTable{ 33 | Id: 1, 34 | Hash: make(map[uint16]*MessageContainer), 35 | used: make(map[uint16]bool), 36 | } 37 | } 38 | 39 | func (self *MessageTable) SetOnFinish(callback func(uint16, codec.Message, interface{})) { 40 | self.OnFinish = callback 41 | } 42 | 43 | func (self *MessageTable) NewId() uint16 { 44 | self.Lock() 45 | if self.Id == 65535 { 46 | self.Id = 0 47 | } 48 | 49 | var id uint16 50 | ok := false 51 | for !ok { 52 | if _, ok = self.used[self.Id]; !ok { 53 | id = self.Id 54 | self.used[self.Id] = true 55 | self.Id++ 56 | break 57 | } 58 | } 59 | 60 | self.Unlock() 61 | return id 62 | } 63 | 64 | func (self *MessageTable) Clean() { 65 | self.Lock() 66 | self.Hash = make(map[uint16]*MessageContainer) 67 | self.Unlock() 68 | } 69 | 70 | func (self *MessageTable) Get(id uint16) (codec.Message, error) { 71 | self.RLock() 72 | if v, ok := self.Hash[id]; ok { 73 | self.RUnlock() 74 | return v.Message, nil 75 | } 76 | 77 | self.RUnlock() 78 | return nil, errors.New("not found") 79 | } 80 | 81 | func (self *MessageTable) Register(id uint16, message codec.Message, opaque interface{}) { 82 | self.Lock() 83 | self.Hash[id] = &MessageContainer{ 84 | Message: message, 85 | Refcount: 1, 86 | Created: time.Now(), 87 | Updated: time.Now(), 88 | Opaque: opaque, 89 | } 90 | self.Unlock() 91 | } 92 | 93 | func (self *MessageTable) Register2(id uint16, message codec.Message, count int, opaque interface{}) { 94 | self.Lock() 95 | self.Hash[id] = &MessageContainer{ 96 | Message: message, 97 | Refcount: count, 98 | Created: time.Now(), 99 | Updated: time.Now(), 100 | Opaque: opaque, 101 | } 102 | self.Unlock() 103 | } 104 | 105 | func (self *MessageTable) Unref(id uint16) { 106 | self.Lock() 107 | if v, ok := self.Hash[id]; ok { 108 | v.Refcount-- 109 | 110 | if v.Refcount < 1 { 111 | if self.OnFinish != nil { 112 | self.OnFinish(id, self.Hash[id].Message, self.Hash[id].Opaque) 113 | } 114 | delete(self.used, id) 115 | delete(self.Hash, id) 116 | } 117 | } 118 | self.Unlock() 119 | } 120 | 121 | func (self *MessageTable) Remove(id uint16) { 122 | self.Lock() 123 | if _, ok := self.Hash[id]; ok { 124 | delete(self.Hash, id) 125 | } 126 | self.Unlock() 127 | } 128 | -------------------------------------------------------------------------------- /util/murmurhash.go: -------------------------------------------------------------------------------- 1 | // this function comes from 2 | // https://github.com/stf-storage/go-stf-server/blob/master/murmurhash.go 3 | // 4 | // Author: github.com/lestrrat 5 | 6 | package util 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | ) 12 | 13 | func MurmurHash(data []byte) uint32 { 14 | const m uint32 = 0x5bd1e995 15 | const r uint8 = 16 16 | var length uint32 = uint32(len(data)) 17 | var h uint32 = length * m 18 | 19 | nblocks := int(length / 4) 20 | buf := bytes.NewBuffer(data) 21 | for i := 0; i < nblocks; i++ { 22 | var x uint32 23 | err := binary.Read(buf, binary.LittleEndian, &x) 24 | if err != nil { 25 | return 0 26 | } 27 | h += x 28 | h *= m 29 | h ^= h >> r 30 | } 31 | 32 | tailIndex := nblocks * 4 33 | switch length & 3 { 34 | case 3: 35 | h += uint32(data[tailIndex+2]) << 16 36 | fallthrough 37 | case 2: 38 | h += uint32(data[tailIndex+1]) << 8 39 | fallthrough 40 | case 1: 41 | h += uint32(data[tailIndex]) 42 | h *= m 43 | h ^= h >> r 44 | } 45 | 46 | h *= m 47 | h ^= h >> 10 48 | h *= m 49 | h ^= h >> 17 50 | 51 | return h 52 | } 53 | -------------------------------------------------------------------------------- /util/qlobber.go: -------------------------------------------------------------------------------- 1 | // This library ported from davedoesdev/qlobber 2 | // 3 | // Copyright (c) 2013 David Halls 4 | // Copyright (c) 2014 Shuhei Tanuma 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is furnished 11 | // to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | package util 25 | 26 | import ( 27 | "fmt" 28 | "io" 29 | "reflect" 30 | "strings" 31 | "sync" 32 | ) 33 | 34 | type QlobberTrie struct { 35 | Collections []interface{} 36 | Trie map[string]*QlobberTrie 37 | } 38 | 39 | type Qlobber struct { 40 | Separator string 41 | WildcardSome string 42 | WildcardOne string 43 | QlobberTrie *QlobberTrie 44 | Mutex *sync.RWMutex 45 | Cache map[string][]interface{} 46 | } 47 | 48 | func NewQlobber() *Qlobber { 49 | q := &Qlobber{ 50 | QlobberTrie: &QlobberTrie{ 51 | Collections: make([]interface{}, 0), 52 | Trie: make(map[string]*QlobberTrie), 53 | }, 54 | Cache: make(map[string][]interface{}), 55 | Mutex: &sync.RWMutex{}, 56 | } 57 | q.Separator = "/" 58 | q.WildcardOne = "+" 59 | q.WildcardSome = "#" 60 | return q 61 | } 62 | 63 | func (self *Qlobber) Match(Topic string) []interface{} { 64 | self.Mutex.RLock() 65 | // if v, ok := self.Cache[Topic]; ok && v != nil { 66 | // self.Mutex.Unlock() 67 | // return v 68 | // } 69 | 70 | var v []interface{} 71 | result := self.match(v, 0, strings.Split(Topic, self.Separator), self.QlobberTrie) 72 | 73 | // self.Cache[Topic] = result 74 | self.Mutex.RUnlock() 75 | return result 76 | } 77 | 78 | func (self *Qlobber) match(v []interface{}, length int, words []string, sub_trie *QlobberTrie) []interface{} { 79 | if st, ok := sub_trie.Trie[self.WildcardSome]; ok { 80 | for offset := range st.Collections { 81 | w := st.Collections[offset] 82 | if w != self.Separator { 83 | for j := length; j < len(words); j++ { 84 | v = self.match(v, j, words, st) 85 | } 86 | break 87 | } 88 | } 89 | v = self.match(v, len(words), words, st) 90 | } 91 | 92 | if length == len(words) { 93 | v = append(v, sub_trie.Collections...) 94 | } else { 95 | word := words[length] 96 | if word != self.WildcardOne && word != self.WildcardSome { 97 | if st, ok := sub_trie.Trie[word]; ok { 98 | v = self.match(v, length+1, words, st) 99 | } 100 | } 101 | 102 | if st, ok := sub_trie.Trie[self.WildcardOne]; ok { 103 | v = self.match(v, length+1, words, st) 104 | } 105 | } 106 | 107 | return v 108 | } 109 | 110 | func (self *Qlobber) Add(Topic string, Value interface{}) { 111 | self.Mutex.Lock() 112 | self.Cache[Topic] = nil 113 | 114 | self.add(Value, 0, strings.Split(Topic, self.Separator), self.QlobberTrie) 115 | self.Mutex.Unlock() 116 | } 117 | 118 | func (self *Qlobber) add(Value interface{}, length int, words []string, sub_trie *QlobberTrie) { 119 | if length == len(words) { 120 | sub_trie.Collections = append(sub_trie.Collections, Value) 121 | return 122 | } 123 | word := words[length] 124 | 125 | var st *QlobberTrie 126 | var ok bool 127 | if st, ok = sub_trie.Trie[word]; !ok { 128 | sub_trie.Trie[word] = &QlobberTrie{ 129 | Collections: make([]interface{}, 0), 130 | Trie: make(map[string]*QlobberTrie), 131 | } 132 | st = sub_trie.Trie[word] 133 | } 134 | 135 | self.add(Value, length+1, words, st) 136 | } 137 | 138 | func (self *Qlobber) remove(val interface{}, i int, words []string, sub_trie *QlobberTrie) { 139 | if i == len(words) { 140 | if val == nil { 141 | sub_trie.Collections = make([]interface{}, 0) 142 | sub_trie.Trie = make(map[string]*QlobberTrie) 143 | } else { 144 | switch val.(type) { 145 | case string: 146 | for o := 0; o < len(sub_trie.Collections); o++ { 147 | sf1 := sub_trie.Collections[o].(string) 148 | if sf1 == val { 149 | sub_trie.Collections = append(sub_trie.Collections[:o], sub_trie.Collections[o+1:]...) 150 | } 151 | } 152 | // TODO: 対応していない奴はpanicしておきたいんだけど 153 | default: 154 | sf2 := reflect.ValueOf(val) 155 | for o := 0; o < len(sub_trie.Collections); o++ { 156 | sf1 := reflect.ValueOf(sub_trie.Collections[o]) 157 | if sf1.Pointer() == sf2.Pointer() { 158 | sub_trie.Collections = append(sub_trie.Collections[:o], sub_trie.Collections[o+1:]...) 159 | } 160 | } 161 | } 162 | } 163 | 164 | if len(sub_trie.Trie) == 0 { 165 | delete(sub_trie.Trie, self.Separator) 166 | } 167 | 168 | return 169 | } 170 | 171 | word := words[i] 172 | 173 | var st *QlobberTrie 174 | var ok bool 175 | if st, ok = sub_trie.Trie[word]; !ok { 176 | return 177 | } 178 | self.remove(val, i+1, words, st) 179 | for _ = range st.Trie { 180 | return 181 | } 182 | for _ = range st.Collections { 183 | return 184 | } 185 | delete(sub_trie.Trie, word) 186 | } 187 | 188 | func (self *Qlobber) Remove(Topic string, val interface{}) { 189 | self.Mutex.Lock() 190 | self.Cache[Topic] = nil 191 | 192 | self.remove(val, 0, strings.Split(Topic, self.Separator), self.QlobberTrie) 193 | self.Mutex.Unlock() 194 | } 195 | 196 | func (self *Qlobber) Dump(writer io.Writer) { 197 | self.Mutex.RLock() 198 | self.dump(self.QlobberTrie, 0, writer) 199 | self.Mutex.RUnlock() 200 | } 201 | 202 | func (self *Qlobber) dump(sub_trie *QlobberTrie, level int, writer io.Writer) { 203 | ls := strings.Repeat(" ", level*2) 204 | for offset := range sub_trie.Collections { 205 | w := sub_trie.Collections[offset] 206 | fmt.Fprintf(writer, "%s`%s\n", ls, w) 207 | } 208 | 209 | for k, v := range sub_trie.Trie { 210 | if k == "" { 211 | k = "root" 212 | } 213 | fmt.Fprintf(writer, "%s[%s]\n", ls, k) 214 | self.dump(v, level+1, writer) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /util/qlobber_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | _ "fmt" 5 | . "gopkg.in/check.v1" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func Test(t *testing.T) { TestingT(t) } 11 | 12 | type QlobberSuite struct{} 13 | 14 | var _ = Suite(&QlobberSuite{}) 15 | 16 | func (s *QlobberSuite) TestQlobber(c *C) { 17 | q := NewQlobber() 18 | q.Add("/debug/chobie", "a") 19 | q.Add("/+/chobie", "b") 20 | q.Add("/#", "c") 21 | 22 | r := q.Match("/debug/chobie") 23 | c.Assert(len(r), Equals, 3) 24 | c.Assert(r[0], Equals, "c") // NOTE: Don't care order 25 | c.Assert(r[1], Equals, "a") 26 | c.Assert(r[2], Equals, "b") 27 | 28 | q.Dump(os.Stdout) 29 | } 30 | 31 | func (s *QlobberSuite) BenchmarkQlobber(c *C) { 32 | q := NewQlobber() 33 | q.Add("/debug/chobie", "a") 34 | q.Add("/+/chobie", "b") 35 | q.Add("/#", "c") 36 | 37 | for i := 0; i < c.N; i++ { 38 | q.Match("/debug/chobie") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Shuhei Tanuma. All rights reserved. 2 | // Use of this source code is governed by a MIT license 3 | // that can be found in the LICENSE file. 4 | 5 | package util 6 | 7 | import ( 8 | "crypto/rand" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "strconv" 13 | "expvar" 14 | ) 15 | 16 | func GetIntValue(i *expvar.Int) int64 { 17 | v := i.String() 18 | vv, e := strconv.ParseInt(v, 10, 64) 19 | if e != nil { 20 | return 0 21 | } 22 | 23 | return vv 24 | } 25 | 26 | func GetFloatValue(f *expvar.Float) float64 { 27 | a, e := strconv.ParseFloat(f.String(), 64) 28 | if e != nil { 29 | return 0 30 | } 31 | 32 | return a 33 | } 34 | 35 | 36 | func GenerateId(n int) string { 37 | const alphanum = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 38 | bytes := make([]byte, n) 39 | 40 | rand.Read(bytes) 41 | for i, b := range bytes { 42 | bytes[i] = alphanum[b%byte(len(alphanum))] 43 | } 44 | 45 | return string(bytes) 46 | } 47 | 48 | func WritePid(pidFile string) { 49 | if pidFile != "" { 50 | pid := strconv.Itoa(os.Getpid()) 51 | if err := ioutil.WriteFile(pidFile, []byte(pid), 0644); err != nil { 52 | panic(err) 53 | } 54 | } 55 | } 56 | 57 | func Daemonize(nochdir, noclose int) int { 58 | if os.Getenv("DAEMONIZE") != "TRUE" { 59 | var pwd string 60 | 61 | path, _ := exec.LookPath(os.Args[0]) 62 | if nochdir == 1 { 63 | pwd, _ = os.Getwd() 64 | } else { 65 | pwd = "/" 66 | } 67 | descriptors := []*os.File{ 68 | os.Stdin, 69 | os.Stdout, 70 | os.Stderr, 71 | } 72 | var env []string 73 | for _, v := range os.Environ() { 74 | env = append(env, v) 75 | } 76 | env = append(env, "DAEMONIZE=TRUE") 77 | p, _ := os.StartProcess(path, os.Args, &os.ProcAttr{ 78 | Dir: pwd, 79 | Env: env, 80 | Files: descriptors, 81 | }) 82 | 83 | if noclose == 0 { 84 | p.Release() 85 | } 86 | os.Exit(0) 87 | } 88 | 89 | return 0 90 | } 91 | --------------------------------------------------------------------------------