mirror of
https://github.com/minio/minio.git
synced 2025-01-22 20:23:14 -05:00
Add NATS notifier (#2795)
This commit is contained in:
parent
64083b9227
commit
1e6afac3bd
@ -150,9 +150,10 @@ func isMinioSNSConfigured(topicARN string, topicConfigs []topicConfig) bool {
|
||||
// Validate if we recognize the queue type.
|
||||
func isValidQueue(sqsARN arnSQS) bool {
|
||||
amqpQ := isAMQPQueue(sqsARN) // Is amqp queue?.
|
||||
natsQ := isNATSQueue(sqsARN) // Is nats queue?.
|
||||
elasticQ := isElasticQueue(sqsARN) // Is elastic queue?.
|
||||
redisQ := isRedisQueue(sqsARN) // Is redis queue?.
|
||||
return amqpQ || elasticQ || redisQ
|
||||
return amqpQ || natsQ || elasticQ || redisQ
|
||||
}
|
||||
|
||||
// Validate if we recognize the topic type.
|
||||
@ -168,6 +169,9 @@ func isValidQueueID(queueARN string) bool {
|
||||
if isAMQPQueue(sqsARN) { // AMQP eueue.
|
||||
amqpN := serverConfig.GetAMQPNotifyByID(sqsARN.AccountID)
|
||||
return amqpN.Enable && amqpN.URL != ""
|
||||
} else if isNATSQueue(sqsARN) {
|
||||
natsN := serverConfig.GetNATSNotifyByID(sqsARN.AccountID)
|
||||
return natsN.Enable && natsN.Address != ""
|
||||
} else if isElasticQueue(sqsARN) { // Elastic queue.
|
||||
elasticN := serverConfig.GetElasticSearchNotifyByID(sqsARN.AccountID)
|
||||
return elasticN.Enable && elasticN.URL != ""
|
||||
@ -347,6 +351,7 @@ func unmarshalTopicARN(topicARN string) arnTopic {
|
||||
// Unmarshals input value of AWS ARN format into minioSqs object.
|
||||
// Returned value represents minio sqs types, currently supported are
|
||||
// - amqp
|
||||
// - nats
|
||||
// - elasticsearch
|
||||
// - redis
|
||||
func unmarshalSqsARN(queueARN string) (mSqs arnSQS) {
|
||||
@ -358,6 +363,8 @@ func unmarshalSqsARN(queueARN string) (mSqs arnSQS) {
|
||||
switch {
|
||||
case strings.HasSuffix(sqsType, queueTypeAMQP):
|
||||
mSqs.Type = queueTypeAMQP
|
||||
case strings.HasSuffix(sqsType, queueTypeNATS):
|
||||
mSqs.Type = queueTypeNATS
|
||||
case strings.HasSuffix(sqsType, queueTypeElastic):
|
||||
mSqs.Type = queueTypeElastic
|
||||
case strings.HasSuffix(sqsType, queueTypeRedis):
|
||||
|
@ -50,6 +50,10 @@ func migrateConfig() error {
|
||||
if err := migrateV6ToV7(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Migrate version '7' to '8'.
|
||||
if err := migrateV7ToV8(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -378,3 +382,73 @@ func migrateV6ToV7() error {
|
||||
console.Println("Migration from version ‘" + cv6.Version + "’ to ‘" + srvConfig.Version + "’ completed successfully.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version '7' to '8' migrates config, removes previous fields related
|
||||
// to backend types and server address. This change further simplifies
|
||||
// the config for future additions.
|
||||
func migrateV7ToV8() error {
|
||||
cv7, err := loadConfigV7()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unable to load config version ‘7’. %v", err)
|
||||
}
|
||||
if cv7.Version != "7" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save only the new fields, ignore the rest.
|
||||
srvConfig := &serverConfigV8{}
|
||||
srvConfig.Version = globalMinioConfigVersion
|
||||
srvConfig.Credential = cv7.Credential
|
||||
srvConfig.Region = cv7.Region
|
||||
if srvConfig.Region == "" {
|
||||
// Region needs to be set for AWS Signature Version 4.
|
||||
srvConfig.Region = "us-east-1"
|
||||
}
|
||||
srvConfig.Logger.Console = cv7.Logger.Console
|
||||
srvConfig.Logger.File = cv7.Logger.File
|
||||
srvConfig.Logger.Syslog = cv7.Logger.Syslog
|
||||
srvConfig.Notify.AMQP = make(map[string]amqpNotify)
|
||||
srvConfig.Notify.NATS = make(map[string]natsNotify)
|
||||
srvConfig.Notify.ElasticSearch = make(map[string]elasticSearchNotify)
|
||||
srvConfig.Notify.Redis = make(map[string]redisNotify)
|
||||
if len(cv7.Notify.AMQP) == 0 {
|
||||
srvConfig.Notify.AMQP["1"] = amqpNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.AMQP = cv7.Notify.AMQP
|
||||
}
|
||||
if len(cv7.Notify.NATS) == 0 {
|
||||
srvConfig.Notify.NATS["1"] = natsNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.NATS = cv7.Notify.NATS
|
||||
}
|
||||
if len(cv7.Notify.ElasticSearch) == 0 {
|
||||
srvConfig.Notify.ElasticSearch["1"] = elasticSearchNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.ElasticSearch = cv7.Notify.ElasticSearch
|
||||
}
|
||||
if len(cv7.Notify.Redis) == 0 {
|
||||
srvConfig.Notify.Redis["1"] = redisNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.Redis = cv7.Notify.Redis
|
||||
}
|
||||
|
||||
qc, err := quick.New(srvConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to initialize the quick config. %v", err)
|
||||
}
|
||||
configFile, err := getConfigFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get config file. %v", err)
|
||||
}
|
||||
|
||||
err = qc.Save(configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to migrate config from ‘"+cv7.Version+"’ to ‘"+srvConfig.Version+"’ failed. %v", err)
|
||||
}
|
||||
|
||||
console.Println("Migration from version ‘" + cv7.Version + "’ to ‘" + srvConfig.Version + "’ completed successfully.")
|
||||
return nil
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const lastConfigVersion = 7
|
||||
const lastConfigVersion = 8
|
||||
|
||||
// TestServerConfigMigrateV1 - tests if a config v1 is purged
|
||||
func TestServerConfigMigrateV1(t *testing.T) {
|
||||
@ -90,10 +90,14 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) {
|
||||
if err := migrateV6ToV7(); err != nil {
|
||||
t.Fatal("migrate v6 to v7 should succeed when no config file is found")
|
||||
}
|
||||
if err := migrateV7ToV8(); err != nil {
|
||||
t.Fatal("migrate v7 to v8 should succeed when no config file is found")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestServerConfigMigrateV2toV7 - tests if a config from v2 to v7 is successfully done
|
||||
func TestServerConfigMigrateV2toV7(t *testing.T) {
|
||||
// TestServerConfigMigrateV2toV8 - tests if a config from v2 to v8 is successfully done
|
||||
func TestServerConfigMigrateV2toV8(t *testing.T) {
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
@ -184,5 +188,7 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) {
|
||||
if err := migrateV6ToV7(); err == nil {
|
||||
t.Fatal("migrateConfigV6ToV7() should fail with a corrupted json")
|
||||
}
|
||||
|
||||
if err := migrateV7ToV8(); err == nil {
|
||||
t.Fatal("migrateConfigV7ToV8() should fail with a corrupted json")
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio/pkg/quick"
|
||||
)
|
||||
@ -310,3 +311,42 @@ func loadConfigV6() (*configV6, error) {
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// configV7 server configuration version '7'.
|
||||
type serverConfigV7 struct {
|
||||
Version string `json:"version"`
|
||||
|
||||
// S3 API configuration.
|
||||
Credential credential `json:"credential"`
|
||||
Region string `json:"region"`
|
||||
|
||||
// Additional error logging configuration.
|
||||
Logger logger `json:"logger"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify notifier `json:"notify"`
|
||||
|
||||
// Read Write mutex.
|
||||
rwMutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// loadConfigV7 load config version '7'.
|
||||
func loadConfigV7() (*serverConfigV7, error) {
|
||||
configFile, err := getConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = os.Stat(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &serverConfigV7{}
|
||||
c.Version = "7"
|
||||
qc, err := quick.New(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := qc.Load(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ import (
|
||||
"github.com/minio/minio/pkg/quick"
|
||||
)
|
||||
|
||||
// serverConfigV7 server configuration version '7'.
|
||||
type serverConfigV7 struct {
|
||||
// serverConfigV8 server configuration version '8'.
|
||||
type serverConfigV8 struct {
|
||||
Version string `json:"version"`
|
||||
|
||||
// S3 API configuration.
|
||||
@ -45,7 +45,7 @@ type serverConfigV7 struct {
|
||||
func initConfig() error {
|
||||
if !isConfigFileExists() {
|
||||
// Initialize server config.
|
||||
srvCfg := &serverConfigV7{}
|
||||
srvCfg := &serverConfigV8{}
|
||||
srvCfg.Version = globalMinioConfigVersion
|
||||
srvCfg.Region = "us-east-1"
|
||||
srvCfg.Credential = mustGenAccessKeys()
|
||||
@ -63,6 +63,9 @@ func initConfig() error {
|
||||
srvCfg.Notify.ElasticSearch["1"] = elasticSearchNotify{}
|
||||
srvCfg.Notify.Redis = make(map[string]redisNotify)
|
||||
srvCfg.Notify.Redis["1"] = redisNotify{}
|
||||
srvCfg.Notify.NATS = make(map[string]natsNotify)
|
||||
srvCfg.Notify.NATS["1"] = natsNotify{}
|
||||
|
||||
srvCfg.rwMutex = &sync.RWMutex{}
|
||||
|
||||
// Create config path.
|
||||
@ -84,7 +87,7 @@ func initConfig() error {
|
||||
if _, err = os.Stat(configFile); err != nil {
|
||||
return err
|
||||
}
|
||||
srvCfg := &serverConfigV7{}
|
||||
srvCfg := &serverConfigV8{}
|
||||
srvCfg.Version = globalMinioConfigVersion
|
||||
srvCfg.rwMutex = &sync.RWMutex{}
|
||||
qc, err := quick.New(srvCfg)
|
||||
@ -103,10 +106,10 @@ func initConfig() error {
|
||||
}
|
||||
|
||||
// serverConfig server config.
|
||||
var serverConfig *serverConfigV7
|
||||
var serverConfig *serverConfigV8
|
||||
|
||||
// GetVersion get current config version.
|
||||
func (s serverConfigV7) GetVersion() string {
|
||||
func (s serverConfigV8) GetVersion() string {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Version
|
||||
@ -114,135 +117,155 @@ func (s serverConfigV7) GetVersion() string {
|
||||
|
||||
/// Logger related.
|
||||
|
||||
func (s *serverConfigV7) SetAMQPNotifyByID(accountID string, amqpn amqpNotify) {
|
||||
func (s *serverConfigV8) SetAMQPNotifyByID(accountID string, amqpn amqpNotify) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Notify.AMQP[accountID] = amqpn
|
||||
}
|
||||
|
||||
func (s serverConfigV7) GetAMQP() map[string]amqpNotify {
|
||||
func (s serverConfigV8) GetAMQP() map[string]amqpNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.AMQP
|
||||
}
|
||||
|
||||
// GetAMQPNotify get current AMQP logger.
|
||||
func (s serverConfigV7) GetAMQPNotifyByID(accountID string) amqpNotify {
|
||||
func (s serverConfigV8) GetAMQPNotifyByID(accountID string) amqpNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.AMQP[accountID]
|
||||
}
|
||||
|
||||
func (s *serverConfigV7) SetElasticSearchNotifyByID(accountID string, esNotify elasticSearchNotify) {
|
||||
//
|
||||
func (s *serverConfigV8) SetNATSNotifyByID(accountID string, natsn natsNotify) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Notify.NATS[accountID] = natsn
|
||||
}
|
||||
|
||||
func (s serverConfigV8) GetNATS() map[string]natsNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.NATS
|
||||
}
|
||||
|
||||
// GetNATSNotify get current NATS logger.
|
||||
func (s serverConfigV8) GetNATSNotifyByID(accountID string) natsNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.NATS[accountID]
|
||||
}
|
||||
|
||||
func (s *serverConfigV8) SetElasticSearchNotifyByID(accountID string, esNotify elasticSearchNotify) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Notify.ElasticSearch[accountID] = esNotify
|
||||
}
|
||||
|
||||
func (s serverConfigV7) GetElasticSearch() map[string]elasticSearchNotify {
|
||||
func (s serverConfigV8) GetElasticSearch() map[string]elasticSearchNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.ElasticSearch
|
||||
}
|
||||
|
||||
// GetElasticSearchNotify get current ElasicSearch logger.
|
||||
func (s serverConfigV7) GetElasticSearchNotifyByID(accountID string) elasticSearchNotify {
|
||||
func (s serverConfigV8) GetElasticSearchNotifyByID(accountID string) elasticSearchNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.ElasticSearch[accountID]
|
||||
}
|
||||
|
||||
func (s *serverConfigV7) SetRedisNotifyByID(accountID string, rNotify redisNotify) {
|
||||
func (s *serverConfigV8) SetRedisNotifyByID(accountID string, rNotify redisNotify) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Notify.Redis[accountID] = rNotify
|
||||
}
|
||||
|
||||
func (s serverConfigV7) GetRedis() map[string]redisNotify {
|
||||
func (s serverConfigV8) GetRedis() map[string]redisNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.Redis
|
||||
}
|
||||
|
||||
// GetRedisNotify get current Redis logger.
|
||||
func (s serverConfigV7) GetRedisNotifyByID(accountID string) redisNotify {
|
||||
func (s serverConfigV8) GetRedisNotifyByID(accountID string) redisNotify {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Notify.Redis[accountID]
|
||||
}
|
||||
|
||||
// SetFileLogger set new file logger.
|
||||
func (s *serverConfigV7) SetFileLogger(flogger fileLogger) {
|
||||
func (s *serverConfigV8) SetFileLogger(flogger fileLogger) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Logger.File = flogger
|
||||
}
|
||||
|
||||
// GetFileLogger get current file logger.
|
||||
func (s serverConfigV7) GetFileLogger() fileLogger {
|
||||
func (s serverConfigV8) GetFileLogger() fileLogger {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Logger.File
|
||||
}
|
||||
|
||||
// SetConsoleLogger set new console logger.
|
||||
func (s *serverConfigV7) SetConsoleLogger(clogger consoleLogger) {
|
||||
func (s *serverConfigV8) SetConsoleLogger(clogger consoleLogger) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Logger.Console = clogger
|
||||
}
|
||||
|
||||
// GetConsoleLogger get current console logger.
|
||||
func (s serverConfigV7) GetConsoleLogger() consoleLogger {
|
||||
func (s serverConfigV8) GetConsoleLogger() consoleLogger {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Logger.Console
|
||||
}
|
||||
|
||||
// SetSyslogLogger set new syslog logger.
|
||||
func (s *serverConfigV7) SetSyslogLogger(slogger syslogLogger) {
|
||||
func (s *serverConfigV8) SetSyslogLogger(slogger syslogLogger) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Logger.Syslog = slogger
|
||||
}
|
||||
|
||||
// GetSyslogLogger get current syslog logger.
|
||||
func (s *serverConfigV7) GetSyslogLogger() syslogLogger {
|
||||
func (s *serverConfigV8) GetSyslogLogger() syslogLogger {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Logger.Syslog
|
||||
}
|
||||
|
||||
// SetRegion set new region.
|
||||
func (s *serverConfigV7) SetRegion(region string) {
|
||||
func (s *serverConfigV8) SetRegion(region string) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Region = region
|
||||
}
|
||||
|
||||
// GetRegion get current region.
|
||||
func (s serverConfigV7) GetRegion() string {
|
||||
func (s serverConfigV8) GetRegion() string {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Region
|
||||
}
|
||||
|
||||
// SetCredentials set new credentials.
|
||||
func (s *serverConfigV7) SetCredential(creds credential) {
|
||||
func (s *serverConfigV8) SetCredential(creds credential) {
|
||||
s.rwMutex.Lock()
|
||||
defer s.rwMutex.Unlock()
|
||||
s.Credential = creds
|
||||
}
|
||||
|
||||
// GetCredentials get current credentials.
|
||||
func (s serverConfigV7) GetCredential() credential {
|
||||
func (s serverConfigV8) GetCredential() credential {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
return s.Credential
|
||||
}
|
||||
|
||||
// Save config.
|
||||
func (s serverConfigV7) Save() error {
|
||||
func (s serverConfigV8) Save() error {
|
||||
s.rwMutex.RLock()
|
||||
defer s.rwMutex.RUnlock()
|
||||
|
@ -332,6 +332,34 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
queueTargets[queueARN] = amqpLog
|
||||
}
|
||||
// Load all nats targets, initialize their respective loggers.
|
||||
for accountID, natsN := range serverConfig.GetNATS() {
|
||||
if !natsN.Enable {
|
||||
continue
|
||||
}
|
||||
// Construct the queue ARN for NATS.
|
||||
queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueTypeNATS
|
||||
// Queue target if already initialized we move to the next ARN.
|
||||
_, ok := queueTargets[queueARN]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
// Using accountID we can now initialize a new NATS logrus instance.
|
||||
natsLog, err := newNATSNotify(accountID)
|
||||
if err != nil {
|
||||
// Encapsulate network error to be more informative.
|
||||
if _, ok := err.(net.Error); ok {
|
||||
return nil, &net.OpError{
|
||||
Op: "Connecting to " + queueARN,
|
||||
Net: "tcp",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
queueTargets[queueARN] = natsLog
|
||||
}
|
||||
|
||||
// Load redis targets, initialize their respective loggers.
|
||||
for accountID, redisN := range serverConfig.GetRedis() {
|
||||
if !redisN.Enable {
|
||||
|
@ -30,7 +30,7 @@ const (
|
||||
|
||||
// minio configuration related constants.
|
||||
const (
|
||||
globalMinioConfigVersion = "7"
|
||||
globalMinioConfigVersion = "8"
|
||||
globalMinioConfigDir = ".minio"
|
||||
globalMinioCertsDir = "certs"
|
||||
globalMinioCertFile = "public.crt"
|
||||
|
@ -30,6 +30,8 @@ const (
|
||||
|
||||
// Static string indicating queue type 'amqp'.
|
||||
queueTypeAMQP = "amqp"
|
||||
// Static string indicating queue type 'nats'.
|
||||
queueTypeNATS = "nats"
|
||||
// Static string indicating queue type 'elasticsearch'.
|
||||
queueTypeElastic = "elasticsearch"
|
||||
// Static string indicating queue type 'redis'.
|
||||
@ -50,6 +52,7 @@ var errNotifyNotEnabled = errors.New("requested notifier not enabled")
|
||||
// Notifier represents collection of supported notification queues.
|
||||
type notifier struct {
|
||||
AMQP map[string]amqpNotify `json:"amqp"`
|
||||
NATS map[string]natsNotify `json:"nats"`
|
||||
ElasticSearch map[string]elasticSearchNotify `json:"elasticsearch"`
|
||||
Redis map[string]redisNotify `json:"redis"`
|
||||
// Add new notification queues.
|
||||
@ -74,6 +77,25 @@ func isAMQPQueue(sqsArn arnSQS) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true if natsArn is for an NATS queue.
|
||||
func isNATSQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeNATS {
|
||||
return false
|
||||
}
|
||||
natsL := serverConfig.GetNATSNotifyByID(sqsArn.AccountID)
|
||||
if !natsL.Enable {
|
||||
return false
|
||||
}
|
||||
// Connect to nats server to validate.
|
||||
natsC, err := dialNATS(natsL)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to connect to nats service. %#v", natsL)
|
||||
return false
|
||||
}
|
||||
defer natsC.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true if queueArn is for an Redis queue.
|
||||
func isRedisQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeRedis {
|
||||
|
108
cmd/notify-nats.go
Normal file
108
cmd/notify-nats.go
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/nats-io/nats"
|
||||
)
|
||||
|
||||
// natsNotify - represents logrus compatible NATS hook.
|
||||
// All fields represent NATS configuration details.
|
||||
type natsNotify struct {
|
||||
Enable bool `json:"enable"`
|
||||
Address string `json:"address"`
|
||||
Subject string `json:"subject"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Token string `json:"token"`
|
||||
Secure bool `json:"secure"`
|
||||
PingInterval int64 `json:"pingInterval"`
|
||||
}
|
||||
|
||||
type natsConn struct {
|
||||
params natsNotify
|
||||
*nats.Conn
|
||||
}
|
||||
|
||||
// dialNATS - dials and returns an natsConn instance,
|
||||
// for sending notifications. Returns error if nats logger
|
||||
// is not enabled.
|
||||
func dialNATS(natsL natsNotify) (natsConn, error) {
|
||||
if !natsL.Enable {
|
||||
return natsConn{}, errNotifyNotEnabled
|
||||
}
|
||||
// Configure and connect to NATS server
|
||||
natsC := nats.DefaultOptions
|
||||
natsC.Url = "nats://" + natsL.Address
|
||||
natsC.User = natsL.Username
|
||||
natsC.Password = natsL.Password
|
||||
natsC.Token = natsL.Token
|
||||
natsC.Secure = natsL.Secure
|
||||
conn, err := natsC.Connect()
|
||||
if err != nil {
|
||||
return natsConn{}, err
|
||||
}
|
||||
return natsConn{Conn: conn, params: natsL}, nil
|
||||
}
|
||||
|
||||
func newNATSNotify(accountID string) (*logrus.Logger, error) {
|
||||
natsL := serverConfig.GetNATSNotifyByID(accountID)
|
||||
|
||||
// Connect to nats server.
|
||||
natsC, err := dialNATS(natsL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
natsLog := logrus.New()
|
||||
|
||||
// Disable writing to console.
|
||||
natsLog.Out = ioutil.Discard
|
||||
|
||||
// Add a nats hook.
|
||||
natsLog.Hooks.Add(natsC)
|
||||
|
||||
// Set default JSON formatter.
|
||||
natsLog.Formatter = new(logrus.JSONFormatter)
|
||||
|
||||
// Successfully enabled all NATSs.
|
||||
return natsLog, nil
|
||||
}
|
||||
|
||||
// Fire is called when an event should be sent to the message broker
|
||||
func (n natsConn) Fire(entry *logrus.Entry) error {
|
||||
ch := n.Conn
|
||||
body, err := entry.Reader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ch.Publish(n.params.Subject, body.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels is available logging levels.
|
||||
func (n natsConn) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.InfoLevel,
|
||||
}
|
||||
}
|
20
vendor/github.com/nats-io/nats/LICENSE
generated
vendored
Normal file
20
vendor/github.com/nats-io/nats/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012-2016 Apcera Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
322
vendor/github.com/nats-io/nats/README.md
generated
vendored
Normal file
322
vendor/github.com/nats-io/nats/README.md
generated
vendored
Normal file
@ -0,0 +1,322 @@
|
||||
# NATS - Go Client
|
||||
A [Go](http://golang.org) client for the [NATS messaging system](https://nats.io).
|
||||
|
||||
[![License MIT](https://img.shields.io/npm/l/express.svg)](http://opensource.org/licenses/MIT)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/nats-io/nats)](https://goreportcard.com/report/github.com/nats-io/nats) [![Build Status](https://travis-ci.org/nats-io/nats.svg?branch=master)](http://travis-ci.org/nats-io/nats) [![GoDoc](http://godoc.org/github.com/nats-io/nats?status.png)](http://godoc.org/github.com/nats-io/nats) [![Coverage Status](https://coveralls.io/repos/nats-io/nats/badge.svg?branch=master)](https://coveralls.io/r/nats-io/nats?branch=master)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Go client
|
||||
go get github.com/nats-io/nats
|
||||
|
||||
# Server
|
||||
go get github.com/nats-io/gnatsd
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
|
||||
nc, _ := nats.Connect(nats.DefaultURL)
|
||||
|
||||
// Simple Publisher
|
||||
nc.Publish("foo", []byte("Hello World"))
|
||||
|
||||
// Simple Async Subscriber
|
||||
nc.Subscribe("foo", func(m *nats.Msg) {
|
||||
fmt.Printf("Received a message: %s\n", string(m.Data))
|
||||
})
|
||||
|
||||
// Simple Sync Subscriber
|
||||
sub, err := nc.SubscribeSync("foo")
|
||||
m, err := sub.NextMsg(timeout)
|
||||
|
||||
// Channel Subscriber
|
||||
ch := make(chan *nats.Msg, 64)
|
||||
sub, err := nc.ChanSubscribe("foo", ch)
|
||||
msg <- ch
|
||||
|
||||
// Unsubscribe
|
||||
sub.Unsubscribe()
|
||||
|
||||
// Requests
|
||||
msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond)
|
||||
|
||||
// Replies
|
||||
nc.Subscribe("help", func(m *Msg) {
|
||||
nc.Publish(m.Reply, []byte("I can help!"))
|
||||
})
|
||||
|
||||
// Close connection
|
||||
nc := nats.Connect("nats://localhost:4222")
|
||||
nc.Close();
|
||||
```
|
||||
|
||||
## Encoded Connections
|
||||
|
||||
```go
|
||||
|
||||
nc, _ := nats.Connect(nats.DefaultURL)
|
||||
c, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
|
||||
defer c.Close()
|
||||
|
||||
// Simple Publisher
|
||||
c.Publish("foo", "Hello World")
|
||||
|
||||
// Simple Async Subscriber
|
||||
c.Subscribe("foo", func(s string) {
|
||||
fmt.Printf("Received a message: %s\n", s)
|
||||
})
|
||||
|
||||
// EncodedConn can Publish any raw Go type using the registered Encoder
|
||||
type person struct {
|
||||
Name string
|
||||
Address string
|
||||
Age int
|
||||
}
|
||||
|
||||
// Go type Subscriber
|
||||
c.Subscribe("hello", func(p *person) {
|
||||
fmt.Printf("Received a person: %+v\n", p)
|
||||
})
|
||||
|
||||
me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery Street, San Francisco, CA"}
|
||||
|
||||
// Go type Publisher
|
||||
c.Publish("hello", me)
|
||||
|
||||
// Unsubscribe
|
||||
sub, err := c.Subscribe("foo", nil)
|
||||
...
|
||||
sub.Unsubscribe()
|
||||
|
||||
// Requests
|
||||
var response string
|
||||
err := c.Request("help", "help me", &response, 10*time.Millisecond)
|
||||
if err != nil {
|
||||
fmt.Printf("Request failed: %v\n", err)
|
||||
}
|
||||
|
||||
// Replying
|
||||
c.Subscribe("help", func(subj, reply string, msg string) {
|
||||
c.Publish(reply, "I can help!")
|
||||
})
|
||||
|
||||
// Close connection
|
||||
c.Close();
|
||||
```
|
||||
|
||||
## TLS
|
||||
|
||||
```go
|
||||
// tls as a scheme will enable secure connections by default. This will also verify the server name.
|
||||
nc, err := nats.Connect("tls://nats.demo.io:4443")
|
||||
|
||||
// If you are using a self-signed certificate, you need to have a tls.Config with RootCAs setup.
|
||||
// We provide a helper method to make this case easier.
|
||||
nc, err = nats.Connect("tls://localhost:4443", nats.RootCAs("./configs/certs/ca.pem"))
|
||||
|
||||
// If the server requires client certificate, there is an helper function for that too:
|
||||
cert := nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem")
|
||||
nc, err = nats.Connect("tls://localhost:4443", cert)
|
||||
|
||||
// You can also supply a complete tls.Config
|
||||
|
||||
certFile := "./configs/certs/client-cert.pem"
|
||||
keyFile := "./configs/certs/client-key.pem"
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing X509 certificate/key pair: %v", err)
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
ServerName: opts.Host,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: pool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
nc, err = nats.Connect("nats://localhost:4443", nats.Secure(config))
|
||||
if err != nil {
|
||||
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Using Go Channels (netchan)
|
||||
|
||||
```go
|
||||
nc, _ := nats.Connect(nats.DefaultURL)
|
||||
ec, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
|
||||
defer ec.Close()
|
||||
|
||||
type person struct {
|
||||
Name string
|
||||
Address string
|
||||
Age int
|
||||
}
|
||||
|
||||
recvCh := make(chan *person)
|
||||
ec.BindRecvChan("hello", recvCh)
|
||||
|
||||
sendCh := make(chan *person)
|
||||
ec.BindSendChan("hello", sendCh)
|
||||
|
||||
me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery Street"}
|
||||
|
||||
// Send via Go channels
|
||||
sendCh <- me
|
||||
|
||||
// Receive via Go channels
|
||||
who := <- recvCh
|
||||
```
|
||||
|
||||
## Wildcard Subscriptions
|
||||
|
||||
```go
|
||||
|
||||
// "*" matches any token, at any level of the subject.
|
||||
nc.Subscribe("foo.*.baz", func(m *Msg) {
|
||||
fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data));
|
||||
})
|
||||
|
||||
nc.Subscribe("foo.bar.*", func(m *Msg) {
|
||||
fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data));
|
||||
})
|
||||
|
||||
// ">" matches any length of the tail of a subject, and can only be the last token
|
||||
// E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
|
||||
nc.Subscribe("foo.>", func(m *Msg) {
|
||||
fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data));
|
||||
})
|
||||
|
||||
// Matches all of the above
|
||||
nc.Publish("foo.bar.baz", []byte("Hello World"))
|
||||
|
||||
```
|
||||
|
||||
## Queue Groups
|
||||
|
||||
```go
|
||||
// All subscriptions with the same queue name will form a queue group.
|
||||
// Each message will be delivered to only one subscriber per queue group,
|
||||
// using queuing semantics. You can have as many queue groups as you wish.
|
||||
// Normal subscribers will continue to work as expected.
|
||||
|
||||
nc.QueueSubscribe("foo", "job_workers", func(_ *Msg) {
|
||||
received += 1;
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
```go
|
||||
|
||||
// Flush connection to server, returns when all messages have been processed.
|
||||
nc.Flush()
|
||||
fmt.Println("All clear!")
|
||||
|
||||
// FlushTimeout specifies a timeout value as well.
|
||||
err := nc.FlushTimeout(1*time.Second)
|
||||
if err != nil {
|
||||
fmt.Println("All clear!")
|
||||
} else {
|
||||
fmt.Println("Flushed timed out!")
|
||||
}
|
||||
|
||||
// Auto-unsubscribe after MAX_WANTED messages received
|
||||
const MAX_WANTED = 10
|
||||
sub, err := nc.Subscribe("foo")
|
||||
sub.AutoUnsubscribe(MAX_WANTED)
|
||||
|
||||
// Multiple connections
|
||||
nc1 := nats.Connect("nats://host1:4222")
|
||||
nc2 := nats.Connect("nats://host2:4222")
|
||||
|
||||
nc1.Subscribe("foo", func(m *Msg) {
|
||||
fmt.Printf("Received a message: %s\n", string(m.Data))
|
||||
})
|
||||
|
||||
nc2.Publish("foo", []byte("Hello World!"));
|
||||
|
||||
```
|
||||
|
||||
## Clustered Usage
|
||||
|
||||
```go
|
||||
|
||||
var servers = "nats://localhost:1222, nats://localhost:1223, nats://localhost:1224"
|
||||
|
||||
nc, err := nats.Connect(servers)
|
||||
|
||||
// Optionally set ReconnectWait and MaxReconnect attempts.
|
||||
// This example means 10 seconds total per backend.
|
||||
nc, err = nats.Connect(servers, nats.MaxReconnects(5), nats.ReconnectWait(2 * time.Second))
|
||||
|
||||
// Optionally disable randomization of the server pool
|
||||
nc, err = nats.Connect(servers, nats.DontRandomize())
|
||||
|
||||
// Setup callbacks to be notified on disconnects, reconnects and connection closed.
|
||||
nc, err = nats.Connect(servers,
|
||||
nats.DisconnectHandler(func(nc *nats.Conn) {
|
||||
fmt.Printf("Got disconnected!\n")
|
||||
}),
|
||||
nats.ReconnectHandler(func(_ *nats.Conn) {
|
||||
fmt.Printf("Got reconnected to %v!\n", nc.ConnectedUrl())
|
||||
}),
|
||||
nats.ClosedHandler(func(nc *nats.Conn) {
|
||||
fmt.Printf("Connection closed. Reason: %q\n", nc.LastError())
|
||||
})
|
||||
)
|
||||
|
||||
// When connecting to a mesh of servers with auto-discovery capabilities,
|
||||
// you may need to provide a username/password or token in order to connect
|
||||
// to any server in that mesh when authentication is required.
|
||||
// Instead of providing the credentials in the initial URL, you will use
|
||||
// new option setters:
|
||||
nc, err = nats.Connect("nats://localhost:4222", nats.UserInfo("foo", "bar"))
|
||||
|
||||
// For token based authentication:
|
||||
nc, err = nats.Connect("nats://localhost:4222", nats.Token("S3cretT0ken"))
|
||||
|
||||
// You can even pass the two at the same time in case one of the server
|
||||
// in the mesh requires token instead of user name and password.
|
||||
nc, err = nats.Connect("nats://localhost:4222",
|
||||
nats.UserInfo("foo", "bar"),
|
||||
nats.Token("S3cretT0ken"))
|
||||
|
||||
// Note that if credentials are specified in the initial URLs, they take
|
||||
// precedence on the credentials specfied through the options.
|
||||
// For instance, in the connect call below, the client library will use
|
||||
// the user "my" and password "pwd" to connect to locahost:4222, however,
|
||||
// it will use username "foo" and password "bar" when (re)connecting to
|
||||
// a different server URL that it got as part of the auto-discovery.
|
||||
nc, err = nats.Connect("nats://my:pwd@localhost:4222", nats.UserInfo("foo", "bar"))
|
||||
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012-2016 Apcera Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
26
vendor/github.com/nats-io/nats/TODO.md
generated
vendored
Normal file
26
vendor/github.com/nats-io/nats/TODO.md
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
- [ ] Better constructors, options handling
|
||||
- [ ] Functions for callback settings after connection created.
|
||||
- [ ] Better options for subscriptions. Slow Consumer state settable, Go routines vs Inline.
|
||||
- [ ] Move off of channels for subscribers, use syncPool linkedLists, etc with highwater.
|
||||
- [ ] Test for valid subjects on publish and subscribe?
|
||||
- [ ] SyncSubscriber and Next for EncodedConn
|
||||
- [ ] Fast Publisher?
|
||||
- [ ] pooling for structs used? leaky bucket?
|
||||
- [ ] Timeout 0 should work as no timeout
|
||||
- [x] Ping timer
|
||||
- [x] Name in Connect for gnatsd
|
||||
- [x] Asynchronous error handling
|
||||
- [x] Parser rewrite
|
||||
- [x] Reconnect
|
||||
- [x] Hide Lock
|
||||
- [x] Easier encoder interface
|
||||
- [x] QueueSubscribeSync
|
||||
- [x] Make nats specific errors prefixed with 'nats:'
|
||||
- [x] API test for closed connection
|
||||
- [x] TLS/SSL
|
||||
- [x] Stats collection
|
||||
- [x] Disconnect detection
|
||||
- [x] Optimized Publish (coalescing)
|
||||
- [x] Do Examples via Go style
|
||||
- [x] Standardized Errors
|
249
vendor/github.com/nats-io/nats/enc.go
generated
vendored
Normal file
249
vendor/github.com/nats-io/nats/enc.go
generated
vendored
Normal file
@ -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/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-marshalling 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/nats/encoders/builtin/default_enc.go
generated
vendored
Normal file
106
vendor/github.com/nats-io/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)
|
||||
}
|
||||
}
|
34
vendor/github.com/nats-io/nats/encoders/builtin/gob_enc.go
generated
vendored
Normal file
34
vendor/github.com/nats-io/nats/encoders/builtin/gob_enc.go
generated
vendored
Normal file
@ -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
|
||||
}
|
45
vendor/github.com/nats-io/nats/encoders/builtin/json_enc.go
generated
vendored
Normal file
45
vendor/github.com/nats-io/nats/encoders/builtin/json_enc.go
generated
vendored
Normal file
@ -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
|
||||
}
|
2571
vendor/github.com/nats-io/nats/nats.go
generated
vendored
Normal file
2571
vendor/github.com/nats-io/nats/nats.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
100
vendor/github.com/nats-io/nats/netchan.go
generated
vendored
Normal file
100
vendor/github.com/nats-io/nats/netchan.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2013-2014 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)
|
||||
}
|
470
vendor/github.com/nats-io/nats/parser.go
generated
vendored
Normal file
470
vendor/github.com/nats-io/nats/parser.go
generated
vendored
Normal file
@ -0,0 +1,470 @@
|
||||
// Copyright 2012-2014 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
|
||||
}
|
21
vendor/github.com/nats-io/nuid/LICENSE
generated
vendored
Normal file
21
vendor/github.com/nats-io/nuid/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012-2016 Apcera Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
66
vendor/github.com/nats-io/nuid/README.md
generated
vendored
Normal file
66
vendor/github.com/nats-io/nuid/README.md
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
# NUID
|
||||
|
||||
[![License MIT](https://img.shields.io/npm/l/express.svg)](http://opensource.org/licenses/MIT)
|
||||
[![ReportCard](http://goreportcard.com/badge/nats-io/nuid)](http://goreportcard.com/report/nats-io/nuid)
|
||||
[![Build Status](https://travis-ci.org/nats-io/nuid.svg?branch=master)](http://travis-ci.org/nats-io/nuid)
|
||||
[![Release](https://img.shields.io/badge/release-v1.0.0-1eb0fc.svg)](https://github.com/nats-io/nuid/releases/tag/v1.0.0)
|
||||
[![GoDoc](http://godoc.org/github.com/nats-io/nuid?status.png)](http://godoc.org/github.com/nats-io/nuid)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/nats-io/nuid/badge.svg?branch=master)](https://coveralls.io/github/nats-io/nuid?branch=master)
|
||||
|
||||
A highly performant unique identifier generator.
|
||||
|
||||
## Installation
|
||||
|
||||
Use the `go` command:
|
||||
|
||||
$ go get github.com/nats-io/nuid
|
||||
|
||||
## Basic Usage
|
||||
```go
|
||||
|
||||
// Utilize the global locked instance
|
||||
nuid := nuid.Next()
|
||||
|
||||
// Create an instance, these are not locked.
|
||||
n := nuid.New()
|
||||
nuid = n.Next()
|
||||
|
||||
// Generate a new crypto/rand seeded prefix.
|
||||
// Generally not needed, happens automatically.
|
||||
n.RandomizePrefix()
|
||||
```
|
||||
|
||||
## Performance
|
||||
NUID needs to be very fast to generate and be truly unique, all while being entropy pool friendly.
|
||||
NUID uses 12 bytes of crypto generated data (entropy draining), and 10 bytes of pseudo-random
|
||||
sequential data that increments with a pseudo-random increment.
|
||||
|
||||
Total length of a NUID string is 22 bytes of base 36 ascii text, so 36^22 or
|
||||
17324272922341479351919144385642496 possibilities.
|
||||
|
||||
NUID can generate identifiers as fast as 60ns, or ~16 million per second. There is an associated
|
||||
benchmark you can use to test performance on your own hardware.
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2016 Apcera Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
124
vendor/github.com/nats-io/nuid/nuid.go
generated
vendored
Normal file
124
vendor/github.com/nats-io/nuid/nuid.go
generated
vendored
Normal file
@ -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]
|
||||
}
|
||||
}
|
18
vendor/vendor.json
vendored
18
vendor/vendor.json
vendored
@ -142,6 +142,24 @@
|
||||
"revision": "6f50cd1d784b2bea46167b6929f16c0d12eefbfb",
|
||||
"revisionTime": "2016-08-16T22:25:11Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Nj7vQ2GlvJiPP7sqJX5AurrDSD4=",
|
||||
"path": "github.com/nats-io/nats",
|
||||
"revision": "70b70be17b77e8da86b6a3bcfe94fb22718a8dd0",
|
||||
"revisionTime": "2016-09-16T18:17:35Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "qAIuHdtQrDi2fFmpaP1dZLbgpGc=",
|
||||
"path": "github.com/nats-io/nats/encoders/builtin",
|
||||
"revision": "70b70be17b77e8da86b6a3bcfe94fb22718a8dd0",
|
||||
"revisionTime": "2016-09-16T18:17:35Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "i8Yom1KrpDKwjlGH/gpJGAQmo68=",
|
||||
"path": "github.com/nats-io/nuid",
|
||||
"revision": "289cccf02c178dc782430d534e3c1f5b72af807f",
|
||||
"revisionTime": "2016-09-27T04:49:45Z"
|
||||
},
|
||||
{
|
||||
"path": "github.com/pkg/profile",
|
||||
"revision": "c78aac22bd43883fd2817833b982153dcac17b3b",
|
||||
|
Loading…
x
Reference in New Issue
Block a user