Skip to content

Commit e86f94f

Browse files
author
Al S-M
authored
Merge branch 'master' into patch-1
2 parents 3c66462 + a436547 commit e86f94f

13 files changed

+407
-74
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ This client is designed to work with the standard Go tools, so installation is a
2222
go get github.com/eclipse/paho.mqtt.golang
2323
```
2424

25-
The client depends on Google's [websockets](https://godoc.org/golang.org/x/net/websocket) and [proxy](https://godoc.org/golang.org/x/net/proxy) package,
25+
The client depends on Google's [proxy](https://godoc.org/golang.org/x/net/proxy) package and the [websockets](https://godoc.org/github.com/gorilla/websocket) package,
2626
also easily installed with the commands:
2727

2828
```
29-
go get golang.org/x/net/websocket
29+
go get github.com/gorilla/websocket
3030
go get golang.org/x/net/proxy
3131
```
3232

@@ -44,6 +44,10 @@ import "github.com/eclipse/paho.mqtt.golang"
4444

4545
Samples are available in the `cmd` directory for reference.
4646

47+
Note:
48+
49+
The library also supports using MQTT over websockets by using the `ws://` (unsecure) or `wss://` (secure) prefix in the URI. If the client is running behind a corporate http/https proxy then the following environment variables `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` are taken into account when establishing the connection.
50+
4751

4852
Runtime tracing
4953
---------------

client.go

+107-28
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package mqtt
1919

2020
import (
21+
"bytes"
2122
"errors"
2223
"fmt"
2324
"net"
@@ -95,8 +96,8 @@ type Client interface {
9596

9697
// client implements the Client interface
9798
type client struct {
98-
lastSent int64
99-
lastReceived int64
99+
lastSent atomic.Value
100+
lastReceived atomic.Value
100101
pingOutstanding int32
101102
status uint32
102103
sync.RWMutex
@@ -140,9 +141,7 @@ func NewClient(o *ClientOptions) Client {
140141
c.messageIds = messageIds{index: make(map[uint16]tokenCompletor)}
141142
c.msgRouter, c.stopRouter = newRouter()
142143
c.msgRouter.setDefaultHandler(c.options.DefaultPublishHandler)
143-
if !c.options.AutoReconnect {
144-
c.options.MessageChannelDepth = 0
145-
}
144+
146145
return c
147146
}
148147

@@ -157,6 +156,8 @@ func (c *client) AddRoute(topic string, callback MessageHandler) {
157156

158157
// IsConnected returns a bool signifying whether
159158
// the client is connected or not.
159+
// connected means that the connection is up now OR it will
160+
// be established/reestablished automatically when possible
160161
func (c *client) IsConnected() bool {
161162
c.RLock()
162163
defer c.RUnlock()
@@ -166,6 +167,8 @@ func (c *client) IsConnected() bool {
166167
return true
167168
case c.options.AutoReconnect && status > connecting:
168169
return true
170+
case c.options.ConnectRetry && status == connecting:
171+
return true
169172
default:
170173
return false
171174
}
@@ -210,14 +213,27 @@ func (c *client) Connect() Token {
210213
t := newToken(packets.Connect).(*ConnectToken)
211214
DEBUG.Println(CLI, "Connect()")
212215

213-
c.obound = make(chan *PacketAndToken, c.options.MessageChannelDepth)
214-
c.oboundP = make(chan *PacketAndToken, c.options.MessageChannelDepth)
216+
if c.options.ConnectRetry && atomic.LoadUint32(&c.status) != disconnected {
217+
// if in any state other than disconnected and ConnectRetry is
218+
// enabled then the connection will come up automatically
219+
// client can assume connection is up
220+
WARN.Println(CLI, "Connect() called but not disconnected")
221+
t.returnCode = packets.Accepted
222+
t.flowComplete()
223+
return t
224+
}
225+
226+
c.obound = make(chan *PacketAndToken)
227+
c.oboundP = make(chan *PacketAndToken)
215228
c.ibound = make(chan packets.ControlPacket)
216229

217-
go func() {
218-
c.persist.Open()
230+
c.persist.Open()
231+
if c.options.ConnectRetry {
232+
c.reserveStoredPublishIDs() // Reserve IDs to allow publish before connect complete
233+
}
234+
c.setConnected(connecting)
219235

220-
c.setConnected(connecting)
236+
go func() {
221237
c.errors = make(chan error, 1)
222238
c.stop = make(chan struct{})
223239

@@ -229,12 +245,16 @@ func (c *client) Connect() Token {
229245
return
230246
}
231247

248+
RETRYCONN:
232249
for _, broker := range c.options.Servers {
233250
cm := newConnectMsgFromOptions(&c.options, broker)
234251
c.options.ProtocolVersion = protocolVersion
235252
CONN:
236253
DEBUG.Println(CLI, "about to write new connect msg")
237-
c.conn, err = openConnection(broker, c.options.TLSConfig, c.options.ConnectTimeout, c.options.HTTPHeaders)
254+
c.Lock()
255+
c.conn, err = openConnection(broker, c.options.TLSConfig, c.options.ConnectTimeout,
256+
c.options.HTTPHeaders)
257+
c.Unlock()
238258
if err == nil {
239259
DEBUG.Println(CLI, "socket connected to broker")
240260
switch c.options.ProtocolVersion {
@@ -260,10 +280,12 @@ func (c *client) Connect() Token {
260280

261281
rc, t.sessionPresent = c.connect()
262282
if rc != packets.Accepted {
283+
c.Lock()
263284
if c.conn != nil {
264285
c.conn.Close()
265286
c.conn = nil
266287
}
288+
c.Unlock()
267289
//if the protocol version was explicitly set don't do any fallback
268290
if c.options.protocolVersionExplicit {
269291
ERROR.Println(CLI, "Connecting to", broker, "CONNACK was not CONN_ACCEPTED, but rather", packets.ConnackReturnCodes[rc])
@@ -284,6 +306,14 @@ func (c *client) Connect() Token {
284306
}
285307

286308
if c.conn == nil {
309+
if c.options.ConnectRetry {
310+
DEBUG.Println(CLI, "Connect failed, sleeping for", int(c.options.ConnectRetryInterval.Seconds()), "seconds and will then retry")
311+
time.Sleep(c.options.ConnectRetryInterval)
312+
313+
if atomic.LoadUint32(&c.status) == connecting {
314+
goto RETRYCONN
315+
}
316+
}
287317
ERROR.Println(CLI, "Failed to connect to a broker")
288318
c.setConnected(disconnected)
289319
c.persist.Close()
@@ -300,13 +330,13 @@ func (c *client) Connect() Token {
300330

301331
if c.options.KeepAlive != 0 {
302332
atomic.StoreInt32(&c.pingOutstanding, 0)
303-
atomic.StoreInt64(&c.lastReceived, time.Now().Unix())
304-
atomic.StoreInt64(&c.lastSent, time.Now().Unix())
333+
c.lastReceived.Store(time.Now())
334+
c.lastSent.Store(time.Now())
305335
c.workers.Add(1)
306336
go keepalive(c)
307337
}
308338

309-
c.incomingPubChan = make(chan *packets.PublishPacket, c.options.MessageChannelDepth)
339+
c.incomingPubChan = make(chan *packets.PublishPacket)
310340
c.msgRouter.matchAndDispatch(c.incomingPubChan, c.options.Order, c)
311341

312342
c.setConnected(connected)
@@ -322,7 +352,7 @@ func (c *client) Connect() Token {
322352
go incoming(c)
323353

324354
// Take care of any messages in the store
325-
if c.options.CleanSession == false {
355+
if !c.options.CleanSession {
326356
c.resume(c.options.ResumeSubs)
327357
} else {
328358
c.persist.Reset()
@@ -375,8 +405,10 @@ func (c *client) reconnect() {
375405

376406
rc, _ = c.connect()
377407
if rc != packets.Accepted {
378-
c.conn.Close()
379-
c.conn = nil
408+
if c.conn != nil {
409+
c.conn.Close()
410+
c.conn = nil
411+
}
380412
//if the protocol version was explicitly set don't do any fallback
381413
if c.options.protocolVersionExplicit {
382414
ERROR.Println(CLI, "Connecting to", broker, "CONNACK was not Accepted, but rather", packets.ConnackReturnCodes[rc])
@@ -412,8 +444,8 @@ func (c *client) reconnect() {
412444

413445
if c.options.KeepAlive != 0 {
414446
atomic.StoreInt32(&c.pingOutstanding, 0)
415-
atomic.StoreInt64(&c.lastReceived, time.Now().Unix())
416-
atomic.StoreInt64(&c.lastSent, time.Now().Unix())
447+
c.lastReceived.Store(time.Now())
448+
c.lastSent.Store(time.Now())
417449
c.workers.Add(1)
418450
go keepalive(c)
419451
}
@@ -580,11 +612,13 @@ func (c *client) Publish(topic string, qos byte, retained bool, payload interfac
580612
pub.Qos = qos
581613
pub.TopicName = topic
582614
pub.Retain = retained
583-
switch payload.(type) {
615+
switch p := payload.(type) {
584616
case string:
585-
pub.Payload = []byte(payload.(string))
617+
pub.Payload = []byte(p)
586618
case []byte:
587-
pub.Payload = payload.([]byte)
619+
pub.Payload = p
620+
case bytes.Buffer:
621+
pub.Payload = p.Bytes()
588622
default:
589623
token.setError(fmt.Errorf("Unknown payload type"))
590624
return token
@@ -595,11 +629,22 @@ func (c *client) Publish(topic string, qos byte, retained bool, payload interfac
595629
token.messageID = pub.MessageID
596630
}
597631
persistOutbound(c.persist, pub)
598-
if c.connectionStatus() == reconnecting {
632+
switch c.connectionStatus() {
633+
case connecting:
634+
DEBUG.Println(CLI, "storing publish message (connecting), topic:", topic)
635+
case reconnecting:
599636
DEBUG.Println(CLI, "storing publish message (reconnecting), topic:", topic)
600-
} else {
637+
default:
601638
DEBUG.Println(CLI, "sending publish message, topic:", topic)
602-
c.obound <- &PacketAndToken{p: pub, t: token}
639+
publishWaitTimeout := c.options.WriteTimeout
640+
if publishWaitTimeout == 0 {
641+
publishWaitTimeout = time.Second * 30
642+
}
643+
select {
644+
case c.obound <- &PacketAndToken{p: pub, t: token}:
645+
case <-time.After(publishWaitTimeout):
646+
token.setError(errors.New("publish was broken by timeout"))
647+
}
603648
}
604649
return token
605650
}
@@ -664,27 +709,58 @@ func (c *client) SubscribeMultiple(filters map[string]byte, callback MessageHand
664709
return token
665710
}
666711

712+
// reserveStoredPublishIDs reserves the ids for publish packets in the persistant store to ensure these are not duplicated
713+
func (c *client) reserveStoredPublishIDs() {
714+
// The resume function sets the stored id for publish packets only (some other packets
715+
// will get new ids in net code). This means that the only keys we need to ensure are
716+
// unique are the publish ones (and these will completed/replaced in resume() )
717+
if c.options.CleanSession == false {
718+
storedKeys := c.persist.All()
719+
for _, key := range storedKeys {
720+
packet := c.persist.Get(key)
721+
if packet == nil {
722+
continue
723+
}
724+
switch packet.(type) {
725+
case *packets.PublishPacket:
726+
details := packet.Details()
727+
token := &PlaceHolderToken{id: details.MessageID}
728+
c.claimID(token, details.MessageID)
729+
}
730+
}
731+
}
732+
}
733+
667734
// Load all stored messages and resend them
668735
// Call this to ensure QOS > 1,2 even after an application crash
669736
func (c *client) resume(subscription bool) {
670737

671738
storedKeys := c.persist.All()
672739
for _, key := range storedKeys {
673740
packet := c.persist.Get(key)
741+
if packet == nil {
742+
continue
743+
}
674744
details := packet.Details()
675745
if isKeyOutbound(key) {
676746
switch packet.(type) {
677747
case *packets.SubscribePacket:
678748
if subscription {
679749
DEBUG.Println(STR, fmt.Sprintf("loaded pending subscribe (%d)", details.MessageID))
680750
token := newToken(packets.Subscribe).(*SubscribeToken)
681-
c.oboundP <- &PacketAndToken{p: packet, t: token}
751+
select {
752+
case c.oboundP <- &PacketAndToken{p: packet, t: token}:
753+
case <-c.stop:
754+
}
682755
}
683756
case *packets.UnsubscribePacket:
684757
if subscription {
685758
DEBUG.Println(STR, fmt.Sprintf("loaded pending unsubscribe (%d)", details.MessageID))
686759
token := newToken(packets.Unsubscribe).(*UnsubscribeToken)
687-
c.oboundP <- &PacketAndToken{p: packet, t: token}
760+
select {
761+
case c.oboundP <- &PacketAndToken{p: packet, t: token}:
762+
case <-c.stop:
763+
}
688764
}
689765
case *packets.PubrelPacket:
690766
DEBUG.Println(STR, fmt.Sprintf("loaded pending pubrel (%d)", details.MessageID))
@@ -698,7 +774,10 @@ func (c *client) resume(subscription bool) {
698774
c.claimID(token, details.MessageID)
699775
DEBUG.Println(STR, fmt.Sprintf("loaded pending publish (%d)", details.MessageID))
700776
DEBUG.Println(STR, details)
701-
c.obound <- &PacketAndToken{p: packet, t: token}
777+
select {
778+
case c.obound <- &PacketAndToken{p: packet, t: token}:
779+
case <-c.stop:
780+
}
702781
default:
703782
ERROR.Println(STR, "invalid message type in store (discarded)")
704783
c.persist.Del(key)

cmd/ssl/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func main() {
113113
c.Subscribe("/go-mqtt/sample", 0, nil)
114114

115115
i := 0
116-
for _ = range time.Tick(time.Duration(1) * time.Second) {
116+
for range time.Tick(time.Duration(1) * time.Second) {
117117
if i == 5 {
118118
break
119119
}

filestore.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func (store *FileStore) all() []string {
166166
for _, f := range files {
167167
DEBUG.Println(STR, "file in All():", f.Name())
168168
name := f.Name()
169-
if name[len(name)-4:len(name)] != msgExt {
169+
if name[len(name)-4:] != msgExt {
170170
DEBUG.Println(STR, "skipping file, doesn't have right extension: ", name)
171171
continue
172172
}

0 commit comments

Comments
 (0)