├── 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 | 
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 |
--------------------------------------------------------------------------------