use nats for logworker
This commit is contained in:
parent
2d46bec18b
commit
7399245c74
|
@ -9,8 +9,8 @@ import (
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
"github.com/Xe/ln"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
"github.com/namsral/flag"
|
"github.com/namsral/flag"
|
||||||
|
nats "github.com/nats-io/go-nats"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
zipkin "github.com/openzipkin/zipkin-go-opentracing"
|
zipkin "github.com/openzipkin/zipkin-go-opentracing"
|
||||||
)
|
)
|
||||||
|
@ -19,7 +19,7 @@ var (
|
||||||
token = flag.String("token", "", "discord bot token")
|
token = flag.String("token", "", "discord bot token")
|
||||||
zipkinURL = flag.String("zipkin-url", "", "URL for Zipkin traces")
|
zipkinURL = flag.String("zipkin-url", "", "URL for Zipkin traces")
|
||||||
databaseURL = flag.String("database-url", "http://", "URL for database (rqlite)")
|
databaseURL = flag.String("database-url", "http://", "URL for database (rqlite)")
|
||||||
mqURL = flag.String("mq-url", "tcp://mq:9000", "URL for STOMP server")
|
natsURL = flag.String("nats-url", "nats://localhost:4222", "URL for nats message queue")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -52,24 +52,26 @@ func main() {
|
||||||
ln.FatalErr(ctx, err, ln.F{"action": "migrate logs table"})
|
ln.FatalErr(ctx, err, ln.F{"action": "migrate logs table"})
|
||||||
}
|
}
|
||||||
|
|
||||||
mq, err := stomp.Dial(*mqURL)
|
mq, err := nats.Connect(*natsURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ln.FatalErr(ctx, err, ln.F{"url": *mqURL})
|
ln.FatalErr(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mq.Subscribe("/topic/message_create", stomp.HandlerFunc(func(m *stomp.Message) {
|
mq.QueueSubscribe("/message/create", "logworker", func(m *nats.Msg) {
|
||||||
sp, ctx := opentracing.StartSpanFromContext(m.Context(), "logworker.topic.message.create")
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sp, ctx := opentracing.StartSpanFromContext(ctx, "message.create")
|
||||||
defer sp.Finish()
|
defer sp.Finish()
|
||||||
|
|
||||||
msg := &discordgo.Message{}
|
msg := &discordgo.Message{}
|
||||||
err := json.Unmarshal(m.Msg, msg)
|
err := json.Unmarshal(m.Data, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ln.Error(ctx, err, ln.F{"action": "can't unmarshal message body to a discordgo message"})
|
ln.Error(ctx, err, ln.F{"action": "can't unmarshal message body to a discordgo message"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f := ln.F{
|
f := ln.F{
|
||||||
"stomp_id": string(m.ID),
|
|
||||||
"channel_id": msg.ChannelID,
|
"channel_id": msg.ChannelID,
|
||||||
"message_id": msg.ID,
|
"message_id": msg.ID,
|
||||||
"message_author": msg.Author.ID,
|
"message_author": msg.Author.ID,
|
||||||
|
@ -81,7 +83,7 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ln.Error(ctx, err, f, ln.F{"action": "can't add discordgo message to the database"})
|
ln.Error(ctx, err, f, ln.F{"action": "can't add discordgo message to the database"})
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {}
|
select {}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,9 +14,9 @@ import (
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
"github.com/Xe/ln"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
_ "github.com/joho/godotenv/autoload"
|
_ "github.com/joho/godotenv/autoload"
|
||||||
"github.com/namsral/flag"
|
"github.com/namsral/flag"
|
||||||
|
nats "github.com/nats-io/go-nats"
|
||||||
xkcd "github.com/nishanths/go-xkcd"
|
xkcd "github.com/nishanths/go-xkcd"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
otlog "github.com/opentracing/opentracing-go/log"
|
otlog "github.com/opentracing/opentracing-go/log"
|
||||||
|
@ -30,7 +31,7 @@ var (
|
||||||
token = flag.String("token", "", "discord bot token")
|
token = flag.String("token", "", "discord bot token")
|
||||||
zipkinURL = flag.String("zipkin-url", "", "URL for Zipkin traces")
|
zipkinURL = flag.String("zipkin-url", "", "URL for Zipkin traces")
|
||||||
databaseURL = flag.String("database-url", "http://", "URL for database (rqlite)")
|
databaseURL = flag.String("database-url", "http://", "URL for database (rqlite)")
|
||||||
mqURL = flag.String("mq-url", "tcp://mq:9000", "URL for STOMP server")
|
natsURL = flag.String("nats-url", "nats://localhost:4222", "URL for Nats message queue")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -80,11 +81,10 @@ func main() {
|
||||||
|
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
|
|
||||||
mq, err := stomp.Dial(*mqURL)
|
mq, err := nats.Connect(*natsURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ln.FatalErr(ctx, err, ln.F{"url": *mqURL})
|
ln.FatalErr(ctx, err)
|
||||||
}
|
}
|
||||||
_ = mq
|
|
||||||
|
|
||||||
c := cron.New()
|
c := cron.New()
|
||||||
|
|
||||||
|
@ -156,25 +156,15 @@ func main() {
|
||||||
"message_author_is_bot": m.Author.Bot,
|
"message_author_is_bot": m.Author.Bot,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mq.SendJSON("/topic/message_create", m.Message)
|
data, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "EOF" {
|
ln.Error(ctx, err, f)
|
||||||
mq, err = stomp.Dial(*mqURL)
|
return
|
||||||
if err != nil {
|
}
|
||||||
ln.Error(ctx, err, f, ln.F{"url": *mqURL, "action": "reconnect to mq"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = mq.SendJSON("/topic/message_create", m.Message)
|
err = mq.Publish("/message/create", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ln.Error(ctx, err, f, ln.F{"action": "retry message_create post to message queue"})
|
ln.Error(ctx, err, f, ln.F{"action": "send new message to nats"})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ln.Error(ctx, err, f, ln.F{"action": "send created message to queue"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +181,12 @@ func main() {
|
||||||
"user_name": m.User.Username,
|
"user_name": m.User.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mq.SendJSON("/topic/member_add", m.Member)
|
data, err := json.Marshal(m.Member)
|
||||||
|
if err != nil {
|
||||||
|
ln.Error(ctx, err, f, ln.F{"action": "prepare member add to queue"})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mq.Publish("/member/add", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ln.Error(ctx, err, f, ln.F{"action": "send added member to queue"})
|
ln.Error(ctx, err, f, ln.F{"action": "send added member to queue"})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,11 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "9411:9411"
|
- "9411:9411"
|
||||||
|
|
||||||
# message queue
|
# nats message queue
|
||||||
mq:
|
nats:
|
||||||
image: drone/mq
|
image: nats
|
||||||
|
ports:
|
||||||
|
- "4222:4222"
|
||||||
|
|
||||||
# database
|
# database
|
||||||
rqlite:
|
rqlite:
|
||||||
|
@ -21,6 +23,8 @@ services:
|
||||||
image: rqlite/rqlite:4.0.2
|
image: rqlite/rqlite:4.0.2
|
||||||
volumes:
|
volumes:
|
||||||
- rqlite:/rqlite/file
|
- rqlite:/rqlite/file
|
||||||
|
ports:
|
||||||
|
- "4001:4001"
|
||||||
command: -on-disk -http-adv-addr rqlite:4001
|
command: -on-disk -http-adv-addr rqlite:4001
|
||||||
|
|
||||||
# the bot and event sourcing ingress
|
# the bot and event sourcing ingress
|
||||||
|
@ -30,7 +34,7 @@ services:
|
||||||
env_file: ./.env
|
env_file: ./.env
|
||||||
depends_on:
|
depends_on:
|
||||||
- zipkin
|
- zipkin
|
||||||
- mq
|
- nats
|
||||||
- rqlite
|
- rqlite
|
||||||
|
|
||||||
logworker:
|
logworker:
|
||||||
|
@ -39,7 +43,7 @@ services:
|
||||||
env_file: ./.env
|
env_file: ./.env
|
||||||
depends_on:
|
depends_on:
|
||||||
- zipkin
|
- zipkin
|
||||||
- mq
|
- nats
|
||||||
- rqlite
|
- rqlite
|
||||||
command: /root/go/bin/logworker
|
command: /root/go/bin/logworker
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (l *Logs) Add(ctx context.Context, m *discordgo.Message) error {
|
||||||
me = 1
|
me = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
bd := stmt.Bind(m.ID, m.ChannelID, m.Content, ts, me, m.Author.ID, m.Author.Username)
|
bd := stmt.Bind(m.ID, m.ChannelID, m.Content, ts.Unix(), me, m.Author.ID, m.Author.Username)
|
||||||
|
|
||||||
l.bdl.Add(bd, len(bd))
|
l.bdl.Add(bd, len(bd))
|
||||||
|
|
||||||
|
|
|
@ -50,3 +50,7 @@ f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f golang.org/x/net/websocket
|
||||||
66aacef3dd8a676686c7ae3716979581e8b03c47 golang.org/x/net/context
|
66aacef3dd8a676686c7ae3716979581e8b03c47 golang.org/x/net/context
|
||||||
f52d1811a62927559de87708c8913c1650ce4f26 golang.org/x/sync/semaphore
|
f52d1811a62927559de87708c8913c1650ce4f26 golang.org/x/sync/semaphore
|
||||||
e0e0e6e500066ff47335c7717e2a090ad127adec google.golang.org/api/support/bundler
|
e0e0e6e500066ff47335c7717e2a090ad127adec google.golang.org/api/support/bundler
|
||||||
|
acf11e4666ad8ab4680680b45d38cf1509a40fed github.com/nats-io/go-nats
|
||||||
|
acf11e4666ad8ab4680680b45d38cf1509a40fed github.com/nats-io/go-nats/encoders/builtin
|
||||||
|
acf11e4666ad8ab4680680b45d38cf1509a40fed github.com/nats-io/go-nats/util
|
||||||
|
3cf34f9fca4e88afa9da8eabd75e3326c9941b44 github.com/nats-io/nuid
|
||||||
|
|
Binary file not shown.
|
@ -1,61 +0,0 @@
|
||||||
package logger
|
|
||||||
|
|
||||||
var std Logger = new(none)
|
|
||||||
|
|
||||||
// Debugf writes a debug message to the standard logger.
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
|
||||||
std.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verbosef writes a verbose message to the standard logger.
|
|
||||||
func Verbosef(format string, args ...interface{}) {
|
|
||||||
std.Verbosef(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noticef writes a notice message to the standard logger.
|
|
||||||
func Noticef(format string, args ...interface{}) {
|
|
||||||
std.Noticef(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf writes a warning message to the standard logger.
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
|
||||||
std.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf writes a default message to the standard logger.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
std.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger sets the standard logger.
|
|
||||||
func SetLogger(logger Logger) {
|
|
||||||
std = logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger represents a logger.
|
|
||||||
type Logger interface {
|
|
||||||
|
|
||||||
// Debugf writes a debug message.
|
|
||||||
Debugf(string, ...interface{})
|
|
||||||
|
|
||||||
// Verbosef writes a verbose message.
|
|
||||||
Verbosef(string, ...interface{})
|
|
||||||
|
|
||||||
// Noticef writes a notice message.
|
|
||||||
Noticef(string, ...interface{})
|
|
||||||
|
|
||||||
// Warningf writes a warning message.
|
|
||||||
Warningf(string, ...interface{})
|
|
||||||
|
|
||||||
// Printf writes a default message.
|
|
||||||
Printf(string, ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// none is a logger that silently ignores all writes.
|
|
||||||
type none struct{}
|
|
||||||
|
|
||||||
func (*none) Debugf(string, ...interface{}) {}
|
|
||||||
func (*none) Verbosef(string, ...interface{}) {}
|
|
||||||
func (*none) Noticef(string, ...interface{}) {}
|
|
||||||
func (*none) Warningf(string, ...interface{}) {}
|
|
||||||
func (*none) Printf(string, ...interface{}) {}
|
|
|
@ -1,259 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"runtime/debug"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/mq/logger"
|
|
||||||
"github.com/drone/mq/stomp/dialer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client defines a client connection to a STOMP server.
|
|
||||||
type Client struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
peer Peer
|
|
||||||
subs map[string]Handler
|
|
||||||
wait map[string]chan struct{}
|
|
||||||
done chan error
|
|
||||||
|
|
||||||
seq int64
|
|
||||||
|
|
||||||
skipVerify bool
|
|
||||||
readBufferSize int
|
|
||||||
writeBufferSize int
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new STOMP client using the given connection.
|
|
||||||
func New(peer Peer) *Client {
|
|
||||||
return &Client{
|
|
||||||
peer: peer,
|
|
||||||
subs: make(map[string]Handler),
|
|
||||||
wait: make(map[string]chan struct{}),
|
|
||||||
done: make(chan error, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial creates a client connection to the given target.
|
|
||||||
func Dial(target string) (*Client, error) {
|
|
||||||
conn, err := dialer.Dial(target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return New(Conn(conn)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send sends the data to the given destination.
|
|
||||||
func (c *Client) Send(dest string, data []byte, opts ...MessageOption) error {
|
|
||||||
m := NewMessage()
|
|
||||||
m.Method = MethodSend
|
|
||||||
m.Dest = []byte(dest)
|
|
||||||
m.Body = data
|
|
||||||
m.Apply(opts...)
|
|
||||||
return c.sendMessage(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendJSON sends the JSON encoding of v to the given destination.
|
|
||||||
func (c *Client) SendJSON(dest string, v interface{}, opts ...MessageOption) error {
|
|
||||||
data, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
opts = append(opts,
|
|
||||||
WithHeader("content-type", "application/json"),
|
|
||||||
)
|
|
||||||
return c.Send(dest, data, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe subscribes to the given destination.
|
|
||||||
func (c *Client) Subscribe(dest string, handler Handler, opts ...MessageOption) (id []byte, err error) {
|
|
||||||
id = c.incr()
|
|
||||||
|
|
||||||
m := NewMessage()
|
|
||||||
m.Method = MethodSubscribe
|
|
||||||
m.ID = id
|
|
||||||
m.Dest = []byte(dest)
|
|
||||||
m.Apply(opts...)
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
c.subs[string(id)] = handler
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
err = c.sendMessage(m)
|
|
||||||
if err != nil {
|
|
||||||
c.mu.Lock()
|
|
||||||
delete(c.subs, string(id))
|
|
||||||
c.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe unsubscribes to the destination.
|
|
||||||
func (c *Client) Unsubscribe(id []byte, opts ...MessageOption) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
delete(c.subs, string(id))
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
m := NewMessage()
|
|
||||||
m.Method = MethodUnsubscribe
|
|
||||||
m.ID = id
|
|
||||||
m.Apply(opts...)
|
|
||||||
|
|
||||||
return c.sendMessage(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ack acknowledges the messages with the given id.
|
|
||||||
func (c *Client) Ack(id []byte, opts ...MessageOption) error {
|
|
||||||
m := NewMessage()
|
|
||||||
m.Method = MethodAck
|
|
||||||
m.ID = id
|
|
||||||
m.Apply(opts...)
|
|
||||||
|
|
||||||
return c.sendMessage(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nack negative-acknowledges the messages with the given id.
|
|
||||||
func (c *Client) Nack(id []byte, opts ...MessageOption) error {
|
|
||||||
m := NewMessage()
|
|
||||||
m.Method = MethodNack
|
|
||||||
m.ID = id
|
|
||||||
m.Apply(opts...)
|
|
||||||
|
|
||||||
return c.peer.Send(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect opens the connection and establishes the session.
|
|
||||||
func (c *Client) Connect(opts ...MessageOption) error {
|
|
||||||
m := NewMessage()
|
|
||||||
m.Proto = STOMP
|
|
||||||
m.Method = MethodStomp
|
|
||||||
m.Apply(opts...)
|
|
||||||
if err := c.sendMessage(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := <-c.peer.Receive()
|
|
||||||
if !ok {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
defer m.Release()
|
|
||||||
|
|
||||||
if !bytes.Equal(m.Method, MethodConnected) {
|
|
||||||
return fmt.Errorf("stomp: inbound message: unexpected method, want connected")
|
|
||||||
}
|
|
||||||
go c.listen()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect terminates the session and closes the connection.
|
|
||||||
func (c *Client) Disconnect() error {
|
|
||||||
m := NewMessage()
|
|
||||||
m.Method = MethodDisconnect
|
|
||||||
c.sendMessage(m)
|
|
||||||
return c.peer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done returns a channel
|
|
||||||
func (c *Client) Done() <-chan error {
|
|
||||||
return c.done
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) incr() []byte {
|
|
||||||
c.mu.Lock()
|
|
||||||
i := c.seq
|
|
||||||
c.seq++
|
|
||||||
c.mu.Unlock()
|
|
||||||
return strconv.AppendInt(nil, i, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) listen() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logger.Warningf("stomp client: recover panic: %s", r)
|
|
||||||
err, ok := r.(error)
|
|
||||||
if !ok {
|
|
||||||
logger.Warningf("%v: %s", r, debug.Stack())
|
|
||||||
c.done <- fmt.Errorf("%v", r)
|
|
||||||
} else {
|
|
||||||
logger.Warningf("%s", err)
|
|
||||||
c.done <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
m, ok := <-c.peer.Receive()
|
|
||||||
if !ok {
|
|
||||||
c.done <- io.EOF
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case bytes.Equal(m.Method, MethodMessage):
|
|
||||||
c.handleMessage(m)
|
|
||||||
case bytes.Equal(m.Method, MethodRecipet):
|
|
||||||
c.handleReceipt(m)
|
|
||||||
default:
|
|
||||||
logger.Noticef("stomp client: unknown message type: %s",
|
|
||||||
string(m.Method),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleReceipt(m *Message) {
|
|
||||||
c.mu.Lock()
|
|
||||||
receiptc, ok := c.wait[string(m.Receipt)]
|
|
||||||
c.mu.Unlock()
|
|
||||||
if !ok {
|
|
||||||
logger.Noticef("stomp client: unknown read receipt: %s",
|
|
||||||
string(m.Receipt),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
receiptc <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleMessage(m *Message) {
|
|
||||||
c.mu.Lock()
|
|
||||||
handler, ok := c.subs[string(m.Subs)]
|
|
||||||
c.mu.Unlock()
|
|
||||||
if !ok {
|
|
||||||
logger.Noticef("stomp client: subscription not found: %s",
|
|
||||||
string(m.Subs),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handler.Handle(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) sendMessage(m *Message) error {
|
|
||||||
if len(m.Receipt) == 0 {
|
|
||||||
return c.peer.Send(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
receiptc := make(chan struct{}, 1)
|
|
||||||
c.wait[string(m.Receipt)] = receiptc
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
delete(c.wait, string(m.Receipt))
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := c.peer.Send(m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-receiptc:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/mq/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
bufferSize = 32 << 10 // default buffer size 32KB
|
|
||||||
bufferLimit = 32 << 15 // default buffer limit 1MB
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
never time.Time
|
|
||||||
deadline = time.Second * 5
|
|
||||||
|
|
||||||
heartbeatTime = time.Second * 30
|
|
||||||
heartbeatWait = time.Second * 60
|
|
||||||
)
|
|
||||||
|
|
||||||
type connPeer struct {
|
|
||||||
conn net.Conn
|
|
||||||
done chan bool
|
|
||||||
|
|
||||||
reader *bufio.Reader
|
|
||||||
writer *bufio.Writer
|
|
||||||
incoming chan *Message
|
|
||||||
outgoing chan *Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn creates a network-connected peer that reads and writes
|
|
||||||
// messages using net.Conn c.
|
|
||||||
func Conn(c net.Conn) Peer {
|
|
||||||
p := &connPeer{
|
|
||||||
reader: bufio.NewReaderSize(c, bufferSize),
|
|
||||||
writer: bufio.NewWriterSize(c, bufferSize),
|
|
||||||
incoming: make(chan *Message),
|
|
||||||
outgoing: make(chan *Message),
|
|
||||||
done: make(chan bool),
|
|
||||||
conn: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
go p.readInto(p.incoming)
|
|
||||||
go p.writeFrom(p.outgoing)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) Receive() <-chan *Message {
|
|
||||||
return c.incoming
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) Send(message *Message) error {
|
|
||||||
select {
|
|
||||||
case <-c.done:
|
|
||||||
return io.EOF
|
|
||||||
default:
|
|
||||||
c.outgoing <- message
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) Addr() string {
|
|
||||||
return c.conn.RemoteAddr().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) Close() error {
|
|
||||||
return c.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) close() error {
|
|
||||||
select {
|
|
||||||
case <-c.done:
|
|
||||||
return io.EOF
|
|
||||||
default:
|
|
||||||
close(c.done)
|
|
||||||
close(c.incoming)
|
|
||||||
close(c.outgoing)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) readInto(messages chan<- *Message) {
|
|
||||||
defer c.close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// lim := io.LimitReader(c.conn, bufferLimit)
|
|
||||||
// buf := bufio.NewReaderSize(lim, bufferSize)
|
|
||||||
|
|
||||||
buf, err := c.reader.ReadBytes(0)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(buf) == 1 {
|
|
||||||
c.conn.SetReadDeadline(time.Now().Add(heartbeatWait))
|
|
||||||
logger.Verbosef("stomp: received heart-beat")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := NewMessage()
|
|
||||||
msg.Parse(buf[:len(buf)-1])
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-c.done:
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
messages <- msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) writeFrom(messages <-chan *Message) {
|
|
||||||
tick := time.NewTicker(time.Millisecond * 100).C
|
|
||||||
heartbeat := time.NewTicker(heartbeatTime).C
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-c.done:
|
|
||||||
break loop
|
|
||||||
case <-heartbeat:
|
|
||||||
logger.Verbosef("stomp: send heart-beat.")
|
|
||||||
c.writer.WriteByte(0)
|
|
||||||
case <-tick:
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(deadline))
|
|
||||||
if err := c.writer.Flush(); err != nil {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
c.conn.SetWriteDeadline(never)
|
|
||||||
case msg, ok := <-messages:
|
|
||||||
if !ok {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
writeTo(c.writer, msg)
|
|
||||||
c.writer.WriteByte(0)
|
|
||||||
msg.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.drain()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connPeer) drain() error {
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(deadline))
|
|
||||||
for msg := range c.outgoing {
|
|
||||||
writeTo(c.writer, msg)
|
|
||||||
c.writer.WriteByte(0)
|
|
||||||
msg.Release()
|
|
||||||
}
|
|
||||||
c.conn.SetWriteDeadline(never)
|
|
||||||
c.writer.Flush()
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
// STOMP protocol version.
|
|
||||||
var STOMP = []byte("1.2")
|
|
||||||
|
|
||||||
// STOMP protocol methods.
|
|
||||||
var (
|
|
||||||
MethodStomp = []byte("STOMP")
|
|
||||||
MethodConnect = []byte("CONNECT")
|
|
||||||
MethodConnected = []byte("CONNECTED")
|
|
||||||
MethodSend = []byte("SEND")
|
|
||||||
MethodSubscribe = []byte("SUBSCRIBE")
|
|
||||||
MethodUnsubscribe = []byte("UNSUBSCRIBE")
|
|
||||||
MethodAck = []byte("ACK")
|
|
||||||
MethodNack = []byte("NACK")
|
|
||||||
MethodDisconnect = []byte("DISCONNECT")
|
|
||||||
MethodMessage = []byte("MESSAGE")
|
|
||||||
MethodRecipet = []byte("RECEIPT")
|
|
||||||
MethodError = []byte("ERROR")
|
|
||||||
)
|
|
||||||
|
|
||||||
// STOMP protocol headers.
|
|
||||||
var (
|
|
||||||
HeaderAccept = []byte("accept-version")
|
|
||||||
HeaderAck = []byte("ack")
|
|
||||||
HeaderExpires = []byte("expires")
|
|
||||||
HeaderDest = []byte("destination")
|
|
||||||
HeaderHost = []byte("host")
|
|
||||||
HeaderLogin = []byte("login")
|
|
||||||
HeaderPass = []byte("passcode")
|
|
||||||
HeaderID = []byte("id")
|
|
||||||
HeaderMessageID = []byte("message-id")
|
|
||||||
HeaderPersist = []byte("persist")
|
|
||||||
HeaderPrefetch = []byte("prefetch-count")
|
|
||||||
HeaderReceipt = []byte("receipt")
|
|
||||||
HeaderReceiptID = []byte("receipt-id")
|
|
||||||
HeaderRetain = []byte("retain")
|
|
||||||
HeaderSelector = []byte("selector")
|
|
||||||
HeaderServer = []byte("server")
|
|
||||||
HeaderSession = []byte("session")
|
|
||||||
HeaderSubscription = []byte("subscription")
|
|
||||||
HeaderVersion = []byte("version")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Common STOMP header values.
|
|
||||||
var (
|
|
||||||
AckAuto = []byte("auto")
|
|
||||||
AckClient = []byte("client")
|
|
||||||
PersistTrue = []byte("true")
|
|
||||||
RetainTrue = []byte("true")
|
|
||||||
RetainLast = []byte("last")
|
|
||||||
RetainAll = []byte("all")
|
|
||||||
RetainRemove = []byte("remove")
|
|
||||||
)
|
|
||||||
|
|
||||||
var headerLookup = map[string]struct{}{
|
|
||||||
"accept-version": struct{}{},
|
|
||||||
"ack": struct{}{},
|
|
||||||
"expires": struct{}{},
|
|
||||||
"destination": struct{}{},
|
|
||||||
"host": struct{}{},
|
|
||||||
"login": struct{}{},
|
|
||||||
"passcode": struct{}{},
|
|
||||||
"id": struct{}{},
|
|
||||||
"message-id": struct{}{},
|
|
||||||
"persist": struct{}{},
|
|
||||||
"prefetch-count": struct{}{},
|
|
||||||
"receipt": struct{}{},
|
|
||||||
"receipt-id": struct{}{},
|
|
||||||
"retain": struct{}{},
|
|
||||||
"selector": struct{}{},
|
|
||||||
"server": struct{}{},
|
|
||||||
"session": struct{}{},
|
|
||||||
"subscription": struct{}{},
|
|
||||||
"version": struct{}{},
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import "golang.org/x/net/context"
|
|
||||||
|
|
||||||
const clientKey = "stomp.client"
|
|
||||||
|
|
||||||
// NewContext adds the client to the context.
|
|
||||||
func (c *Client) NewContext(ctx context.Context, client *Client) context.Context {
|
|
||||||
// HACK for use with gin and echo
|
|
||||||
if s, ok := ctx.(setter); ok {
|
|
||||||
s.Set(clientKey, clientKey)
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
return context.WithValue(ctx, clientKey, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromContext retrieves the client from context
|
|
||||||
func FromContext(ctx context.Context) (*Client, bool) {
|
|
||||||
client, ok := ctx.Value(clientKey).(*Client)
|
|
||||||
return client, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromContext retrieves the client from context. Panics if not found
|
|
||||||
func MustFromContext(ctx context.Context) *Client {
|
|
||||||
client, ok := FromContext(ctx)
|
|
||||||
if !ok {
|
|
||||||
panic("stomp.Client not found in context")
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK setter defines a context that enables setting values. This is a
|
|
||||||
// temporary workaround for use with gin and echo and will eventually
|
|
||||||
// be removed. DO NOT depend on this.
|
|
||||||
type setter interface {
|
|
||||||
Set(string, interface{})
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
protoHTTP = "http"
|
|
||||||
protoHTTPS = "https"
|
|
||||||
protoWS = "ws"
|
|
||||||
protoWSS = "wss"
|
|
||||||
protoTCP = "tcp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dial creates a client connection to the given target.
|
|
||||||
func Dial(target string) (net.Conn, error) {
|
|
||||||
u, err := url.Parse(target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch u.Scheme {
|
|
||||||
case protoHTTP, protoHTTPS, protoWS, protoWSS:
|
|
||||||
return dialWebsocket(u)
|
|
||||||
case protoTCP:
|
|
||||||
return dialSocket(u)
|
|
||||||
default:
|
|
||||||
panic("stomp: invalid protocol")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialWebsocket(target *url.URL) (net.Conn, error) {
|
|
||||||
origin, err := target.Parse("/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch origin.Scheme {
|
|
||||||
case protoWS:
|
|
||||||
origin.Scheme = protoHTTP
|
|
||||||
case protoWSS:
|
|
||||||
origin.Scheme = protoHTTPS
|
|
||||||
}
|
|
||||||
return websocket.Dial(target.String(), "", origin.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialSocket(target *url.URL) (net.Conn, error) {
|
|
||||||
return net.Dial(protoTCP, target.Host)
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
// Handler handles a STOMP message.
|
|
||||||
type Handler interface {
|
|
||||||
Handle(*Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The HandlerFunc type is an adapter to allow the use of an ordinary
|
|
||||||
// function as a STOMP message handler.
|
|
||||||
type HandlerFunc func(*Message)
|
|
||||||
|
|
||||||
// Handle calls f(m).
|
|
||||||
func (f HandlerFunc) Handle(m *Message) { f(m) }
|
|
|
@ -1,109 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultHeaderLen = 5
|
|
||||||
|
|
||||||
type item struct {
|
|
||||||
name []byte
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header represents the header section of the STOMP message.
|
|
||||||
type Header struct {
|
|
||||||
items []item
|
|
||||||
itemc int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHeader() *Header {
|
|
||||||
return &Header{
|
|
||||||
items: make([]item, defaultHeaderLen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the named header value.
|
|
||||||
func (h *Header) Get(name []byte) (b []byte) {
|
|
||||||
for i := 0; i < h.itemc; i++ {
|
|
||||||
if v := h.items[i]; bytes.Equal(v.name, name) {
|
|
||||||
return v.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the named header value.
|
|
||||||
func (h *Header) GetString(name string) string {
|
|
||||||
k := []byte(name)
|
|
||||||
v := h.Get(k)
|
|
||||||
return string(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBool returns the named header value.
|
|
||||||
func (h *Header) GetBool(name string) bool {
|
|
||||||
s := h.GetString(name)
|
|
||||||
b, _ := strconv.ParseBool(s)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt returns the named header value.
|
|
||||||
func (h *Header) GetInt(name string) int {
|
|
||||||
s := h.GetString(name)
|
|
||||||
i, _ := strconv.Atoi(s)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt64 returns the named header value.
|
|
||||||
func (h *Header) GetInt64(name string) int64 {
|
|
||||||
s := h.GetString(name)
|
|
||||||
i, _ := strconv.ParseInt(s, 10, 64)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field returns the named header value in string format. This is used to
|
|
||||||
// provide compatibility with the SQL expression evaluation package.
|
|
||||||
func (h *Header) Field(name []byte) []byte {
|
|
||||||
return h.Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add appens the key value pair to the header.
|
|
||||||
func (h *Header) Add(name, data []byte) {
|
|
||||||
h.grow()
|
|
||||||
h.items[h.itemc].name = name
|
|
||||||
h.items[h.itemc].data = data
|
|
||||||
h.itemc++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index returns the keypair at index i.
|
|
||||||
func (h *Header) Index(i int) (k, v []byte) {
|
|
||||||
if i > h.itemc {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
k = h.items[i].name
|
|
||||||
v = h.items[i].data
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the header length.
|
|
||||||
func (h *Header) Len() int {
|
|
||||||
return h.itemc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Header) grow() {
|
|
||||||
if h.itemc > defaultHeaderLen-1 {
|
|
||||||
h.items = append(h.items, item{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Header) reset() {
|
|
||||||
h.itemc = 0
|
|
||||||
h.items = h.items[:defaultHeaderLen]
|
|
||||||
for i := range h.items {
|
|
||||||
h.items[i].name = zeroBytes
|
|
||||||
h.items[i].data = zeroBytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var zeroBytes []byte
|
|
|
@ -1,146 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Message represents a parsed STOMP message.
|
|
||||||
type Message struct {
|
|
||||||
ID []byte // id header
|
|
||||||
Proto []byte // stomp version
|
|
||||||
Method []byte // stomp method
|
|
||||||
User []byte // username header
|
|
||||||
Pass []byte // password header
|
|
||||||
Dest []byte // destination header
|
|
||||||
Subs []byte // subscription id
|
|
||||||
Ack []byte // ack id
|
|
||||||
Msg []byte // message-id header
|
|
||||||
Persist []byte // persist header
|
|
||||||
Retain []byte // retain header
|
|
||||||
Prefetch []byte // prefetch count
|
|
||||||
Expires []byte // expires header
|
|
||||||
Receipt []byte // receipt header
|
|
||||||
Selector []byte // selector header
|
|
||||||
Body []byte
|
|
||||||
Header *Header // custom headers
|
|
||||||
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a copy of the Message.
|
|
||||||
func (m *Message) Copy() *Message {
|
|
||||||
c := NewMessage()
|
|
||||||
c.ID = m.ID
|
|
||||||
c.Proto = m.Proto
|
|
||||||
c.Method = m.Method
|
|
||||||
c.User = m.User
|
|
||||||
c.Pass = m.Pass
|
|
||||||
c.Dest = m.Dest
|
|
||||||
c.Subs = m.Subs
|
|
||||||
c.Ack = m.Ack
|
|
||||||
c.Prefetch = m.Prefetch
|
|
||||||
c.Selector = m.Selector
|
|
||||||
c.Persist = m.Persist
|
|
||||||
c.Retain = m.Retain
|
|
||||||
c.Receipt = m.Receipt
|
|
||||||
c.Expires = m.Expires
|
|
||||||
c.Body = m.Body
|
|
||||||
c.ctx = m.ctx
|
|
||||||
c.Header.itemc = m.Header.itemc
|
|
||||||
copy(c.Header.items, m.Header.items)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply applies the options to the message.
|
|
||||||
func (m *Message) Apply(opts ...MessageOption) {
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the raw bytes into the message.
|
|
||||||
func (m *Message) Parse(b []byte) error {
|
|
||||||
return read(b, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the Message in raw byte format.
|
|
||||||
func (m *Message) Bytes() []byte {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
writeTo(&buf, m)
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the Message in string format.
|
|
||||||
func (m *Message) String() string {
|
|
||||||
return string(m.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release releases the message back to the message pool.
|
|
||||||
func (m *Message) Release() {
|
|
||||||
m.Reset()
|
|
||||||
pool.Put(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets the meesage fields to their zero values.
|
|
||||||
func (m *Message) Reset() {
|
|
||||||
m.ID = m.ID[:0]
|
|
||||||
m.Proto = m.Proto[:0]
|
|
||||||
m.Method = m.Method[:0]
|
|
||||||
m.User = m.User[:0]
|
|
||||||
m.Pass = m.Pass[:0]
|
|
||||||
m.Dest = m.Dest[:0]
|
|
||||||
m.Subs = m.Subs[:0]
|
|
||||||
m.Ack = m.Ack[:0]
|
|
||||||
m.Prefetch = m.Prefetch[:0]
|
|
||||||
m.Selector = m.Selector[:0]
|
|
||||||
m.Persist = m.Persist[:0]
|
|
||||||
m.Retain = m.Retain[:0]
|
|
||||||
m.Receipt = m.Receipt[:0]
|
|
||||||
m.Expires = m.Expires[:0]
|
|
||||||
m.Body = m.Body[:0]
|
|
||||||
m.ctx = nil
|
|
||||||
m.Header.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context returns the request's context.
|
|
||||||
func (m *Message) Context() context.Context {
|
|
||||||
if m.ctx != nil {
|
|
||||||
return m.ctx
|
|
||||||
}
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContext returns a shallow copy of m with its context changed
|
|
||||||
// to ctx. The provided ctx must be non-nil.
|
|
||||||
func (m *Message) WithContext(ctx context.Context) *Message {
|
|
||||||
c := m.Copy()
|
|
||||||
c.ctx = ctx
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal parses the JSON-encoded body of the message and
|
|
||||||
// stores the result in the value pointed to by v.
|
|
||||||
func (m *Message) Unmarshal(v interface{}) error {
|
|
||||||
return json.Unmarshal(m.Body, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessage returns an empty message from the message pool.
|
|
||||||
func NewMessage() *Message {
|
|
||||||
return pool.Get().(*Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pool = sync.Pool{New: func() interface{} {
|
|
||||||
return &Message{Header: newHeader()}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Rand returns a random int64 number as a []byte of
|
|
||||||
// ascii characters.
|
|
||||||
func Rand() []byte {
|
|
||||||
return strconv.AppendInt(nil, rand.Int63(), 10)
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageOption configures message options.
|
|
||||||
type MessageOption func(*Message)
|
|
||||||
|
|
||||||
// WithCredentials returns a MessageOption which sets credentials.
|
|
||||||
func WithCredentials(username, password string) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.User = []byte(username)
|
|
||||||
m.Pass = []byte(password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHeader returns a MessageOption which sets a header.
|
|
||||||
func WithHeader(key, value string) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
_, ok := headerLookup[strings.ToLower(key)]
|
|
||||||
if !ok {
|
|
||||||
m.Header.Add(
|
|
||||||
[]byte(key),
|
|
||||||
[]byte(value),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHeaders returns a MessageOption which sets headers.
|
|
||||||
func WithHeaders(headers map[string]string) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
for key, value := range headers {
|
|
||||||
_, ok := headerLookup[strings.ToLower(key)]
|
|
||||||
if !ok {
|
|
||||||
m.Header.Add(
|
|
||||||
[]byte(key),
|
|
||||||
[]byte(value),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithExpires returns a MessageOption configured with an expiration.
|
|
||||||
func WithExpires(exp int64) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Expires = strconv.AppendInt(nil, exp, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPrefetch returns a MessageOption configured with a prefetch count.
|
|
||||||
func WithPrefetch(prefetch int) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Prefetch = strconv.AppendInt(nil, int64(prefetch), 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithReceipt returns a MessageOption configured with a receipt request.
|
|
||||||
func WithReceipt() MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Receipt = strconv.AppendInt(nil, rand.Int63(), 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPersistence returns a MessageOption configured to persist.
|
|
||||||
func WithPersistence() MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Persist = PersistTrue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRetain returns a MessageOption configured to retain the message.
|
|
||||||
func WithRetain(retain string) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Retain = []byte(retain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSelector returns a MessageOption configured to filter messages
|
|
||||||
// using a sql-like evaluation string.
|
|
||||||
func WithSelector(selector string) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Selector = []byte(selector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAck returns a MessageOption configured with an ack policy.
|
|
||||||
func WithAck(ack string) MessageOption {
|
|
||||||
return func(m *Message) {
|
|
||||||
m.Ack = []byte(ack)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Peer defines a peer-to-peer connection.
|
|
||||||
type Peer interface {
|
|
||||||
// Send sends a message.
|
|
||||||
Send(*Message) error
|
|
||||||
|
|
||||||
// Receive returns a channel of inbound messages.
|
|
||||||
Receive() <-chan *Message
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Addr returns the peer address.
|
|
||||||
Addr() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipe creates a synchronous in-memory pipe, where reads on one end are
|
|
||||||
// matched with writes on the other. This is useful for direct, in-memory
|
|
||||||
// client-server communication.
|
|
||||||
func Pipe() (Peer, Peer) {
|
|
||||||
atob := make(chan *Message, 10)
|
|
||||||
btoa := make(chan *Message, 10)
|
|
||||||
|
|
||||||
a := &localPeer{
|
|
||||||
incoming: btoa,
|
|
||||||
outgoing: atob,
|
|
||||||
finished: make(chan bool),
|
|
||||||
}
|
|
||||||
b := &localPeer{
|
|
||||||
incoming: atob,
|
|
||||||
outgoing: btoa,
|
|
||||||
finished: make(chan bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
return a, b
|
|
||||||
}
|
|
||||||
|
|
||||||
type localPeer struct {
|
|
||||||
finished chan bool
|
|
||||||
outgoing chan<- *Message
|
|
||||||
incoming <-chan *Message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *localPeer) Receive() <-chan *Message {
|
|
||||||
return p.incoming
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *localPeer) Send(m *Message) error {
|
|
||||||
select {
|
|
||||||
case <-p.finished:
|
|
||||||
return io.EOF
|
|
||||||
default:
|
|
||||||
p.outgoing <- m
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *localPeer) Close() error {
|
|
||||||
close(p.finished)
|
|
||||||
close(p.outgoing)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *localPeer) Addr() string {
|
|
||||||
peerAddrOnce.Do(func() {
|
|
||||||
// get the local address list
|
|
||||||
addr, _ := net.InterfaceAddrs()
|
|
||||||
if len(addr) != 0 {
|
|
||||||
// use the last address in the list
|
|
||||||
peerAddr = addr[len(addr)-1].String()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return peerAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
var peerAddrOnce sync.Once
|
|
||||||
|
|
||||||
// default address displayed for local pipes
|
|
||||||
var peerAddr = "127.0.0.1/8"
|
|
|
@ -1,139 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func read(input []byte, m *Message) (err error) {
|
|
||||||
var (
|
|
||||||
pos int
|
|
||||||
off int
|
|
||||||
tot = len(input)
|
|
||||||
)
|
|
||||||
|
|
||||||
// parse the stomp message
|
|
||||||
for ; ; off++ {
|
|
||||||
if off == tot {
|
|
||||||
return fmt.Errorf("stomp: invalid method")
|
|
||||||
}
|
|
||||||
if input[off] == '\n' {
|
|
||||||
m.Method = input[pos:off]
|
|
||||||
off++
|
|
||||||
pos = off
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the stomp headers
|
|
||||||
for {
|
|
||||||
if off == tot {
|
|
||||||
return fmt.Errorf("stomp: unexpected eof")
|
|
||||||
}
|
|
||||||
if input[off] == '\n' {
|
|
||||||
off++
|
|
||||||
pos = off
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
name []byte
|
|
||||||
value []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
loop:
|
|
||||||
// parse each individual header
|
|
||||||
for ; ; off++ {
|
|
||||||
if off >= tot {
|
|
||||||
return fmt.Errorf("stomp: unexpected eof")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch input[off] {
|
|
||||||
case '\n':
|
|
||||||
value = input[pos:off]
|
|
||||||
off++
|
|
||||||
pos = off
|
|
||||||
break loop
|
|
||||||
case ':':
|
|
||||||
name = input[pos:off]
|
|
||||||
off++
|
|
||||||
pos = off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case bytes.Equal(name, HeaderAccept):
|
|
||||||
m.Proto = value
|
|
||||||
case bytes.Equal(name, HeaderAck):
|
|
||||||
m.Ack = value
|
|
||||||
case bytes.Equal(name, HeaderDest):
|
|
||||||
m.Dest = value
|
|
||||||
case bytes.Equal(name, HeaderExpires):
|
|
||||||
m.Expires = value
|
|
||||||
case bytes.Equal(name, HeaderLogin):
|
|
||||||
m.User = value
|
|
||||||
case bytes.Equal(name, HeaderPass):
|
|
||||||
m.Pass = value
|
|
||||||
case bytes.Equal(name, HeaderID):
|
|
||||||
m.ID = value
|
|
||||||
case bytes.Equal(name, HeaderMessageID):
|
|
||||||
m.ID = value
|
|
||||||
case bytes.Equal(name, HeaderPersist):
|
|
||||||
m.Persist = value
|
|
||||||
case bytes.Equal(name, HeaderPrefetch):
|
|
||||||
m.Prefetch = value
|
|
||||||
case bytes.Equal(name, HeaderReceipt):
|
|
||||||
m.Receipt = value
|
|
||||||
case bytes.Equal(name, HeaderReceiptID):
|
|
||||||
m.Receipt = value
|
|
||||||
case bytes.Equal(name, HeaderRetain):
|
|
||||||
m.Retain = value
|
|
||||||
case bytes.Equal(name, HeaderSelector):
|
|
||||||
m.Selector = value
|
|
||||||
case bytes.Equal(name, HeaderSubscription):
|
|
||||||
m.Subs = value
|
|
||||||
case bytes.Equal(name, HeaderVersion):
|
|
||||||
m.Proto = value
|
|
||||||
default:
|
|
||||||
m.Header.Add(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tot > pos {
|
|
||||||
m.Body = input[pos:]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
asciiZero = 48
|
|
||||||
asciiNine = 57
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseInt returns the ascii integer value.
|
|
||||||
func ParseInt(d []byte) (n int) {
|
|
||||||
if len(d) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
for _, dec := range d {
|
|
||||||
if dec < asciiZero || dec > asciiNine {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
n = n*10 + (int(dec) - asciiZero)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseInt64 returns the ascii integer value.
|
|
||||||
func ParseInt64(d []byte) (n int64) {
|
|
||||||
if len(d) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
for _, dec := range d {
|
|
||||||
if dec < asciiZero || dec > asciiNine {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
n = n*10 + (int64(dec) - asciiZero)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
package stomp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
crlf = []byte{'\r', '\n'}
|
|
||||||
newline = []byte{'\n'}
|
|
||||||
separator = []byte{':'}
|
|
||||||
terminator = []byte{0}
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeTo(w io.Writer, m *Message) {
|
|
||||||
w.Write(m.Method)
|
|
||||||
w.Write(newline)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case bytes.Equal(m.Method, MethodStomp):
|
|
||||||
// version
|
|
||||||
w.Write(HeaderAccept)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Proto)
|
|
||||||
w.Write(newline)
|
|
||||||
// login
|
|
||||||
if len(m.User) != 0 {
|
|
||||||
w.Write(HeaderLogin)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.User)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
// passcode
|
|
||||||
if len(m.Pass) != 0 {
|
|
||||||
w.Write(HeaderPass)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Pass)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
case bytes.Equal(m.Method, MethodConnected):
|
|
||||||
// version
|
|
||||||
w.Write(HeaderVersion)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Proto)
|
|
||||||
w.Write(newline)
|
|
||||||
case bytes.Equal(m.Method, MethodSend):
|
|
||||||
// dest
|
|
||||||
w.Write(HeaderDest)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Dest)
|
|
||||||
w.Write(newline)
|
|
||||||
if len(m.Expires) != 0 {
|
|
||||||
w.Write(HeaderExpires)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Expires)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
if len(m.Retain) != 0 {
|
|
||||||
w.Write(HeaderRetain)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Retain)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
if len(m.Persist) != 0 {
|
|
||||||
w.Write(HeaderPersist)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Persist)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
case bytes.Equal(m.Method, MethodSubscribe):
|
|
||||||
// id
|
|
||||||
w.Write(HeaderID)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.ID)
|
|
||||||
w.Write(newline)
|
|
||||||
// destination
|
|
||||||
w.Write(HeaderDest)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Dest)
|
|
||||||
w.Write(newline)
|
|
||||||
// selector
|
|
||||||
if len(m.Selector) != 0 {
|
|
||||||
w.Write(HeaderSelector)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Selector)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
// prefetch
|
|
||||||
if len(m.Prefetch) != 0 {
|
|
||||||
w.Write(HeaderPrefetch)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Prefetch)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
if len(m.Ack) != 0 {
|
|
||||||
w.Write(HeaderAck)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Ack)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
case bytes.Equal(m.Method, MethodUnsubscribe):
|
|
||||||
// id
|
|
||||||
w.Write(HeaderID)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.ID)
|
|
||||||
w.Write(newline)
|
|
||||||
case bytes.Equal(m.Method, MethodAck):
|
|
||||||
// id
|
|
||||||
w.Write(HeaderID)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.ID)
|
|
||||||
w.Write(newline)
|
|
||||||
case bytes.Equal(m.Method, MethodNack):
|
|
||||||
// id
|
|
||||||
w.Write(HeaderID)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.ID)
|
|
||||||
w.Write(newline)
|
|
||||||
case bytes.Equal(m.Method, MethodMessage):
|
|
||||||
// message-id
|
|
||||||
w.Write(HeaderMessageID)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.ID)
|
|
||||||
w.Write(newline)
|
|
||||||
// destination
|
|
||||||
w.Write(HeaderDest)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Dest)
|
|
||||||
w.Write(newline)
|
|
||||||
// subscription
|
|
||||||
w.Write(HeaderSubscription)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Subs)
|
|
||||||
w.Write(newline)
|
|
||||||
// ack
|
|
||||||
if len(m.Ack) != 0 {
|
|
||||||
w.Write(HeaderAck)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Ack)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
case bytes.Equal(m.Method, MethodRecipet):
|
|
||||||
// receipt-id
|
|
||||||
w.Write(HeaderReceiptID)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Receipt)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
// receipt header
|
|
||||||
if includeReceiptHeader(m) {
|
|
||||||
w.Write(HeaderReceipt)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(m.Receipt)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, item := range m.Header.items {
|
|
||||||
if m.Header.itemc == i {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.Write(item.name)
|
|
||||||
w.Write(separator)
|
|
||||||
w.Write(item.data)
|
|
||||||
w.Write(newline)
|
|
||||||
}
|
|
||||||
w.Write(newline)
|
|
||||||
w.Write(m.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func includeReceiptHeader(m *Message) bool {
|
|
||||||
return len(m.Receipt) != 0 && !bytes.Equal(m.Method, MethodRecipet)
|
|
||||||
}
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright 2012-2017 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
// A Go client for the NATS messaging system (https://nats.io).
|
||||||
|
package nats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestWithContext takes a context, a subject and payload
|
||||||
|
// in bytes and request expecting a single response.
|
||||||
|
func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return nil, ErrInvalidContext
|
||||||
|
}
|
||||||
|
if nc == nil {
|
||||||
|
return nil, ErrInvalidConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
nc.mu.Lock()
|
||||||
|
// If user wants the old style.
|
||||||
|
if nc.Opts.UseOldRequestStyle {
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return nc.oldRequestWithContext(ctx, subj, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do setup for the new style.
|
||||||
|
if nc.respMap == nil {
|
||||||
|
// _INBOX wildcard
|
||||||
|
nc.respSub = fmt.Sprintf("%s.*", NewInbox())
|
||||||
|
nc.respMap = make(map[string]chan *Msg)
|
||||||
|
}
|
||||||
|
// Create literal Inbox and map to a chan msg.
|
||||||
|
mch := make(chan *Msg, RequestChanLen)
|
||||||
|
respInbox := nc.newRespInbox()
|
||||||
|
token := respToken(respInbox)
|
||||||
|
nc.respMap[token] = mch
|
||||||
|
createSub := nc.respMux == nil
|
||||||
|
ginbox := nc.respSub
|
||||||
|
nc.mu.Unlock()
|
||||||
|
|
||||||
|
if createSub {
|
||||||
|
// Make sure scoped subscription is setup only once.
|
||||||
|
var err error
|
||||||
|
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nc.PublishRequest(subj, respInbox, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var msg *Msg
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg, ok = <-mch:
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrConnectionClosed
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
nc.mu.Lock()
|
||||||
|
delete(nc.respMap, token)
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// oldRequestWithContext utilizes inbox and subscription per request.
|
||||||
|
func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
|
||||||
|
inbox := NewInbox()
|
||||||
|
ch := make(chan *Msg, RequestChanLen)
|
||||||
|
|
||||||
|
s, err := nc.subscribe(inbox, _EMPTY_, nil, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.AutoUnsubscribe(1)
|
||||||
|
defer s.Unsubscribe()
|
||||||
|
|
||||||
|
err = nc.PublishRequest(subj, inbox, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.NextMsgWithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMsgWithContext takes a context and returns the next message
|
||||||
|
// available to a synchronous subscriber, blocking until it is delivered
|
||||||
|
// or context gets canceled.
|
||||||
|
func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return nil, ErrInvalidContext
|
||||||
|
}
|
||||||
|
if s == nil {
|
||||||
|
return nil, ErrBadSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
err := s.validateNextMsgState()
|
||||||
|
if err != nil {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshot
|
||||||
|
mch := s.mch
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var msg *Msg
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg, ok = <-mch:
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrConnectionClosed
|
||||||
|
}
|
||||||
|
err := s.processNextMsgDelivered(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestWithContext will create an Inbox and perform a Request
|
||||||
|
// using the provided cancellation context with the Inbox reply
|
||||||
|
// for the data v. A response will be decoded into the vPtrResponse.
|
||||||
|
func (c *EncodedConn) RequestWithContext(ctx context.Context, subject string, v interface{}, vPtr interface{}) error {
|
||||||
|
if ctx == nil {
|
||||||
|
return ErrInvalidContext
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := c.Enc.Encode(subject, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := c.Conn.RequestWithContext(ctx, subject, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(vPtr) == emptyMsgType {
|
||||||
|
mPtr := vPtr.(*Msg)
|
||||||
|
*mPtr = *m
|
||||||
|
} else {
|
||||||
|
err := c.Enc.Decode(m.Subject, m.Data, vPtr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Copyright 2012-2015 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
package nats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
// Default Encoders
|
||||||
|
. "github.com/nats-io/go-nats/encoders/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder interface is for all register encoders
|
||||||
|
type Encoder interface {
|
||||||
|
Encode(subject string, v interface{}) ([]byte, error)
|
||||||
|
Decode(subject string, data []byte, vPtr interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var encMap map[string]Encoder
|
||||||
|
var encLock sync.Mutex
|
||||||
|
|
||||||
|
// Indexe names into the Registered Encoders.
|
||||||
|
const (
|
||||||
|
JSON_ENCODER = "json"
|
||||||
|
GOB_ENCODER = "gob"
|
||||||
|
DEFAULT_ENCODER = "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
encMap = make(map[string]Encoder)
|
||||||
|
// Register json, gob and default encoder
|
||||||
|
RegisterEncoder(JSON_ENCODER, &JsonEncoder{})
|
||||||
|
RegisterEncoder(GOB_ENCODER, &GobEncoder{})
|
||||||
|
RegisterEncoder(DEFAULT_ENCODER, &DefaultEncoder{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodedConn are the preferred way to interface with NATS. They wrap a bare connection to
|
||||||
|
// a nats server and have an extendable encoder system that will encode and decode messages
|
||||||
|
// from raw Go types.
|
||||||
|
type EncodedConn struct {
|
||||||
|
Conn *Conn
|
||||||
|
Enc Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncodedConn will wrap an existing Connection and utilize the appropriate registered
|
||||||
|
// encoder.
|
||||||
|
func NewEncodedConn(c *Conn, encType string) (*EncodedConn, error) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, errors.New("nats: Nil Connection")
|
||||||
|
}
|
||||||
|
if c.IsClosed() {
|
||||||
|
return nil, ErrConnectionClosed
|
||||||
|
}
|
||||||
|
ec := &EncodedConn{Conn: c, Enc: EncoderForType(encType)}
|
||||||
|
if ec.Enc == nil {
|
||||||
|
return nil, fmt.Errorf("No encoder registered for '%s'", encType)
|
||||||
|
}
|
||||||
|
return ec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterEncoder will register the encType with the given Encoder. Useful for customization.
|
||||||
|
func RegisterEncoder(encType string, enc Encoder) {
|
||||||
|
encLock.Lock()
|
||||||
|
defer encLock.Unlock()
|
||||||
|
encMap[encType] = enc
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncoderForType will return the registered Encoder for the encType.
|
||||||
|
func EncoderForType(encType string) Encoder {
|
||||||
|
encLock.Lock()
|
||||||
|
defer encLock.Unlock()
|
||||||
|
return encMap[encType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish publishes the data argument to the given subject. The data argument
|
||||||
|
// will be encoded using the associated encoder.
|
||||||
|
func (c *EncodedConn) Publish(subject string, v interface{}) error {
|
||||||
|
b, err := c.Enc.Encode(subject, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Conn.publish(subject, _EMPTY_, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishRequest will perform a Publish() expecting a response on the
|
||||||
|
// reply subject. Use Request() for automatically waiting for a response
|
||||||
|
// inline.
|
||||||
|
func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error {
|
||||||
|
b, err := c.Enc.Encode(subject, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Conn.publish(subject, reply, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request will create an Inbox and perform a Request() call
|
||||||
|
// with the Inbox reply for the data v. A response will be
|
||||||
|
// decoded into the vPtrResponse.
|
||||||
|
func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
|
||||||
|
b, err := c.Enc.Encode(subject, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := c.Conn.Request(subject, b, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(vPtr) == emptyMsgType {
|
||||||
|
mPtr := vPtr.(*Msg)
|
||||||
|
*mPtr = *m
|
||||||
|
} else {
|
||||||
|
err = c.Enc.Decode(m.Subject, m.Data, vPtr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is a specific callback used for Subscribe. It is generalized to
|
||||||
|
// an interface{}, but we will discover its format and arguments at runtime
|
||||||
|
// and perform the correct callback, including de-marshaling JSON strings
|
||||||
|
// back into the appropriate struct based on the signature of the Handler.
|
||||||
|
//
|
||||||
|
// Handlers are expected to have one of four signatures.
|
||||||
|
//
|
||||||
|
// type person struct {
|
||||||
|
// Name string `json:"name,omitempty"`
|
||||||
|
// Age uint `json:"age,omitempty"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// handler := func(m *Msg)
|
||||||
|
// handler := func(p *person)
|
||||||
|
// handler := func(subject string, o *obj)
|
||||||
|
// handler := func(subject, reply string, o *obj)
|
||||||
|
//
|
||||||
|
// These forms allow a callback to request a raw Msg ptr, where the processing
|
||||||
|
// of the message from the wire is untouched. Process a JSON representation
|
||||||
|
// and demarshal it into the given struct, e.g. person.
|
||||||
|
// There are also variants where the callback wants either the subject, or the
|
||||||
|
// subject and the reply subject.
|
||||||
|
type Handler interface{}
|
||||||
|
|
||||||
|
// Dissect the cb Handler's signature
|
||||||
|
func argInfo(cb Handler) (reflect.Type, int) {
|
||||||
|
cbType := reflect.TypeOf(cb)
|
||||||
|
if cbType.Kind() != reflect.Func {
|
||||||
|
panic("nats: Handler needs to be a func")
|
||||||
|
}
|
||||||
|
numArgs := cbType.NumIn()
|
||||||
|
if numArgs == 0 {
|
||||||
|
return nil, numArgs
|
||||||
|
}
|
||||||
|
return cbType.In(numArgs - 1), numArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyMsgType = reflect.TypeOf(&Msg{})
|
||||||
|
|
||||||
|
// Subscribe will create a subscription on the given subject and process incoming
|
||||||
|
// messages using the specified Handler. The Handler should be a func that matches
|
||||||
|
// a signature from the description of Handler from above.
|
||||||
|
func (c *EncodedConn) Subscribe(subject string, cb Handler) (*Subscription, error) {
|
||||||
|
return c.subscribe(subject, _EMPTY_, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueSubscribe will create a queue subscription on the given subject and process
|
||||||
|
// incoming messages using the specified Handler. The Handler should be a func that
|
||||||
|
// matches a signature from the description of Handler from above.
|
||||||
|
func (c *EncodedConn) QueueSubscribe(subject, queue string, cb Handler) (*Subscription, error) {
|
||||||
|
return c.subscribe(subject, queue, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal implementation that all public functions will use.
|
||||||
|
func (c *EncodedConn) subscribe(subject, queue string, cb Handler) (*Subscription, error) {
|
||||||
|
if cb == nil {
|
||||||
|
return nil, errors.New("nats: Handler required for EncodedConn Subscription")
|
||||||
|
}
|
||||||
|
argType, numArgs := argInfo(cb)
|
||||||
|
if argType == nil {
|
||||||
|
return nil, errors.New("nats: Handler requires at least one argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
cbValue := reflect.ValueOf(cb)
|
||||||
|
wantsRaw := (argType == emptyMsgType)
|
||||||
|
|
||||||
|
natsCB := func(m *Msg) {
|
||||||
|
var oV []reflect.Value
|
||||||
|
if wantsRaw {
|
||||||
|
oV = []reflect.Value{reflect.ValueOf(m)}
|
||||||
|
} else {
|
||||||
|
var oPtr reflect.Value
|
||||||
|
if argType.Kind() != reflect.Ptr {
|
||||||
|
oPtr = reflect.New(argType)
|
||||||
|
} else {
|
||||||
|
oPtr = reflect.New(argType.Elem())
|
||||||
|
}
|
||||||
|
if err := c.Enc.Decode(m.Subject, m.Data, oPtr.Interface()); err != nil {
|
||||||
|
if c.Conn.Opts.AsyncErrorCB != nil {
|
||||||
|
c.Conn.ach <- func() {
|
||||||
|
c.Conn.Opts.AsyncErrorCB(c.Conn, m.Sub, errors.New("nats: Got an error trying to unmarshal: "+err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if argType.Kind() != reflect.Ptr {
|
||||||
|
oPtr = reflect.Indirect(oPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Arity
|
||||||
|
switch numArgs {
|
||||||
|
case 1:
|
||||||
|
oV = []reflect.Value{oPtr}
|
||||||
|
case 2:
|
||||||
|
subV := reflect.ValueOf(m.Subject)
|
||||||
|
oV = []reflect.Value{subV, oPtr}
|
||||||
|
case 3:
|
||||||
|
subV := reflect.ValueOf(m.Subject)
|
||||||
|
replyV := reflect.ValueOf(m.Reply)
|
||||||
|
oV = []reflect.Value{subV, replyV, oPtr}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
cbValue.Call(oV)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Conn.subscribe(subject, queue, natsCB, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushTimeout allows a Flush operation to have an associated timeout.
|
||||||
|
func (c *EncodedConn) FlushTimeout(timeout time.Duration) (err error) {
|
||||||
|
return c.Conn.FlushTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush will perform a round trip to the server and return when it
|
||||||
|
// receives the internal reply.
|
||||||
|
func (c *EncodedConn) Flush() error {
|
||||||
|
return c.Conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will close the connection to the server. This call will release
|
||||||
|
// all blocking calls, such as Flush(), etc.
|
||||||
|
func (c *EncodedConn) Close() {
|
||||||
|
c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastError reports the last error encountered via the Connection.
|
||||||
|
func (c *EncodedConn) LastError() error {
|
||||||
|
return c.Conn.err
|
||||||
|
}
|
106
vendor/github.com/nats-io/go-nats/encoders/builtin/default_enc.go
generated
vendored
Normal file
106
vendor/github.com/nats-io/go-nats/encoders/builtin/default_enc.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2012-2015 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
package builtin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultEncoder implementation for EncodedConn.
|
||||||
|
// This encoder will leave []byte and string untouched, but will attempt to
|
||||||
|
// turn numbers into appropriate strings that can be decoded. It will also
|
||||||
|
// propely encoded and decode bools. If will encode a struct, but if you want
|
||||||
|
// to properly handle structures you should use JsonEncoder.
|
||||||
|
type DefaultEncoder struct {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
var trueB = []byte("true")
|
||||||
|
var falseB = []byte("false")
|
||||||
|
var nilB = []byte("")
|
||||||
|
|
||||||
|
// Encode
|
||||||
|
func (je *DefaultEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
||||||
|
switch arg := v.(type) {
|
||||||
|
case string:
|
||||||
|
bytes := *(*[]byte)(unsafe.Pointer(&arg))
|
||||||
|
return bytes, nil
|
||||||
|
case []byte:
|
||||||
|
return arg, nil
|
||||||
|
case bool:
|
||||||
|
if arg {
|
||||||
|
return trueB, nil
|
||||||
|
} else {
|
||||||
|
return falseB, nil
|
||||||
|
}
|
||||||
|
case nil:
|
||||||
|
return nilB, nil
|
||||||
|
default:
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintf(&buf, "%+v", arg)
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
func (je *DefaultEncoder) Decode(subject string, data []byte, vPtr interface{}) error {
|
||||||
|
// Figure out what it's pointing to...
|
||||||
|
sData := *(*string)(unsafe.Pointer(&data))
|
||||||
|
switch arg := vPtr.(type) {
|
||||||
|
case *string:
|
||||||
|
*arg = sData
|
||||||
|
return nil
|
||||||
|
case *[]byte:
|
||||||
|
*arg = data
|
||||||
|
return nil
|
||||||
|
case *int:
|
||||||
|
n, err := strconv.ParseInt(sData, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*arg = int(n)
|
||||||
|
return nil
|
||||||
|
case *int32:
|
||||||
|
n, err := strconv.ParseInt(sData, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*arg = int32(n)
|
||||||
|
return nil
|
||||||
|
case *int64:
|
||||||
|
n, err := strconv.ParseInt(sData, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*arg = int64(n)
|
||||||
|
return nil
|
||||||
|
case *float32:
|
||||||
|
n, err := strconv.ParseFloat(sData, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*arg = float32(n)
|
||||||
|
return nil
|
||||||
|
case *float64:
|
||||||
|
n, err := strconv.ParseFloat(sData, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*arg = float64(n)
|
||||||
|
return nil
|
||||||
|
case *bool:
|
||||||
|
b, err := strconv.ParseBool(sData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*arg = b
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
vt := reflect.TypeOf(arg).Elem()
|
||||||
|
return fmt.Errorf("nats: Default Encoder can't decode to type %s", vt)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2013-2015 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
package builtin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GobEncoder is a Go specific GOB Encoder implementation for EncodedConn.
|
||||||
|
// This encoder will use the builtin encoding/gob to Marshal
|
||||||
|
// and Unmarshal most types, including structs.
|
||||||
|
type GobEncoder struct {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(dlc) - This could probably be more efficient.
|
||||||
|
|
||||||
|
// Encode
|
||||||
|
func (ge *GobEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
enc := gob.NewEncoder(b)
|
||||||
|
if err := enc.Encode(v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
func (ge *GobEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
|
||||||
|
dec := gob.NewDecoder(bytes.NewBuffer(data))
|
||||||
|
err = dec.Decode(vPtr)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright 2012-2015 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
package builtin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JsonEncoder is a JSON Encoder implementation for EncodedConn.
|
||||||
|
// This encoder will use the builtin encoding/json to Marshal
|
||||||
|
// and Unmarshal most types, including structs.
|
||||||
|
type JsonEncoder struct {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode
|
||||||
|
func (je *JsonEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
func (je *JsonEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
|
||||||
|
switch arg := vPtr.(type) {
|
||||||
|
case *string:
|
||||||
|
// If they want a string and it is a JSON string, strip quotes
|
||||||
|
// This allows someone to send a struct but receive as a plain string
|
||||||
|
// This cast should be efficient for Go 1.3 and beyond.
|
||||||
|
str := string(data)
|
||||||
|
if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
|
||||||
|
*arg = str[1 : len(str)-1]
|
||||||
|
} else {
|
||||||
|
*arg = str
|
||||||
|
}
|
||||||
|
case *[]byte:
|
||||||
|
*arg = data
|
||||||
|
default:
|
||||||
|
err = json.Unmarshal(data, arg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2013-2017 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
package nats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This allows the functionality for network channels by binding send and receive Go chans
|
||||||
|
// to subjects and optionally queue groups.
|
||||||
|
// Data will be encoded and decoded via the EncodedConn and its associated encoders.
|
||||||
|
|
||||||
|
// BindSendChan binds a channel for send operations to NATS.
|
||||||
|
func (c *EncodedConn) BindSendChan(subject string, channel interface{}) error {
|
||||||
|
chVal := reflect.ValueOf(channel)
|
||||||
|
if chVal.Kind() != reflect.Chan {
|
||||||
|
return ErrChanArg
|
||||||
|
}
|
||||||
|
go chPublish(c, chVal, subject)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish all values that arrive on the channel until it is closed or we
|
||||||
|
// encounter an error.
|
||||||
|
func chPublish(c *EncodedConn, chVal reflect.Value, subject string) {
|
||||||
|
for {
|
||||||
|
val, ok := chVal.Recv()
|
||||||
|
if !ok {
|
||||||
|
// Channel has most likely been closed.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e := c.Publish(subject, val.Interface()); e != nil {
|
||||||
|
// Do this under lock.
|
||||||
|
c.Conn.mu.Lock()
|
||||||
|
defer c.Conn.mu.Unlock()
|
||||||
|
|
||||||
|
if c.Conn.Opts.AsyncErrorCB != nil {
|
||||||
|
// FIXME(dlc) - Not sure this is the right thing to do.
|
||||||
|
// FIXME(ivan) - If the connection is not yet closed, try to schedule the callback
|
||||||
|
if c.Conn.isClosed() {
|
||||||
|
go c.Conn.Opts.AsyncErrorCB(c.Conn, nil, e)
|
||||||
|
} else {
|
||||||
|
c.Conn.ach <- func() { c.Conn.Opts.AsyncErrorCB(c.Conn, nil, e) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindRecvChan binds a channel for receive operations from NATS.
|
||||||
|
func (c *EncodedConn) BindRecvChan(subject string, channel interface{}) (*Subscription, error) {
|
||||||
|
return c.bindRecvChan(subject, _EMPTY_, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindRecvQueueChan binds a channel for queue-based receive operations from NATS.
|
||||||
|
func (c *EncodedConn) BindRecvQueueChan(subject, queue string, channel interface{}) (*Subscription, error) {
|
||||||
|
return c.bindRecvChan(subject, queue, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal function to bind receive operations for a channel.
|
||||||
|
func (c *EncodedConn) bindRecvChan(subject, queue string, channel interface{}) (*Subscription, error) {
|
||||||
|
chVal := reflect.ValueOf(channel)
|
||||||
|
if chVal.Kind() != reflect.Chan {
|
||||||
|
return nil, ErrChanArg
|
||||||
|
}
|
||||||
|
argType := chVal.Type().Elem()
|
||||||
|
|
||||||
|
cb := func(m *Msg) {
|
||||||
|
var oPtr reflect.Value
|
||||||
|
if argType.Kind() != reflect.Ptr {
|
||||||
|
oPtr = reflect.New(argType)
|
||||||
|
} else {
|
||||||
|
oPtr = reflect.New(argType.Elem())
|
||||||
|
}
|
||||||
|
if err := c.Enc.Decode(m.Subject, m.Data, oPtr.Interface()); err != nil {
|
||||||
|
c.Conn.err = errors.New("nats: Got an error trying to unmarshal: " + err.Error())
|
||||||
|
if c.Conn.Opts.AsyncErrorCB != nil {
|
||||||
|
c.Conn.ach <- func() { c.Conn.Opts.AsyncErrorCB(c.Conn, m.Sub, c.Conn.err) }
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if argType.Kind() != reflect.Ptr {
|
||||||
|
oPtr = reflect.Indirect(oPtr)
|
||||||
|
}
|
||||||
|
// This is a bit hacky, but in this instance we may be trying to send to a closed channel.
|
||||||
|
// and the user does not know when it is safe to close the channel.
|
||||||
|
defer func() {
|
||||||
|
// If we have panicked, recover and close the subscription.
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
m.Sub.Unsubscribe()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Actually do the send to the channel.
|
||||||
|
chVal.Send(oPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Conn.subscribe(subject, queue, cb, nil)
|
||||||
|
}
|
|
@ -0,0 +1,470 @@
|
||||||
|
// Copyright 2012-2017 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
package nats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgArg struct {
|
||||||
|
subject []byte
|
||||||
|
reply []byte
|
||||||
|
sid int64
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_CONTROL_LINE_SIZE = 1024
|
||||||
|
|
||||||
|
type parseState struct {
|
||||||
|
state int
|
||||||
|
as int
|
||||||
|
drop int
|
||||||
|
ma msgArg
|
||||||
|
argBuf []byte
|
||||||
|
msgBuf []byte
|
||||||
|
scratch [MAX_CONTROL_LINE_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
OP_START = iota
|
||||||
|
OP_PLUS
|
||||||
|
OP_PLUS_O
|
||||||
|
OP_PLUS_OK
|
||||||
|
OP_MINUS
|
||||||
|
OP_MINUS_E
|
||||||
|
OP_MINUS_ER
|
||||||
|
OP_MINUS_ERR
|
||||||
|
OP_MINUS_ERR_SPC
|
||||||
|
MINUS_ERR_ARG
|
||||||
|
OP_M
|
||||||
|
OP_MS
|
||||||
|
OP_MSG
|
||||||
|
OP_MSG_SPC
|
||||||
|
MSG_ARG
|
||||||
|
MSG_PAYLOAD
|
||||||
|
MSG_END
|
||||||
|
OP_P
|
||||||
|
OP_PI
|
||||||
|
OP_PIN
|
||||||
|
OP_PING
|
||||||
|
OP_PO
|
||||||
|
OP_PON
|
||||||
|
OP_PONG
|
||||||
|
OP_I
|
||||||
|
OP_IN
|
||||||
|
OP_INF
|
||||||
|
OP_INFO
|
||||||
|
OP_INFO_SPC
|
||||||
|
INFO_ARG
|
||||||
|
)
|
||||||
|
|
||||||
|
// parse is the fast protocol parser engine.
|
||||||
|
func (nc *Conn) parse(buf []byte) error {
|
||||||
|
var i int
|
||||||
|
var b byte
|
||||||
|
|
||||||
|
// Move to loop instead of range syntax to allow jumping of i
|
||||||
|
for i = 0; i < len(buf); i++ {
|
||||||
|
b = buf[i]
|
||||||
|
|
||||||
|
switch nc.ps.state {
|
||||||
|
case OP_START:
|
||||||
|
switch b {
|
||||||
|
case 'M', 'm':
|
||||||
|
nc.ps.state = OP_M
|
||||||
|
case 'P', 'p':
|
||||||
|
nc.ps.state = OP_P
|
||||||
|
case '+':
|
||||||
|
nc.ps.state = OP_PLUS
|
||||||
|
case '-':
|
||||||
|
nc.ps.state = OP_MINUS
|
||||||
|
case 'I', 'i':
|
||||||
|
nc.ps.state = OP_I
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_M:
|
||||||
|
switch b {
|
||||||
|
case 'S', 's':
|
||||||
|
nc.ps.state = OP_MS
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MS:
|
||||||
|
switch b {
|
||||||
|
case 'G', 'g':
|
||||||
|
nc.ps.state = OP_MSG
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MSG:
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t':
|
||||||
|
nc.ps.state = OP_MSG_SPC
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MSG_SPC:
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
nc.ps.state = MSG_ARG
|
||||||
|
nc.ps.as = i
|
||||||
|
}
|
||||||
|
case MSG_ARG:
|
||||||
|
switch b {
|
||||||
|
case '\r':
|
||||||
|
nc.ps.drop = 1
|
||||||
|
case '\n':
|
||||||
|
var arg []byte
|
||||||
|
if nc.ps.argBuf != nil {
|
||||||
|
arg = nc.ps.argBuf
|
||||||
|
} else {
|
||||||
|
arg = buf[nc.ps.as : i-nc.ps.drop]
|
||||||
|
}
|
||||||
|
if err := nc.processMsgArgs(arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, MSG_PAYLOAD
|
||||||
|
|
||||||
|
// jump ahead with the index. If this overruns
|
||||||
|
// what is left we fall out and process split
|
||||||
|
// buffer.
|
||||||
|
i = nc.ps.as + nc.ps.ma.size - 1
|
||||||
|
default:
|
||||||
|
if nc.ps.argBuf != nil {
|
||||||
|
nc.ps.argBuf = append(nc.ps.argBuf, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case MSG_PAYLOAD:
|
||||||
|
if nc.ps.msgBuf != nil {
|
||||||
|
if len(nc.ps.msgBuf) >= nc.ps.ma.size {
|
||||||
|
nc.processMsg(nc.ps.msgBuf)
|
||||||
|
nc.ps.argBuf, nc.ps.msgBuf, nc.ps.state = nil, nil, MSG_END
|
||||||
|
} else {
|
||||||
|
// copy as much as we can to the buffer and skip ahead.
|
||||||
|
toCopy := nc.ps.ma.size - len(nc.ps.msgBuf)
|
||||||
|
avail := len(buf) - i
|
||||||
|
|
||||||
|
if avail < toCopy {
|
||||||
|
toCopy = avail
|
||||||
|
}
|
||||||
|
|
||||||
|
if toCopy > 0 {
|
||||||
|
start := len(nc.ps.msgBuf)
|
||||||
|
// This is needed for copy to work.
|
||||||
|
nc.ps.msgBuf = nc.ps.msgBuf[:start+toCopy]
|
||||||
|
copy(nc.ps.msgBuf[start:], buf[i:i+toCopy])
|
||||||
|
// Update our index
|
||||||
|
i = (i + toCopy) - 1
|
||||||
|
} else {
|
||||||
|
nc.ps.msgBuf = append(nc.ps.msgBuf, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if i-nc.ps.as >= nc.ps.ma.size {
|
||||||
|
nc.processMsg(buf[nc.ps.as:i])
|
||||||
|
nc.ps.argBuf, nc.ps.msgBuf, nc.ps.state = nil, nil, MSG_END
|
||||||
|
}
|
||||||
|
case MSG_END:
|
||||||
|
switch b {
|
||||||
|
case '\n':
|
||||||
|
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case OP_PLUS:
|
||||||
|
switch b {
|
||||||
|
case 'O', 'o':
|
||||||
|
nc.ps.state = OP_PLUS_O
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PLUS_O:
|
||||||
|
switch b {
|
||||||
|
case 'K', 'k':
|
||||||
|
nc.ps.state = OP_PLUS_OK
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PLUS_OK:
|
||||||
|
switch b {
|
||||||
|
case '\n':
|
||||||
|
nc.processOK()
|
||||||
|
nc.ps.drop, nc.ps.state = 0, OP_START
|
||||||
|
}
|
||||||
|
case OP_MINUS:
|
||||||
|
switch b {
|
||||||
|
case 'E', 'e':
|
||||||
|
nc.ps.state = OP_MINUS_E
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MINUS_E:
|
||||||
|
switch b {
|
||||||
|
case 'R', 'r':
|
||||||
|
nc.ps.state = OP_MINUS_ER
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MINUS_ER:
|
||||||
|
switch b {
|
||||||
|
case 'R', 'r':
|
||||||
|
nc.ps.state = OP_MINUS_ERR
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MINUS_ERR:
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t':
|
||||||
|
nc.ps.state = OP_MINUS_ERR_SPC
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_MINUS_ERR_SPC:
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
nc.ps.state = MINUS_ERR_ARG
|
||||||
|
nc.ps.as = i
|
||||||
|
}
|
||||||
|
case MINUS_ERR_ARG:
|
||||||
|
switch b {
|
||||||
|
case '\r':
|
||||||
|
nc.ps.drop = 1
|
||||||
|
case '\n':
|
||||||
|
var arg []byte
|
||||||
|
if nc.ps.argBuf != nil {
|
||||||
|
arg = nc.ps.argBuf
|
||||||
|
nc.ps.argBuf = nil
|
||||||
|
} else {
|
||||||
|
arg = buf[nc.ps.as : i-nc.ps.drop]
|
||||||
|
}
|
||||||
|
nc.processErr(string(arg))
|
||||||
|
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START
|
||||||
|
default:
|
||||||
|
if nc.ps.argBuf != nil {
|
||||||
|
nc.ps.argBuf = append(nc.ps.argBuf, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OP_P:
|
||||||
|
switch b {
|
||||||
|
case 'I', 'i':
|
||||||
|
nc.ps.state = OP_PI
|
||||||
|
case 'O', 'o':
|
||||||
|
nc.ps.state = OP_PO
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PO:
|
||||||
|
switch b {
|
||||||
|
case 'N', 'n':
|
||||||
|
nc.ps.state = OP_PON
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PON:
|
||||||
|
switch b {
|
||||||
|
case 'G', 'g':
|
||||||
|
nc.ps.state = OP_PONG
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PONG:
|
||||||
|
switch b {
|
||||||
|
case '\n':
|
||||||
|
nc.processPong()
|
||||||
|
nc.ps.drop, nc.ps.state = 0, OP_START
|
||||||
|
}
|
||||||
|
case OP_PI:
|
||||||
|
switch b {
|
||||||
|
case 'N', 'n':
|
||||||
|
nc.ps.state = OP_PIN
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PIN:
|
||||||
|
switch b {
|
||||||
|
case 'G', 'g':
|
||||||
|
nc.ps.state = OP_PING
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_PING:
|
||||||
|
switch b {
|
||||||
|
case '\n':
|
||||||
|
nc.processPing()
|
||||||
|
nc.ps.drop, nc.ps.state = 0, OP_START
|
||||||
|
}
|
||||||
|
case OP_I:
|
||||||
|
switch b {
|
||||||
|
case 'N', 'n':
|
||||||
|
nc.ps.state = OP_IN
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_IN:
|
||||||
|
switch b {
|
||||||
|
case 'F', 'f':
|
||||||
|
nc.ps.state = OP_INF
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_INF:
|
||||||
|
switch b {
|
||||||
|
case 'O', 'o':
|
||||||
|
nc.ps.state = OP_INFO
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_INFO:
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t':
|
||||||
|
nc.ps.state = OP_INFO_SPC
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
case OP_INFO_SPC:
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
nc.ps.state = INFO_ARG
|
||||||
|
nc.ps.as = i
|
||||||
|
}
|
||||||
|
case INFO_ARG:
|
||||||
|
switch b {
|
||||||
|
case '\r':
|
||||||
|
nc.ps.drop = 1
|
||||||
|
case '\n':
|
||||||
|
var arg []byte
|
||||||
|
if nc.ps.argBuf != nil {
|
||||||
|
arg = nc.ps.argBuf
|
||||||
|
nc.ps.argBuf = nil
|
||||||
|
} else {
|
||||||
|
arg = buf[nc.ps.as : i-nc.ps.drop]
|
||||||
|
}
|
||||||
|
nc.processAsyncInfo(arg)
|
||||||
|
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START
|
||||||
|
default:
|
||||||
|
if nc.ps.argBuf != nil {
|
||||||
|
nc.ps.argBuf = append(nc.ps.argBuf, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
goto parseErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for split buffer scenarios
|
||||||
|
if (nc.ps.state == MSG_ARG || nc.ps.state == MINUS_ERR_ARG || nc.ps.state == INFO_ARG) && nc.ps.argBuf == nil {
|
||||||
|
nc.ps.argBuf = nc.ps.scratch[:0]
|
||||||
|
nc.ps.argBuf = append(nc.ps.argBuf, buf[nc.ps.as:i-nc.ps.drop]...)
|
||||||
|
// FIXME, check max len
|
||||||
|
}
|
||||||
|
// Check for split msg
|
||||||
|
if nc.ps.state == MSG_PAYLOAD && nc.ps.msgBuf == nil {
|
||||||
|
// We need to clone the msgArg if it is still referencing the
|
||||||
|
// read buffer and we are not able to process the msg.
|
||||||
|
if nc.ps.argBuf == nil {
|
||||||
|
nc.cloneMsgArg()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we will overflow the scratch buffer, just create a
|
||||||
|
// new buffer to hold the split message.
|
||||||
|
if nc.ps.ma.size > cap(nc.ps.scratch)-len(nc.ps.argBuf) {
|
||||||
|
lrem := len(buf[nc.ps.as:])
|
||||||
|
|
||||||
|
nc.ps.msgBuf = make([]byte, lrem, nc.ps.ma.size)
|
||||||
|
copy(nc.ps.msgBuf, buf[nc.ps.as:])
|
||||||
|
} else {
|
||||||
|
nc.ps.msgBuf = nc.ps.scratch[len(nc.ps.argBuf):len(nc.ps.argBuf)]
|
||||||
|
nc.ps.msgBuf = append(nc.ps.msgBuf, (buf[nc.ps.as:])...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
parseErr:
|
||||||
|
return fmt.Errorf("nats: Parse Error [%d]: '%s'", nc.ps.state, buf[i:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneMsgArg is used when the split buffer scenario has the pubArg in the existing read buffer, but
|
||||||
|
// we need to hold onto it into the next read.
|
||||||
|
func (nc *Conn) cloneMsgArg() {
|
||||||
|
nc.ps.argBuf = nc.ps.scratch[:0]
|
||||||
|
nc.ps.argBuf = append(nc.ps.argBuf, nc.ps.ma.subject...)
|
||||||
|
nc.ps.argBuf = append(nc.ps.argBuf, nc.ps.ma.reply...)
|
||||||
|
nc.ps.ma.subject = nc.ps.argBuf[:len(nc.ps.ma.subject)]
|
||||||
|
if nc.ps.ma.reply != nil {
|
||||||
|
nc.ps.ma.reply = nc.ps.argBuf[len(nc.ps.ma.subject):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const argsLenMax = 4
|
||||||
|
|
||||||
|
func (nc *Conn) processMsgArgs(arg []byte) error {
|
||||||
|
// Unroll splitArgs to avoid runtime/heap issues
|
||||||
|
a := [argsLenMax][]byte{}
|
||||||
|
args := a[:0]
|
||||||
|
start := -1
|
||||||
|
for i, b := range arg {
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t', '\r', '\n':
|
||||||
|
if start >= 0 {
|
||||||
|
args = append(args, arg[start:i])
|
||||||
|
start = -1
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if start < 0 {
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start >= 0 {
|
||||||
|
args = append(args, arg[start:])
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 3:
|
||||||
|
nc.ps.ma.subject = args[0]
|
||||||
|
nc.ps.ma.sid = parseInt64(args[1])
|
||||||
|
nc.ps.ma.reply = nil
|
||||||
|
nc.ps.ma.size = int(parseInt64(args[2]))
|
||||||
|
case 4:
|
||||||
|
nc.ps.ma.subject = args[0]
|
||||||
|
nc.ps.ma.sid = parseInt64(args[1])
|
||||||
|
nc.ps.ma.reply = args[2]
|
||||||
|
nc.ps.ma.size = int(parseInt64(args[3]))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("nats: processMsgArgs Parse Error: '%s'", arg)
|
||||||
|
}
|
||||||
|
if nc.ps.ma.sid < 0 {
|
||||||
|
return fmt.Errorf("nats: processMsgArgs Bad or Missing Sid: '%s'", arg)
|
||||||
|
}
|
||||||
|
if nc.ps.ma.size < 0 {
|
||||||
|
return fmt.Errorf("nats: processMsgArgs Bad or Missing Size: '%s'", arg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ascii numbers 0-9
|
||||||
|
const (
|
||||||
|
ascii_0 = 48
|
||||||
|
ascii_9 = 57
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseInt64 expects decimal positive numbers. We
|
||||||
|
// return -1 to signal error
|
||||||
|
func parseInt64(d []byte) (n int64) {
|
||||||
|
if len(d) == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for _, dec := range d {
|
||||||
|
if dec < ascii_0 || dec > ascii_9 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
n = n*10 + (int64(dec) - ascii_0)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package nats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// global pool of *time.Timer's. can be used by multiple goroutines concurrently.
|
||||||
|
var globalTimerPool timerPool
|
||||||
|
|
||||||
|
// timerPool provides GC-able pooling of *time.Timer's.
|
||||||
|
// can be used by multiple goroutines concurrently.
|
||||||
|
type timerPool struct {
|
||||||
|
p sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a timer that completes after the given duration.
|
||||||
|
func (tp *timerPool) Get(d time.Duration) *time.Timer {
|
||||||
|
if t, _ := tp.p.Get().(*time.Timer); t != nil {
|
||||||
|
t.Reset(d)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.NewTimer(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put pools the given timer.
|
||||||
|
//
|
||||||
|
// There is no need to call t.Stop() before calling Put.
|
||||||
|
//
|
||||||
|
// Put will try to stop the timer before pooling. If the
|
||||||
|
// given timer already expired, Put will read the unreceived
|
||||||
|
// value if there is one.
|
||||||
|
func (tp *timerPool) Put(t *time.Timer) {
|
||||||
|
if !t.Stop() {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.p.Put(t)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2016 Apcera Inc. All rights reserved.
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloneTLSConfig returns a copy of c. Only the exported fields are copied.
|
||||||
|
// This is temporary, until this is provided by the language.
|
||||||
|
// https://go-review.googlesource.com/#/c/28075/
|
||||||
|
func CloneTLSConfig(c *tls.Config) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: c.Rand,
|
||||||
|
Time: c.Time,
|
||||||
|
Certificates: c.Certificates,
|
||||||
|
NameToCertificate: c.NameToCertificate,
|
||||||
|
GetCertificate: c.GetCertificate,
|
||||||
|
RootCAs: c.RootCAs,
|
||||||
|
NextProtos: c.NextProtos,
|
||||||
|
ServerName: c.ServerName,
|
||||||
|
ClientAuth: c.ClientAuth,
|
||||||
|
ClientCAs: c.ClientCAs,
|
||||||
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
|
CipherSuites: c.CipherSuites,
|
||||||
|
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||||
|
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||||
|
SessionTicketKey: c.SessionTicketKey,
|
||||||
|
ClientSessionCache: c.ClientSessionCache,
|
||||||
|
MinVersion: c.MinVersion,
|
||||||
|
MaxVersion: c.MaxVersion,
|
||||||
|
CurvePreferences: c.CurvePreferences,
|
||||||
|
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
||||||
|
Renegotiation: c.Renegotiation,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2016 Apcera Inc. All rights reserved.
|
||||||
|
// +build go1.5,!go1.7
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloneTLSConfig returns a copy of c. Only the exported fields are copied.
|
||||||
|
// This is temporary, until this is provided by the language.
|
||||||
|
// https://go-review.googlesource.com/#/c/28075/
|
||||||
|
func CloneTLSConfig(c *tls.Config) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: c.Rand,
|
||||||
|
Time: c.Time,
|
||||||
|
Certificates: c.Certificates,
|
||||||
|
NameToCertificate: c.NameToCertificate,
|
||||||
|
GetCertificate: c.GetCertificate,
|
||||||
|
RootCAs: c.RootCAs,
|
||||||
|
NextProtos: c.NextProtos,
|
||||||
|
ServerName: c.ServerName,
|
||||||
|
ClientAuth: c.ClientAuth,
|
||||||
|
ClientCAs: c.ClientCAs,
|
||||||
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
|
CipherSuites: c.CipherSuites,
|
||||||
|
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||||
|
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||||
|
SessionTicketKey: c.SessionTicketKey,
|
||||||
|
ClientSessionCache: c.ClientSessionCache,
|
||||||
|
MinVersion: c.MinVersion,
|
||||||
|
MaxVersion: c.MaxVersion,
|
||||||
|
CurvePreferences: c.CurvePreferences,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2016 Apcera Inc. All rights reserved.
|
||||||
|
|
||||||
|
// A unique identifier generator that is high performance, very fast, and tries to be entropy pool friendly.
|
||||||
|
package nuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
prand "math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NUID needs to be very fast to generate and truly unique, all while being entropy pool friendly.
|
||||||
|
// We will use 12 bytes of crypto generated data (entropy draining), and 10 bytes of sequential data
|
||||||
|
// that is started at a pseudo random number and increments with a pseudo-random increment.
|
||||||
|
// Total is 22 bytes of base 62 ascii text :)
|
||||||
|
|
||||||
|
// Version of the library
|
||||||
|
const Version = "1.0.0"
|
||||||
|
|
||||||
|
const (
|
||||||
|
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
base = 62
|
||||||
|
preLen = 12
|
||||||
|
seqLen = 10
|
||||||
|
maxSeq = int64(839299365868340224) // base^seqLen == 62^10
|
||||||
|
minInc = int64(33)
|
||||||
|
maxInc = int64(333)
|
||||||
|
totalLen = preLen + seqLen
|
||||||
|
)
|
||||||
|
|
||||||
|
type NUID struct {
|
||||||
|
pre []byte
|
||||||
|
seq int64
|
||||||
|
inc int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type lockedNUID struct {
|
||||||
|
sync.Mutex
|
||||||
|
*NUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global NUID
|
||||||
|
var globalNUID *lockedNUID
|
||||||
|
|
||||||
|
// Seed sequential random with crypto or math/random and current time
|
||||||
|
// and generate crypto prefix.
|
||||||
|
func init() {
|
||||||
|
r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||||
|
if err != nil {
|
||||||
|
prand.Seed(time.Now().UnixNano())
|
||||||
|
} else {
|
||||||
|
prand.Seed(r.Int64())
|
||||||
|
}
|
||||||
|
globalNUID = &lockedNUID{NUID: New()}
|
||||||
|
globalNUID.RandomizePrefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New will generate a new NUID and properly initialize the prefix, sequential start, and sequential increment.
|
||||||
|
func New() *NUID {
|
||||||
|
n := &NUID{
|
||||||
|
seq: prand.Int63n(maxSeq),
|
||||||
|
inc: minInc + prand.Int63n(maxInc-minInc),
|
||||||
|
pre: make([]byte, preLen),
|
||||||
|
}
|
||||||
|
n.RandomizePrefix()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the next NUID string from the global locked NUID instance.
|
||||||
|
func Next() string {
|
||||||
|
globalNUID.Lock()
|
||||||
|
nuid := globalNUID.Next()
|
||||||
|
globalNUID.Unlock()
|
||||||
|
return nuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the next NUID string.
|
||||||
|
func (n *NUID) Next() string {
|
||||||
|
// Increment and capture.
|
||||||
|
n.seq += n.inc
|
||||||
|
if n.seq >= maxSeq {
|
||||||
|
n.RandomizePrefix()
|
||||||
|
n.resetSequential()
|
||||||
|
}
|
||||||
|
seq := n.seq
|
||||||
|
|
||||||
|
// Copy prefix
|
||||||
|
var b [totalLen]byte
|
||||||
|
bs := b[:preLen]
|
||||||
|
copy(bs, n.pre)
|
||||||
|
|
||||||
|
// copy in the seq in base36.
|
||||||
|
for i, l := len(b), seq; i > preLen; l /= base {
|
||||||
|
i -= 1
|
||||||
|
b[i] = digits[l%base]
|
||||||
|
}
|
||||||
|
return string(b[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets the sequential portion of the NUID.
|
||||||
|
func (n *NUID) resetSequential() {
|
||||||
|
n.seq = prand.Int63n(maxSeq)
|
||||||
|
n.inc = minInc + prand.Int63n(maxInc-minInc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new prefix from crypto/rand.
|
||||||
|
// This call *can* drain entropy and will be called automatically when we exhaust the sequential range.
|
||||||
|
// Will panic if it gets an error from rand.Int()
|
||||||
|
func (n *NUID) RandomizePrefix() {
|
||||||
|
var cb [preLen]byte
|
||||||
|
cbs := cb[:]
|
||||||
|
if nb, err := rand.Read(cbs); nb != preLen || err != nil {
|
||||||
|
panic(fmt.Sprintf("nuid: failed generating crypto random number: %v\n", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < preLen; i++ {
|
||||||
|
n.pre[i] = digits[int(cbs[i])%base]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue