├── encoders ├── protobuf │ ├── testdata │ │ ├── pbtest.proto │ │ └── pbtest.pb.go │ ├── protobuf_enc.go │ └── protobuf_test.go └── builtin │ ├── gob_enc.go │ ├── json_enc.go │ ├── default_enc.go │ ├── gob_test.go │ ├── json_test.go │ └── enc_test.go ├── test ├── configs │ ├── tls.conf │ ├── tlsverify.conf │ └── certs │ │ ├── client-cert.pem │ │ ├── server.pem │ │ ├── ca.pem │ │ ├── client-key.pem │ │ └── key.pem ├── test.go ├── bench_test.go ├── auth_test.go ├── netchan_test.go ├── cluster_test.go ├── reconnect_test.go └── basic_test.go ├── .gitignore ├── .travis.yml ├── scripts └── cov.sh ├── examples ├── nats-pub.go ├── nats-req.go ├── nats-sub.go ├── nats-rply.go ├── nats-qsub.go └── nats-bench.go ├── TODO.md ├── LICENSE ├── util ├── tls_pre17.go └── tls.go ├── netchan.go ├── bench ├── benchlib_test.go └── bench.go ├── example_test.go ├── enc_test.go ├── enc.go ├── README.md └── parser.go /encoders/protobuf/testdata/pbtest.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package testdata; 4 | 5 | message Person { 6 | string name = 1; 7 | int32 age = 2; 8 | string address = 3; 9 | 10 | map children = 10; 11 | } 12 | -------------------------------------------------------------------------------- /test/configs/tls.conf: -------------------------------------------------------------------------------- 1 | 2 | # Simple TLS config file 3 | 4 | port: 4443 5 | net: localhost # net interface 6 | 7 | tls { 8 | cert_file: "./configs/certs/server.pem" 9 | key_file: "./configs/certs/key.pem" 10 | timeout: 2 11 | } 12 | 13 | authorization { 14 | user: derek 15 | password: buckley 16 | timeout: 1 17 | } 18 | -------------------------------------------------------------------------------- /test/configs/tlsverify.conf: -------------------------------------------------------------------------------- 1 | 2 | # Simple TLS config file 3 | 4 | port: 4443 5 | net: localhost 6 | 7 | tls { 8 | cert_file: "./configs/certs/server.pem" 9 | key_file: "./configs/certs/key.pem" 10 | timeout: 2 11 | 12 | # Optional certificate authority for clients 13 | ca_file: "./configs/certs/ca.pem" 14 | 15 | # Require a client certificate 16 | verify: true 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | # Emacs 25 | *~ 26 | \#*\# 27 | .\#* 28 | 29 | # vi/vim 30 | .??*.swp 31 | 32 | # Mac 33 | .DS_Store 34 | 35 | # Eclipse 36 | .project 37 | .settings/ 38 | 39 | # bin 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.6.3 5 | - 1.7.3 6 | install: 7 | - go get -t ./... 8 | - go get github.com/nats-io/gnatsd 9 | - go get github.com/mattn/goveralls 10 | - go get github.com/wadey/gocovmerge 11 | - go get honnef.co/go/staticcheck/cmd/staticcheck 12 | script: 13 | - go fmt ./... 14 | - go vet ./... 15 | - go test -i -race ./... 16 | - go test -v -race ./... 17 | - staticcheck -ignore="github.com/nats-io/go-nats/*_test.go:SA2002 github.com/nats-io/go-nats/*/*_test.go:SA2002" ./... 18 | after_script: 19 | - if [ "$TRAVIS_GO_VERSION" = "1.7.3" ]; then ./scripts/cov.sh; fi 20 | -------------------------------------------------------------------------------- /scripts/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Run from directory above via ./scripts/cov.sh 3 | 4 | rm -rf ./cov 5 | mkdir cov 6 | go test -v -covermode=atomic -coverprofile=./cov/nats.out 7 | go test -v -covermode=atomic -coverprofile=./cov/builtin.out ./encoders/builtin 8 | go test -v -covermode=atomic -coverprofile=./cov/protobuf.out ./encoders/protobuf 9 | go test -v -covermode=atomic -coverprofile=./cov/test.out -coverpkg=github.com/nats-io/go-nats ./test 10 | gocovmerge ./cov/*.out > acc.out 11 | rm -rf ./cov 12 | 13 | # If we have an arg, assume travis run and push to coveralls. Otherwise launch browser results 14 | if [[ -n $1 ]]; then 15 | $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci 16 | rm -rf ./acc.out 17 | else 18 | go tool cover -html=acc.out 19 | fi 20 | -------------------------------------------------------------------------------- /encoders/builtin/gob_enc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Apcera Inc. All rights reserved. 2 | 3 | package builtin 4 | 5 | import ( 6 | "bytes" 7 | "encoding/gob" 8 | ) 9 | 10 | // GobEncoder is a Go specific GOB Encoder implementation for EncodedConn. 11 | // This encoder will use the builtin encoding/gob to Marshal 12 | // and Unmarshal most types, including structs. 13 | type GobEncoder struct { 14 | // Empty 15 | } 16 | 17 | // FIXME(dlc) - This could probably be more efficient. 18 | 19 | // Encode 20 | func (ge *GobEncoder) Encode(subject string, v interface{}) ([]byte, error) { 21 | b := new(bytes.Buffer) 22 | enc := gob.NewEncoder(b) 23 | if err := enc.Encode(v); err != nil { 24 | return nil, err 25 | } 26 | return b.Bytes(), nil 27 | } 28 | 29 | // Decode 30 | func (ge *GobEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) { 31 | dec := gob.NewDecoder(bytes.NewBuffer(data)) 32 | err = dec.Decode(vPtr) 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /examples/nats-pub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2016 Apcera Inc. All rights reserved. 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "log" 9 | 10 | "github.com/nats-io/go-nats" 11 | ) 12 | 13 | // NOTE: Use tls scheme for TLS, e.g. nats-pub -s tls://demo.nats.io:4443 foo hello 14 | func usage() { 15 | log.Fatalf("Usage: nats-pub [-s server (%s)] \n", nats.DefaultURL) 16 | } 17 | 18 | func main() { 19 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 20 | 21 | log.SetFlags(0) 22 | flag.Usage = usage 23 | flag.Parse() 24 | 25 | args := flag.Args() 26 | if len(args) < 2 { 27 | usage() 28 | } 29 | 30 | nc, err := nats.Connect(*urls) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | defer nc.Close() 35 | 36 | subj, msg := args[0], []byte(args[1]) 37 | 38 | nc.Publish(subj, msg) 39 | nc.Flush() 40 | 41 | if err := nc.LastError(); err != nil { 42 | log.Fatal(err) 43 | } else { 44 | log.Printf("Published [%s] : '%s'\n", subj, msg) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | - [ ] Better constructors, options handling 3 | - [ ] Functions for callback settings after connection created. 4 | - [ ] Better options for subscriptions. Slow Consumer state settable, Go routines vs Inline. 5 | - [ ] Move off of channels for subscribers, use syncPool linkedLists, etc with highwater. 6 | - [ ] Test for valid subjects on publish and subscribe? 7 | - [ ] SyncSubscriber and Next for EncodedConn 8 | - [ ] Fast Publisher? 9 | - [ ] pooling for structs used? leaky bucket? 10 | - [ ] Timeout 0 should work as no timeout 11 | - [x] Ping timer 12 | - [x] Name in Connect for gnatsd 13 | - [x] Asynchronous error handling 14 | - [x] Parser rewrite 15 | - [x] Reconnect 16 | - [x] Hide Lock 17 | - [x] Easier encoder interface 18 | - [x] QueueSubscribeSync 19 | - [x] Make nats specific errors prefixed with 'nats:' 20 | - [x] API test for closed connection 21 | - [x] TLS/SSL 22 | - [x] Stats collection 23 | - [x] Disconnect detection 24 | - [x] Optimized Publish (coalescing) 25 | - [x] Do Examples via Go style 26 | - [x] Standardized Errors 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2016 Apcera Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/nats-req.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2016 Apcera Inc. All rights reserved. 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "log" 9 | "time" 10 | 11 | "github.com/nats-io/go-nats" 12 | ) 13 | 14 | // NOTE: Use tls scheme for TLS, e.g. nats-req -s tls://demo.nats.io:4443 foo hello 15 | func usage() { 16 | log.Fatalf("Usage: nats-req [-s server (%s)] \n", nats.DefaultURL) 17 | } 18 | 19 | func main() { 20 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 21 | 22 | log.SetFlags(0) 23 | flag.Usage = usage 24 | flag.Parse() 25 | 26 | args := flag.Args() 27 | if len(args) < 2 { 28 | usage() 29 | } 30 | 31 | nc, err := nats.Connect(*urls) 32 | if err != nil { 33 | log.Fatalf("Can't connect: %v\n", err) 34 | } 35 | defer nc.Close() 36 | subj, payload := args[0], []byte(args[1]) 37 | 38 | msg, err := nc.Request(subj, []byte(payload), 100*time.Millisecond) 39 | if err != nil { 40 | if nc.LastError() != nil { 41 | log.Fatalf("Error in Request: %v\n", nc.LastError()) 42 | } 43 | log.Fatalf("Error in Request: %v\n", err) 44 | } 45 | 46 | log.Printf("Published [%s] : '%s'\n", subj, payload) 47 | log.Printf("Received [%v] : '%s'\n", msg.Subject, string(msg.Data)) 48 | } 49 | -------------------------------------------------------------------------------- /encoders/builtin/json_enc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Apcera Inc. All rights reserved. 2 | 3 | package builtin 4 | 5 | import ( 6 | "encoding/json" 7 | "strings" 8 | ) 9 | 10 | // JsonEncoder is a JSON Encoder implementation for EncodedConn. 11 | // This encoder will use the builtin encoding/json to Marshal 12 | // and Unmarshal most types, including structs. 13 | type JsonEncoder struct { 14 | // Empty 15 | } 16 | 17 | // Encode 18 | func (je *JsonEncoder) Encode(subject string, v interface{}) ([]byte, error) { 19 | b, err := json.Marshal(v) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return b, nil 24 | } 25 | 26 | // Decode 27 | func (je *JsonEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) { 28 | switch arg := vPtr.(type) { 29 | case *string: 30 | // If they want a string and it is a JSON string, strip quotes 31 | // This allows someone to send a struct but receive as a plain string 32 | // This cast should be efficient for Go 1.3 and beyond. 33 | str := string(data) 34 | if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) { 35 | *arg = str[1 : len(str)-1] 36 | } else { 37 | *arg = str 38 | } 39 | case *[]byte: 40 | *arg = data 41 | default: 42 | err = json.Unmarshal(data, arg) 43 | } 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /encoders/protobuf/testdata/pbtest.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: pbtest.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package testdata is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | pbtest.proto 10 | 11 | It has these top-level messages: 12 | Person 13 | */ 14 | package testdata 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | 21 | type Person struct { 22 | Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 23 | Age int32 `protobuf:"varint,2,opt,name=age" json:"age,omitempty"` 24 | Address string `protobuf:"bytes,3,opt,name=address" json:"address,omitempty"` 25 | Children map[string]*Person `protobuf:"bytes,10,rep,name=children" json:"children,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 26 | } 27 | 28 | func (m *Person) Reset() { *m = Person{} } 29 | func (m *Person) String() string { return proto.CompactTextString(m) } 30 | func (*Person) ProtoMessage() {} 31 | 32 | func (m *Person) GetChildren() map[string]*Person { 33 | if m != nil { 34 | return m.Children 35 | } 36 | return nil 37 | } 38 | 39 | func init() { 40 | } 41 | -------------------------------------------------------------------------------- /examples/nats-sub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2016 Apcera Inc. All rights reserved. 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "log" 9 | "runtime" 10 | 11 | "github.com/nats-io/go-nats" 12 | ) 13 | 14 | // NOTE: Use tls scheme for TLS, e.g. nats-sub -s tls://demo.nats.io:4443 foo 15 | func usage() { 16 | log.Fatalf("Usage: nats-sub [-s server] [-t] \n") 17 | } 18 | 19 | func printMsg(m *nats.Msg, i int) { 20 | log.Printf("[#%d] Received on [%s]: '%s'\n", i, m.Subject, string(m.Data)) 21 | } 22 | 23 | func main() { 24 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 25 | var showTime = flag.Bool("t", false, "Display timestamps") 26 | 27 | log.SetFlags(0) 28 | flag.Usage = usage 29 | flag.Parse() 30 | 31 | args := flag.Args() 32 | if len(args) < 1 { 33 | usage() 34 | } 35 | 36 | nc, err := nats.Connect(*urls) 37 | if err != nil { 38 | log.Fatalf("Can't connect: %v\n", err) 39 | } 40 | 41 | subj, i := args[0], 0 42 | 43 | nc.Subscribe(subj, func(msg *nats.Msg) { 44 | i += 1 45 | printMsg(msg, i) 46 | }) 47 | nc.Flush() 48 | 49 | if err := nc.LastError(); err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | log.Printf("Listening on [%s]\n", subj) 54 | if *showTime { 55 | log.SetFlags(log.LstdFlags) 56 | } 57 | 58 | runtime.Goexit() 59 | } 60 | -------------------------------------------------------------------------------- /util/tls_pre17.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Apcera Inc. All rights reserved. 2 | // +build go1.5,!go1.7 3 | 4 | package util 5 | 6 | import ( 7 | "crypto/tls" 8 | ) 9 | 10 | // CloneTLSConfig returns a copy of c. Only the exported fields are copied. 11 | // This is temporary, until this is provided by the language. 12 | // https://go-review.googlesource.com/#/c/28075/ 13 | func CloneTLSConfig(c *tls.Config) *tls.Config { 14 | return &tls.Config{ 15 | Rand: c.Rand, 16 | Time: c.Time, 17 | Certificates: c.Certificates, 18 | NameToCertificate: c.NameToCertificate, 19 | GetCertificate: c.GetCertificate, 20 | RootCAs: c.RootCAs, 21 | NextProtos: c.NextProtos, 22 | ServerName: c.ServerName, 23 | ClientAuth: c.ClientAuth, 24 | ClientCAs: c.ClientCAs, 25 | InsecureSkipVerify: c.InsecureSkipVerify, 26 | CipherSuites: c.CipherSuites, 27 | PreferServerCipherSuites: c.PreferServerCipherSuites, 28 | SessionTicketsDisabled: c.SessionTicketsDisabled, 29 | SessionTicketKey: c.SessionTicketKey, 30 | ClientSessionCache: c.ClientSessionCache, 31 | MinVersion: c.MinVersion, 32 | MaxVersion: c.MaxVersion, 33 | CurvePreferences: c.CurvePreferences, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/nats-rply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2016 Apcera Inc. All rights reserved. 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "log" 9 | "runtime" 10 | 11 | "github.com/nats-io/go-nats" 12 | ) 13 | 14 | // NOTE: Use tls scheme for TLS, e.g. nats-rply -s tls://demo.nats.io:4443 foo hello 15 | func usage() { 16 | log.Fatalf("Usage: nats-rply [-s server][-t] \n") 17 | } 18 | 19 | func printMsg(m *nats.Msg, i int) { 20 | log.Printf("[#%d] Received on [%s]: '%s'\n", i, m.Subject, string(m.Data)) 21 | } 22 | 23 | func main() { 24 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 25 | var showTime = flag.Bool("t", false, "Display timestamps") 26 | 27 | log.SetFlags(0) 28 | flag.Usage = usage 29 | flag.Parse() 30 | 31 | args := flag.Args() 32 | if len(args) < 2 { 33 | usage() 34 | } 35 | 36 | nc, err := nats.Connect(*urls) 37 | if err != nil { 38 | log.Fatalf("Can't connect: %v\n", err) 39 | } 40 | 41 | subj, reply, i := args[0], args[1], 0 42 | 43 | nc.Subscribe(subj, func(msg *nats.Msg) { 44 | i++ 45 | printMsg(msg, i) 46 | nc.Publish(msg.Reply, []byte(reply)) 47 | }) 48 | nc.Flush() 49 | 50 | if err := nc.LastError(); err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | log.Printf("Listening on [%s]\n", subj) 55 | if *showTime { 56 | log.SetFlags(log.LstdFlags) 57 | } 58 | 59 | runtime.Goexit() 60 | } 61 | -------------------------------------------------------------------------------- /examples/nats-qsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2016 Apcera Inc. All rights reserved. 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "log" 9 | "os" 10 | "runtime" 11 | 12 | "github.com/nats-io/go-nats" 13 | ) 14 | 15 | // NOTE: Use tls scheme for TLS, e.g. nats-qsub -s tls://demo.nats.io:4443 foo 16 | func usage() { 17 | log.Fatalf("Usage: nats-qsub [-s server] [-t] \n") 18 | } 19 | 20 | func printMsg(m *nats.Msg, i int) { 21 | log.Printf("[#%d] Received on [%s] Queue[%s] Pid[%d]: '%s'\n", i, m.Subject, m.Sub.Queue, os.Getpid(), string(m.Data)) 22 | } 23 | 24 | func main() { 25 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 26 | var showTime = flag.Bool("t", false, "Display timestamps") 27 | 28 | log.SetFlags(0) 29 | flag.Usage = usage 30 | flag.Parse() 31 | 32 | args := flag.Args() 33 | if len(args) < 2 { 34 | usage() 35 | } 36 | 37 | nc, err := nats.Connect(*urls) 38 | if err != nil { 39 | log.Fatalf("Can't connect: %v\n", err) 40 | } 41 | 42 | subj, queue, i := args[0], args[1], 0 43 | 44 | nc.QueueSubscribe(subj, queue, func(msg *nats.Msg) { 45 | i++ 46 | printMsg(msg, i) 47 | }) 48 | nc.Flush() 49 | 50 | if err := nc.LastError(); err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | log.Printf("Listening on [%s]\n", subj) 55 | if *showTime { 56 | log.SetFlags(log.LstdFlags) 57 | } 58 | 59 | runtime.Goexit() 60 | } 61 | -------------------------------------------------------------------------------- /util/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Apcera Inc. All rights reserved. 2 | // +build go1.7 3 | 4 | package util 5 | 6 | import ( 7 | "crypto/tls" 8 | ) 9 | 10 | // CloneTLSConfig returns a copy of c. Only the exported fields are copied. 11 | // This is temporary, until this is provided by the language. 12 | // https://go-review.googlesource.com/#/c/28075/ 13 | func CloneTLSConfig(c *tls.Config) *tls.Config { 14 | return &tls.Config{ 15 | Rand: c.Rand, 16 | Time: c.Time, 17 | Certificates: c.Certificates, 18 | NameToCertificate: c.NameToCertificate, 19 | GetCertificate: c.GetCertificate, 20 | RootCAs: c.RootCAs, 21 | NextProtos: c.NextProtos, 22 | ServerName: c.ServerName, 23 | ClientAuth: c.ClientAuth, 24 | ClientCAs: c.ClientCAs, 25 | InsecureSkipVerify: c.InsecureSkipVerify, 26 | CipherSuites: c.CipherSuites, 27 | PreferServerCipherSuites: c.PreferServerCipherSuites, 28 | SessionTicketsDisabled: c.SessionTicketsDisabled, 29 | SessionTicketKey: c.SessionTicketKey, 30 | ClientSessionCache: c.ClientSessionCache, 31 | MinVersion: c.MinVersion, 32 | MaxVersion: c.MaxVersion, 33 | CurvePreferences: c.CurvePreferences, 34 | DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, 35 | Renegotiation: c.Renegotiation, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /encoders/protobuf/protobuf_enc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Apcera Inc. All rights reserved. 2 | 3 | package protobuf 4 | 5 | import ( 6 | "errors" 7 | 8 | "github.com/golang/protobuf/proto" 9 | "github.com/nats-io/go-nats" 10 | ) 11 | 12 | // Additional index for registered Encoders. 13 | const ( 14 | PROTOBUF_ENCODER = "protobuf" 15 | ) 16 | 17 | func init() { 18 | // Register protobuf encoder 19 | nats.RegisterEncoder(PROTOBUF_ENCODER, &ProtobufEncoder{}) 20 | } 21 | 22 | // ProtobufEncoder is a protobuf implementation for EncodedConn 23 | // This encoder will use the builtin protobuf lib to Marshal 24 | // and Unmarshal structs. 25 | type ProtobufEncoder struct { 26 | // Empty 27 | } 28 | 29 | var ( 30 | ErrInvalidProtoMsgEncode = errors.New("nats: Invalid protobuf proto.Message object passed to encode") 31 | ErrInvalidProtoMsgDecode = errors.New("nats: Invalid protobuf proto.Message object passed to decode") 32 | ) 33 | 34 | // Encode 35 | func (pb *ProtobufEncoder) Encode(subject string, v interface{}) ([]byte, error) { 36 | if v == nil { 37 | return nil, nil 38 | } 39 | i, found := v.(proto.Message) 40 | if !found { 41 | return nil, ErrInvalidProtoMsgEncode 42 | } 43 | 44 | b, err := proto.Marshal(i) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return b, nil 49 | } 50 | 51 | // Decode 52 | func (pb *ProtobufEncoder) Decode(subject string, data []byte, vPtr interface{}) error { 53 | if _, ok := vPtr.(*interface{}); ok { 54 | return nil 55 | } 56 | i, found := vPtr.(proto.Message) 57 | if !found { 58 | return ErrInvalidProtoMsgDecode 59 | } 60 | 61 | err := proto.Unmarshal(data, i) 62 | if err != nil { 63 | return err 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /test/configs/certs/client-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPDCCAySgAwIBAgIJAO+k4G7bNTypMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR 4 | BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv 5 | Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy 6 | MzEwNDdaFw0xOTExMDQyMzEwNDdaMBYxFDASBgNVBAMTC25hdHMtY2xpZW50MIIC 7 | IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArgLxszD5/vDrDUwwIEgQx9I0 8 | J/H6MXPO0Tj9D2BnR+nwjCe9M03fsq4Il96BVzoaAiAQD1r4NyAX2adKydlnE3/m 9 | bUFiSVHErJceEi9aSs+WlLdmKEgU2qrsIal9KzthlI786qtjb7OFSCxP14R4xYA5 10 | dlZXhJ9oUuFhVTdaVmRMzWuWj8RbBx8VptSZ0f7Q+Uv8GuB0kyiVkv6GYcH/IWuI 11 | 7jnM0QcVWBmxJfWmqd0yx/FLlX/LRXqdiyoFSIlMaP0VOwto3uEhAoBk83Z+/zrZ 12 | Brymx1Nnz3qzTCf8/mdMjPuWibXDTLbo0/Kf6neHs6wxx8irb1ZfIwhn8grXTcgd 13 | rg9bfcyyUOBey7QXiedpU0xFqoH26E+Aq+CV4R56i1sJKsSYEGu8O69H8zu5dgan 14 | LZRhcCHcZhMe7Nbiu5BcuOW4r3rGDMTLXSugEX91iy5jJaYmRjtPN5imQIJtf+GK 15 | Vq7YLv4MQV6R3xRiZXaocCae1qzIMc4kxCKvZTmxuJsvIUPjNnGumwbjV/a2fLFX 16 | 9tMqUKyEmiPtFtqNH/kmkHCQ5FGYIIj3wGuD5yWfK5Tr3iHOdNJoNNPgPBg9tMRw 17 | j3+W8+uyBxc+FUEb8a9m3R4VmAYyiqgzCA0DWZBF1fOYLWfRnwS5OBKiP4OUlUEb 18 | YZUEzfvDbLOwQrb123cCAwEAAaMXMBUwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ 19 | KoZIhvcNAQELBQADggIBACNKPbvaXwl5rRTqFw37Am1r6e+LkUg9dFogSwXDnuT/ 20 | RRZJi5MHsC5MUOkHB28lTmPwkAogs+LBmKrM0Npzk6OPkT/LCgKqpVoz2Tc1nGMI 21 | Jy8jxPYogMmDCOhoEoC7zsWABMLiX5KDAuKommk61w7AwKu4kK198ngwbfF2fzdH 22 | 1DUGID7iV4fyPGI+pCU3Ullv51c5xkhqjVy1JYdYc0+s6rFyVTibSABa7PfHE2ML 23 | A+cNFWoKQhugVHQU7qYvuWvnEqZro2T6nmSmpK3oOaUgVnDuY2q4JwiMbZAtuyD7 24 | 8LFwCim49WzgYcfs/BwKlUrTV/QBYurruHWjElZzwA39/ZlbnOjJJ85j/YqxR+4S 25 | fK/KktegyrPJU3fxdl2+77zVlfgzxaQ//58vx5LgXWhl2KeHyakeD0jQFVn1R7GD 26 | bynAlHlSOr+nGkwP2WVqXKf+l/gb/gUEY7bC8fCVRCctkcK+smEl+sIKH3O9JY8l 27 | rBWjOXkMY91ZDh77hfTNni/s2/DGAoNrEft8rgu3/NPxhCTfQH3ranCryth9mF6I 28 | qsOFr5/81WGKqU+Kec8st/RSU2vBjBp41HILAEEhUiB6prhc9B3+exwkvQSPz22W 29 | PIvhkzqeOYRoEDE2bWGC1ukd818qvQp618eLBmJSvwGh4YfUcmgqHaEk2NjoPIMV 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /test/configs/certs/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFQTCCAymgAwIBAgIJAO+k4G7bNTyoMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR 4 | BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv 5 | Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy 6 | MzA2MzRaFw0xOTExMDQyMzA2MzRaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIw 7 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALYBy3IEY3kqlf5h2vEtk9CB4Vnt 8 | AD+eaVc3Y9xuFHJ4k0ScsjwrYH3YcwQW0fDpOQCI0102YoQT7tPCBT+rC0w1mM82 9 | 0ZSKS/y2SIK9cM6LHWkUbQcWfeaL+uz2HB3jTEm8tmEEFTLBJFMbMpUsvjA5GLqG 10 | URswsNjYEl8M9wS1BETw2e+eCFa4wxq9oGHp/Dgh0vZglHzQL5osEpRct1aaQo6O 11 | jWzZc1Cgx4SxmMOoMWF8BQzlO7aikbZEJxk03TIFNph/azJt7mviMseW72mP+bX9 12 | sm/8bsINiYgJMM2HAyjIgFVMXX8AYfEFC4wozYloLqn0yy9TdjhyGbsUjg0yTd4l 13 | A9LkGKroBdY1drPSek5Nj9br27UGGGfU2ddAD5xYBIeeFY+3nqST868oIXB/m1P7 14 | 1p8tpkgujx/RqKr3nvOqBHizmoQaWZsPC3X/Jc4NvVHihpuNzN/u1D5mxGhxsx+R 15 | qnrIkhS0IqNrokggPZazugmHntd95HgTb3JpjY3RGEYXAQNr+mZGUCc+CVu0mhFX 16 | xAMZcfVp5nDg4hKHiaRv0KcaqBmnn8AB5w5FiTppzUbRP0zz7GkwrdulwR6c2Eb5 17 | 75+/022TbgCx8B9SH4zJRTj5mtrK56eFgTcnuXB+YnWaP7/7qmKIZzxrd3UDvnza 18 | bhnMiiIK7vL8qiOTAgMBAAGjHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA 19 | ATANBgkqhkiG9w0BAQsFAAOCAgEAOrh8XfW6quwBAcCxHf6/uvu/iNq4yHCg2qH6 20 | VtWs/x38I2t3BRSNsLsJGieh6yLlZDzOus+XYui4uDE50XmcwaIsY0VcXnvdyZVZ 21 | w9+lMyfp00kRF1o3B6eVxq0pRE5VB0cai7XI7tyfpRwGzA+oNLF4vBvxAHm9Ony5 22 | Q57DC/HFzyUogdkMYciO/kd9oa4HosDEXwaE8UvZUL8OVl/dptMXLL/GGwzZsUAE 23 | 1sLAbgm044YChLUDzgBAtDTkB/HNkcPzSKwULuskhe7ndoaEQNXVZuP7quGiZ/W1 24 | 1lE59gnmnyG8ySFCL05jHrKLtFAJe88gQjgDK65ZJv4W/k7ocmT+HhCxWyQWcX6v 25 | abJ0EssqeSQuzRMuZebMJJ8s46d6RcYuMdIX3RDXq+1moJDFopE7lgNrlRhWgaky 26 | Og8f/u8s1j75tk1YaYcY9uBKjKk7f681R9wMumkd6IEmEvkUwHNFsctxi4fGI7h1 27 | PRdKL0DlhVmnpHlKs6Kvm2sJ3twSAGSrC4u0LuxACeR3XbiBfyhFV/291LSuw/y1 28 | JtWOW5koh0g1k9xtkiu3/ePVdG/CLp796IyRhdB1jP/vD7W5RLLG/VAlomfjsPsB 29 | AnwFYbVZ8KrmMKYUpTJOH31CRzFdOB6nWqXu5tk3nOtLKo1nIOuVtmp9XLz3VtHe 30 | NiZPnqA= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /test/configs/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 37 | +Svp 38 | -----END CERTIFICATE----- 39 | -------------------------------------------------------------------------------- /encoders/builtin/default_enc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Apcera Inc. All rights reserved. 2 | 3 | package builtin 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "reflect" 9 | "strconv" 10 | "unsafe" 11 | ) 12 | 13 | // DefaultEncoder implementation for EncodedConn. 14 | // This encoder will leave []byte and string untouched, but will attempt to 15 | // turn numbers into appropriate strings that can be decoded. It will also 16 | // propely encoded and decode bools. If will encode a struct, but if you want 17 | // to properly handle structures you should use JsonEncoder. 18 | type DefaultEncoder struct { 19 | // Empty 20 | } 21 | 22 | var trueB = []byte("true") 23 | var falseB = []byte("false") 24 | var nilB = []byte("") 25 | 26 | // Encode 27 | func (je *DefaultEncoder) Encode(subject string, v interface{}) ([]byte, error) { 28 | switch arg := v.(type) { 29 | case string: 30 | bytes := *(*[]byte)(unsafe.Pointer(&arg)) 31 | return bytes, nil 32 | case []byte: 33 | return arg, nil 34 | case bool: 35 | if arg { 36 | return trueB, nil 37 | } else { 38 | return falseB, nil 39 | } 40 | case nil: 41 | return nilB, nil 42 | default: 43 | var buf bytes.Buffer 44 | fmt.Fprintf(&buf, "%+v", arg) 45 | return buf.Bytes(), nil 46 | } 47 | } 48 | 49 | // Decode 50 | func (je *DefaultEncoder) Decode(subject string, data []byte, vPtr interface{}) error { 51 | // Figure out what it's pointing to... 52 | sData := *(*string)(unsafe.Pointer(&data)) 53 | switch arg := vPtr.(type) { 54 | case *string: 55 | *arg = sData 56 | return nil 57 | case *[]byte: 58 | *arg = data 59 | return nil 60 | case *int: 61 | n, err := strconv.ParseInt(sData, 10, 64) 62 | if err != nil { 63 | return err 64 | } 65 | *arg = int(n) 66 | return nil 67 | case *int32: 68 | n, err := strconv.ParseInt(sData, 10, 64) 69 | if err != nil { 70 | return err 71 | } 72 | *arg = int32(n) 73 | return nil 74 | case *int64: 75 | n, err := strconv.ParseInt(sData, 10, 64) 76 | if err != nil { 77 | return err 78 | } 79 | *arg = int64(n) 80 | return nil 81 | case *float32: 82 | n, err := strconv.ParseFloat(sData, 32) 83 | if err != nil { 84 | return err 85 | } 86 | *arg = float32(n) 87 | return nil 88 | case *float64: 89 | n, err := strconv.ParseFloat(sData, 64) 90 | if err != nil { 91 | return err 92 | } 93 | *arg = float64(n) 94 | return nil 95 | case *bool: 96 | b, err := strconv.ParseBool(sData) 97 | if err != nil { 98 | return err 99 | } 100 | *arg = b 101 | return nil 102 | default: 103 | vt := reflect.TypeOf(arg).Elem() 104 | return fmt.Errorf("nats: Default Encoder can't decode to type %s", vt) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Apcera Inc. All rights reserved. 2 | 3 | package test 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/nats-io/gnatsd/server" 11 | "github.com/nats-io/go-nats" 12 | 13 | gnatsd "github.com/nats-io/gnatsd/test" 14 | ) 15 | 16 | // So that we can pass tests and benchmarks... 17 | type tLogger interface { 18 | Fatalf(format string, args ...interface{}) 19 | Errorf(format string, args ...interface{}) 20 | } 21 | 22 | // TestLogger 23 | type TestLogger tLogger 24 | 25 | // Dumb wait program to sync on callbacks, etc... Will timeout 26 | func Wait(ch chan bool) error { 27 | return WaitTime(ch, 5*time.Second) 28 | } 29 | 30 | // Wait for a chan with a timeout. 31 | func WaitTime(ch chan bool, timeout time.Duration) error { 32 | select { 33 | case <-ch: 34 | return nil 35 | case <-time.After(timeout): 36 | } 37 | return errors.New("timeout") 38 | } 39 | 40 | //////////////////////////////////////////////////////////////////////////////// 41 | // Creating client connections 42 | //////////////////////////////////////////////////////////////////////////////// 43 | 44 | // NewDefaultConnection 45 | func NewDefaultConnection(t tLogger) *nats.Conn { 46 | return NewConnection(t, nats.DefaultPort) 47 | } 48 | 49 | // NewConnection forms connection on a given port. 50 | func NewConnection(t tLogger, port int) *nats.Conn { 51 | url := fmt.Sprintf("nats://localhost:%d", port) 52 | nc, err := nats.Connect(url) 53 | if err != nil { 54 | t.Fatalf("Failed to create default connection: %v\n", err) 55 | return nil 56 | } 57 | return nc 58 | } 59 | 60 | // NewEConn 61 | func NewEConn(t tLogger) *nats.EncodedConn { 62 | ec, err := nats.NewEncodedConn(NewDefaultConnection(t), nats.DEFAULT_ENCODER) 63 | if err != nil { 64 | t.Fatalf("Failed to create an encoded connection: %v\n", err) 65 | } 66 | return ec 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | // Running gnatsd server in separate Go routines 71 | //////////////////////////////////////////////////////////////////////////////// 72 | 73 | // RunDefaultServer will run a server on the default port. 74 | func RunDefaultServer() *server.Server { 75 | return RunServerOnPort(nats.DefaultPort) 76 | } 77 | 78 | // RunServerOnPort will run a server on the given port. 79 | func RunServerOnPort(port int) *server.Server { 80 | opts := gnatsd.DefaultTestOptions 81 | opts.Port = port 82 | return RunServerWithOptions(opts) 83 | } 84 | 85 | // RunServerWithOptions will run a server with the given options. 86 | func RunServerWithOptions(opts server.Options) *server.Server { 87 | return gnatsd.RunServer(&opts) 88 | } 89 | 90 | // RunServerWithConfig will run a server with the given configuration file. 91 | func RunServerWithConfig(configFile string) (*server.Server, *server.Options) { 92 | return gnatsd.RunServerWithConfig(configFile) 93 | } 94 | -------------------------------------------------------------------------------- /test/bench_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | "time" 7 | 8 | "github.com/nats-io/go-nats" 9 | ) 10 | 11 | func BenchmarkPublishSpeed(b *testing.B) { 12 | b.StopTimer() 13 | s := RunDefaultServer() 14 | defer s.Shutdown() 15 | nc := NewDefaultConnection(b) 16 | defer nc.Close() 17 | b.StartTimer() 18 | 19 | msg := []byte("Hello World") 20 | 21 | for i := 0; i < b.N; i++ { 22 | if err := nc.Publish("foo", msg); err != nil { 23 | b.Fatalf("Error in benchmark during Publish: %v\n", err) 24 | } 25 | } 26 | // Make sure they are all processed. 27 | nc.Flush() 28 | b.StopTimer() 29 | } 30 | 31 | func BenchmarkPubSubSpeed(b *testing.B) { 32 | b.StopTimer() 33 | s := RunDefaultServer() 34 | defer s.Shutdown() 35 | nc := NewDefaultConnection(b) 36 | defer nc.Close() 37 | 38 | ch := make(chan bool) 39 | 40 | nc.SetErrorHandler(func(nc *nats.Conn, s *nats.Subscription, err error) { 41 | b.Fatalf("Error : %v\n", err) 42 | }) 43 | 44 | received := int32(0) 45 | 46 | nc.Subscribe("foo", func(m *nats.Msg) { 47 | if nr := atomic.AddInt32(&received, 1); nr >= int32(b.N) { 48 | ch <- true 49 | } 50 | }) 51 | 52 | msg := []byte("Hello World") 53 | 54 | b.StartTimer() 55 | 56 | for i := 0; i < b.N; i++ { 57 | if err := nc.Publish("foo", msg); err != nil { 58 | b.Fatalf("Error in benchmark during Publish: %v\n", err) 59 | } 60 | // Don't overrun ourselves and be a slow consumer, server will cut us off 61 | if int32(i)-atomic.LoadInt32(&received) > 32768 { 62 | time.Sleep(100 * time.Nanosecond) 63 | } 64 | } 65 | 66 | // Make sure they are all processed. 67 | err := WaitTime(ch, 10*time.Second) 68 | if err != nil { 69 | b.Fatal("Timed out waiting for messages") 70 | } else if atomic.LoadInt32(&received) != int32(b.N) { 71 | b.Fatalf("Received: %d, err:%v", received, nc.LastError()) 72 | } 73 | b.StopTimer() 74 | } 75 | 76 | func BenchmarkAsyncSubscriptionCreationSpeed(b *testing.B) { 77 | b.StopTimer() 78 | s := RunDefaultServer() 79 | defer s.Shutdown() 80 | nc := NewDefaultConnection(b) 81 | defer nc.Close() 82 | b.StartTimer() 83 | b.ReportAllocs() 84 | 85 | for i := 0; i < b.N; i++ { 86 | nc.Subscribe("foo", func(m *nats.Msg) {}) 87 | } 88 | } 89 | 90 | func BenchmarkSyncSubscriptionCreationSpeed(b *testing.B) { 91 | b.StopTimer() 92 | s := RunDefaultServer() 93 | defer s.Shutdown() 94 | nc := NewDefaultConnection(b) 95 | defer nc.Close() 96 | b.StartTimer() 97 | b.ReportAllocs() 98 | 99 | for i := 0; i < b.N; i++ { 100 | nc.SubscribeSync("foo") 101 | } 102 | } 103 | 104 | func BenchmarkInboxCreation(b *testing.B) { 105 | for i := 0; i < b.N; i++ { 106 | nats.NewInbox() 107 | } 108 | } 109 | 110 | func BenchmarkRequest(b *testing.B) { 111 | b.StopTimer() 112 | s := RunDefaultServer() 113 | defer s.Shutdown() 114 | nc := NewDefaultConnection(b) 115 | defer nc.Close() 116 | ok := []byte("ok") 117 | nc.Subscribe("req", func(m *nats.Msg) { 118 | nc.Publish(m.Reply, ok) 119 | }) 120 | b.StartTimer() 121 | b.ReportAllocs() 122 | q := []byte("q") 123 | for i := 0; i < b.N; i++ { 124 | _, err := nc.Request("req", q, 1*time.Second) 125 | if err != nil { 126 | b.Fatalf("Err %v\n", err) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/configs/certs/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /test/configs/certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /netchan.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2014 Apcera Inc. All rights reserved. 2 | 3 | package nats 4 | 5 | import ( 6 | "errors" 7 | "reflect" 8 | ) 9 | 10 | // This allows the functionality for network channels by binding send and receive Go chans 11 | // to subjects and optionally queue groups. 12 | // Data will be encoded and decoded via the EncodedConn and its associated encoders. 13 | 14 | // BindSendChan binds a channel for send operations to NATS. 15 | func (c *EncodedConn) BindSendChan(subject string, channel interface{}) error { 16 | chVal := reflect.ValueOf(channel) 17 | if chVal.Kind() != reflect.Chan { 18 | return ErrChanArg 19 | } 20 | go chPublish(c, chVal, subject) 21 | return nil 22 | } 23 | 24 | // Publish all values that arrive on the channel until it is closed or we 25 | // encounter an error. 26 | func chPublish(c *EncodedConn, chVal reflect.Value, subject string) { 27 | for { 28 | val, ok := chVal.Recv() 29 | if !ok { 30 | // Channel has most likely been closed. 31 | return 32 | } 33 | if e := c.Publish(subject, val.Interface()); e != nil { 34 | // Do this under lock. 35 | c.Conn.mu.Lock() 36 | defer c.Conn.mu.Unlock() 37 | 38 | if c.Conn.Opts.AsyncErrorCB != nil { 39 | // FIXME(dlc) - Not sure this is the right thing to do. 40 | // FIXME(ivan) - If the connection is not yet closed, try to schedule the callback 41 | if c.Conn.isClosed() { 42 | go c.Conn.Opts.AsyncErrorCB(c.Conn, nil, e) 43 | } else { 44 | c.Conn.ach <- func() { c.Conn.Opts.AsyncErrorCB(c.Conn, nil, e) } 45 | } 46 | } 47 | return 48 | } 49 | } 50 | } 51 | 52 | // BindRecvChan binds a channel for receive operations from NATS. 53 | func (c *EncodedConn) BindRecvChan(subject string, channel interface{}) (*Subscription, error) { 54 | return c.bindRecvChan(subject, _EMPTY_, channel) 55 | } 56 | 57 | // BindRecvQueueChan binds a channel for queue-based receive operations from NATS. 58 | func (c *EncodedConn) BindRecvQueueChan(subject, queue string, channel interface{}) (*Subscription, error) { 59 | return c.bindRecvChan(subject, queue, channel) 60 | } 61 | 62 | // Internal function to bind receive operations for a channel. 63 | func (c *EncodedConn) bindRecvChan(subject, queue string, channel interface{}) (*Subscription, error) { 64 | chVal := reflect.ValueOf(channel) 65 | if chVal.Kind() != reflect.Chan { 66 | return nil, ErrChanArg 67 | } 68 | argType := chVal.Type().Elem() 69 | 70 | cb := func(m *Msg) { 71 | var oPtr reflect.Value 72 | if argType.Kind() != reflect.Ptr { 73 | oPtr = reflect.New(argType) 74 | } else { 75 | oPtr = reflect.New(argType.Elem()) 76 | } 77 | if err := c.Enc.Decode(m.Subject, m.Data, oPtr.Interface()); err != nil { 78 | c.Conn.err = errors.New("nats: Got an error trying to unmarshal: " + err.Error()) 79 | if c.Conn.Opts.AsyncErrorCB != nil { 80 | c.Conn.ach <- func() { c.Conn.Opts.AsyncErrorCB(c.Conn, m.Sub, c.Conn.err) } 81 | } 82 | return 83 | } 84 | if argType.Kind() != reflect.Ptr { 85 | oPtr = reflect.Indirect(oPtr) 86 | } 87 | // This is a bit hacky, but in this instance we may be trying to send to a closed channel. 88 | // and the user does not know when it is safe to close the channel. 89 | defer func() { 90 | // If we have panicked, recover and close the subscription. 91 | if r := recover(); r != nil { 92 | m.Sub.Unsubscribe() 93 | } 94 | }() 95 | // Actually do the send to the channel. 96 | chVal.Send(oPtr) 97 | } 98 | 99 | return c.Conn.subscribe(subject, queue, cb, nil) 100 | } 101 | -------------------------------------------------------------------------------- /encoders/builtin/gob_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Apcera Inc. All rights reserved. 2 | 3 | package builtin_test 4 | 5 | import ( 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/nats-io/go-nats" 10 | "github.com/nats-io/go-nats/test" 11 | ) 12 | 13 | func NewGobEncodedConn(tl test.TestLogger) *nats.EncodedConn { 14 | ec, err := nats.NewEncodedConn(test.NewConnection(tl, TEST_PORT), nats.GOB_ENCODER) 15 | if err != nil { 16 | tl.Fatalf("Failed to create an encoded connection: %v\n", err) 17 | } 18 | return ec 19 | } 20 | 21 | func TestGobMarshalString(t *testing.T) { 22 | s := test.RunServerOnPort(TEST_PORT) 23 | defer s.Shutdown() 24 | 25 | ec := NewGobEncodedConn(t) 26 | defer ec.Close() 27 | ch := make(chan bool) 28 | 29 | testString := "Hello World!" 30 | 31 | ec.Subscribe("gob_string", func(s string) { 32 | if s != testString { 33 | t.Fatalf("Received test string of '%s', wanted '%s'\n", s, testString) 34 | } 35 | ch <- true 36 | }) 37 | ec.Publish("gob_string", testString) 38 | if e := test.Wait(ch); e != nil { 39 | t.Fatal("Did not receive the message") 40 | } 41 | } 42 | 43 | func TestGobMarshalInt(t *testing.T) { 44 | s := test.RunServerOnPort(TEST_PORT) 45 | defer s.Shutdown() 46 | 47 | ec := NewGobEncodedConn(t) 48 | defer ec.Close() 49 | ch := make(chan bool) 50 | 51 | testN := 22 52 | 53 | ec.Subscribe("gob_int", func(n int) { 54 | if n != testN { 55 | t.Fatalf("Received test int of '%d', wanted '%d'\n", n, testN) 56 | } 57 | ch <- true 58 | }) 59 | ec.Publish("gob_int", testN) 60 | if e := test.Wait(ch); e != nil { 61 | t.Fatal("Did not receive the message") 62 | } 63 | } 64 | 65 | func TestGobMarshalStruct(t *testing.T) { 66 | s := test.RunServerOnPort(TEST_PORT) 67 | defer s.Shutdown() 68 | 69 | ec := NewGobEncodedConn(t) 70 | defer ec.Close() 71 | ch := make(chan bool) 72 | 73 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 74 | me.Children = make(map[string]*person) 75 | 76 | me.Children["sam"] = &person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 77 | me.Children["meg"] = &person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 78 | 79 | me.Assets = make(map[string]uint) 80 | me.Assets["house"] = 1000 81 | me.Assets["car"] = 100 82 | 83 | ec.Subscribe("gob_struct", func(p *person) { 84 | if !reflect.DeepEqual(p, me) { 85 | t.Fatalf("Did not receive the correct struct response") 86 | } 87 | ch <- true 88 | }) 89 | 90 | ec.Publish("gob_struct", me) 91 | if e := test.Wait(ch); e != nil { 92 | t.Fatal("Did not receive the message") 93 | } 94 | } 95 | 96 | func BenchmarkPublishGobStruct(b *testing.B) { 97 | // stop benchmark for set-up 98 | b.StopTimer() 99 | 100 | s := test.RunServerOnPort(TEST_PORT) 101 | defer s.Shutdown() 102 | 103 | ec := NewGobEncodedConn(b) 104 | defer ec.Close() 105 | ch := make(chan bool) 106 | 107 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 108 | me.Children = make(map[string]*person) 109 | 110 | me.Children["sam"] = &person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 111 | me.Children["meg"] = &person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 112 | 113 | ec.Subscribe("gob_struct", func(p *person) { 114 | if !reflect.DeepEqual(p, me) { 115 | b.Fatalf("Did not receive the correct struct response") 116 | } 117 | ch <- true 118 | }) 119 | 120 | // resume benchmark 121 | b.StartTimer() 122 | 123 | for n := 0; n < b.N; n++ { 124 | ec.Publish("gob_struct", me) 125 | if e := test.Wait(ch); e != nil { 126 | b.Fatal("Did not receive the message") 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /encoders/protobuf/protobuf_test.go: -------------------------------------------------------------------------------- 1 | package protobuf_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/nats-io/go-nats" 9 | "github.com/nats-io/go-nats/test" 10 | 11 | "github.com/nats-io/go-nats/encoders/protobuf" 12 | pb "github.com/nats-io/go-nats/encoders/protobuf/testdata" 13 | ) 14 | 15 | const TEST_PORT = 8068 16 | 17 | func NewProtoEncodedConn(tl test.TestLogger) *nats.EncodedConn { 18 | ec, err := nats.NewEncodedConn(test.NewConnection(tl, TEST_PORT), protobuf.PROTOBUF_ENCODER) 19 | if err != nil { 20 | tl.Fatalf("Failed to create an encoded connection: %v\n", err) 21 | } 22 | return ec 23 | } 24 | 25 | func TestProtoMarshalStruct(t *testing.T) { 26 | s := test.RunServerOnPort(TEST_PORT) 27 | defer s.Shutdown() 28 | 29 | ec := NewProtoEncodedConn(t) 30 | defer ec.Close() 31 | ch := make(chan bool) 32 | 33 | me := &pb.Person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 34 | me.Children = make(map[string]*pb.Person) 35 | 36 | me.Children["sam"] = &pb.Person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 37 | me.Children["meg"] = &pb.Person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 38 | 39 | ec.Subscribe("protobuf_test", func(p *pb.Person) { 40 | if !reflect.DeepEqual(p, me) { 41 | t.Fatal("Did not receive the correct protobuf response") 42 | } 43 | ch <- true 44 | }) 45 | 46 | ec.Publish("protobuf_test", me) 47 | if e := test.Wait(ch); e != nil { 48 | t.Fatal("Did not receive the message") 49 | } 50 | } 51 | 52 | func TestProtoNilRequest(t *testing.T) { 53 | s := test.RunServerOnPort(TEST_PORT) 54 | defer s.Shutdown() 55 | 56 | ec := NewProtoEncodedConn(t) 57 | defer ec.Close() 58 | 59 | testPerson := &pb.Person{Name: "Anatolii", Age: 25, Address: "Ukraine, Nikolaev"} 60 | 61 | //Subscribe with empty interface shouldn't failed on empty message 62 | ec.Subscribe("nil_test", func(_, reply string, _ interface{}) { 63 | ec.Publish(reply, testPerson) 64 | }) 65 | 66 | resp := new(pb.Person) 67 | 68 | //Request with nil argument shouldn't failed with nil argument 69 | err := ec.Request("nil_test", nil, resp, 100*time.Millisecond) 70 | ec.Flush() 71 | 72 | if err != nil { 73 | t.Error("Fail to send empty message via encoded proto connection") 74 | } 75 | 76 | if !reflect.DeepEqual(testPerson, resp) { 77 | t.Error("Fail to receive encoded response") 78 | } 79 | } 80 | 81 | func BenchmarkProtobufMarshalStruct(b *testing.B) { 82 | me := &pb.Person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 83 | me.Children = make(map[string]*pb.Person) 84 | 85 | me.Children["sam"] = &pb.Person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 86 | me.Children["meg"] = &pb.Person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 87 | 88 | encoder := &protobuf.ProtobufEncoder{} 89 | for n := 0; n < b.N; n++ { 90 | if _, err := encoder.Encode("protobuf_test", me); err != nil { 91 | b.Fatal("Couldn't serialize object", err) 92 | } 93 | } 94 | } 95 | 96 | func BenchmarkPublishProtobufStruct(b *testing.B) { 97 | // stop benchmark for set-up 98 | b.StopTimer() 99 | 100 | s := test.RunServerOnPort(TEST_PORT) 101 | defer s.Shutdown() 102 | 103 | ec := NewProtoEncodedConn(b) 104 | defer ec.Close() 105 | ch := make(chan bool) 106 | 107 | me := &pb.Person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 108 | me.Children = make(map[string]*pb.Person) 109 | 110 | me.Children["sam"] = &pb.Person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 111 | me.Children["meg"] = &pb.Person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 112 | 113 | ec.Subscribe("protobuf_test", func(p *pb.Person) { 114 | if !reflect.DeepEqual(p, me) { 115 | b.Fatalf("Did not receive the correct protobuf response") 116 | } 117 | ch <- true 118 | }) 119 | 120 | // resume benchmark 121 | b.StartTimer() 122 | 123 | for n := 0; n < b.N; n++ { 124 | ec.Publish("protobuf_test", me) 125 | if e := test.Wait(ch); e != nil { 126 | b.Fatal("Did not receive the message") 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /examples/nats-bench.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Apcera Inc. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/nats-io/go-nats" 15 | "github.com/nats-io/go-nats/bench" 16 | ) 17 | 18 | // Some sane defaults 19 | const ( 20 | DefaultNumMsgs = 100000 21 | DefaultNumPubs = 1 22 | DefaultNumSubs = 0 23 | DefaultMessageSize = 128 24 | ) 25 | 26 | func usage() { 27 | log.Fatalf("Usage: nats-bench [-s server (%s)] [--tls] [-np NUM_PUBLISHERS] [-ns NUM_SUBSCRIBERS] [-n NUM_MSGS] [-ms MESSAGE_SIZE] [-csv csvfile] \n", nats.DefaultURL) 28 | } 29 | 30 | var benchmark *bench.Benchmark 31 | 32 | func main() { 33 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 34 | var tls = flag.Bool("tls", false, "Use TLS Secure Connection") 35 | var numPubs = flag.Int("np", DefaultNumPubs, "Number of Concurrent Publishers") 36 | var numSubs = flag.Int("ns", DefaultNumSubs, "Number of Concurrent Subscribers") 37 | var numMsgs = flag.Int("n", DefaultNumMsgs, "Number of Messages to Publish") 38 | var msgSize = flag.Int("ms", DefaultMessageSize, "Size of the message.") 39 | var csvFile = flag.String("csv", "", "Save bench data to csv file") 40 | 41 | log.SetFlags(0) 42 | flag.Usage = usage 43 | flag.Parse() 44 | 45 | args := flag.Args() 46 | if len(args) != 1 { 47 | usage() 48 | } 49 | 50 | if *numMsgs <= 0 { 51 | log.Fatal("Number of messages should be greater than zero.") 52 | } 53 | 54 | // Setup the option block 55 | opts := nats.DefaultOptions 56 | opts.Servers = strings.Split(*urls, ",") 57 | for i, s := range opts.Servers { 58 | opts.Servers[i] = strings.Trim(s, " ") 59 | } 60 | opts.Secure = *tls 61 | 62 | benchmark = bench.NewBenchmark("NATS", *numSubs, *numPubs) 63 | 64 | var startwg sync.WaitGroup 65 | var donewg sync.WaitGroup 66 | 67 | donewg.Add(*numPubs + *numSubs) 68 | 69 | // Run Subscribers first 70 | startwg.Add(*numSubs) 71 | for i := 0; i < *numSubs; i++ { 72 | go runSubscriber(&startwg, &donewg, opts, *numMsgs, *msgSize) 73 | } 74 | startwg.Wait() 75 | 76 | // Now Publishers 77 | startwg.Add(*numPubs) 78 | pubCounts := bench.MsgsPerClient(*numMsgs, *numPubs) 79 | for i := 0; i < *numPubs; i++ { 80 | go runPublisher(&startwg, &donewg, opts, pubCounts[i], *msgSize) 81 | } 82 | 83 | log.Printf("Starting benchmark [msgs=%d, msgsize=%d, pubs=%d, subs=%d]\n", *numMsgs, *msgSize, *numPubs, *numSubs) 84 | 85 | startwg.Wait() 86 | donewg.Wait() 87 | 88 | benchmark.Close() 89 | 90 | fmt.Print(benchmark.Report()) 91 | 92 | if len(*csvFile) > 0 { 93 | csv := benchmark.CSV() 94 | ioutil.WriteFile(*csvFile, []byte(csv), 0644) 95 | fmt.Printf("Saved metric data in csv file %s\n", *csvFile) 96 | } 97 | } 98 | 99 | func runPublisher(startwg, donewg *sync.WaitGroup, opts nats.Options, numMsgs int, msgSize int) { 100 | nc, err := opts.Connect() 101 | if err != nil { 102 | log.Fatalf("Can't connect: %v\n", err) 103 | } 104 | defer nc.Close() 105 | startwg.Done() 106 | 107 | args := flag.Args() 108 | subj := args[0] 109 | var msg []byte 110 | if msgSize > 0 { 111 | msg = make([]byte, msgSize) 112 | } 113 | 114 | start := time.Now() 115 | 116 | for i := 0; i < numMsgs; i++ { 117 | nc.Publish(subj, msg) 118 | } 119 | nc.Flush() 120 | benchmark.AddPubSample(bench.NewSample(numMsgs, msgSize, start, time.Now(), nc)) 121 | 122 | donewg.Done() 123 | } 124 | 125 | func runSubscriber(startwg, donewg *sync.WaitGroup, opts nats.Options, numMsgs int, msgSize int) { 126 | nc, err := opts.Connect() 127 | if err != nil { 128 | log.Fatalf("Can't connect: %v\n", err) 129 | } 130 | 131 | args := flag.Args() 132 | subj := args[0] 133 | 134 | received := 0 135 | start := time.Now() 136 | nc.Subscribe(subj, func(msg *nats.Msg) { 137 | received++ 138 | if received >= numMsgs { 139 | benchmark.AddSubSample(bench.NewSample(numMsgs, msgSize, start, time.Now(), nc)) 140 | donewg.Done() 141 | nc.Close() 142 | } 143 | }) 144 | nc.Flush() 145 | startwg.Done() 146 | } 147 | -------------------------------------------------------------------------------- /test/auth_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | 9 | "github.com/nats-io/gnatsd/auth" 10 | "github.com/nats-io/go-nats" 11 | ) 12 | 13 | func TestAuth(t *testing.T) { 14 | s := RunServerOnPort(8232) 15 | 16 | // Auth is pluggable, so need to set here.. 17 | auth := &auth.Plain{ 18 | Username: "derek", 19 | Password: "foo", 20 | } 21 | s.SetClientAuthMethod(auth) 22 | 23 | defer s.Shutdown() 24 | 25 | _, err := nats.Connect("nats://localhost:8232") 26 | if err == nil { 27 | t.Fatal("Should have received an error while trying to connect") 28 | } 29 | 30 | // This test may be a bit too strict for the future, but for now makes 31 | // sure that we correctly process the -ERR content on connect. 32 | if err.Error() != nats.ErrAuthorization.Error() { 33 | t.Fatalf("Expected error '%v', got '%v'", nats.ErrAuthorization, err) 34 | } 35 | 36 | nc, err := nats.Connect("nats://derek:foo@localhost:8232") 37 | if err != nil { 38 | t.Fatal("Should have connected successfully with a token") 39 | } 40 | nc.Close() 41 | 42 | // Use Options 43 | nc, err = nats.Connect("nats://localhost:8232", nats.UserInfo("derek", "foo")) 44 | if err != nil { 45 | t.Fatalf("Should have connected successfully with a token: %v", err) 46 | } 47 | nc.Close() 48 | // Verify that credentials in URL take precedence. 49 | nc, err = nats.Connect("nats://derek:foo@localhost:8232", nats.UserInfo("foo", "bar")) 50 | if err != nil { 51 | t.Fatalf("Should have connected successfully with a token: %v", err) 52 | } 53 | nc.Close() 54 | } 55 | 56 | func TestAuthFailNoDisconnectCB(t *testing.T) { 57 | s := RunServerOnPort(8232) 58 | 59 | // Auth is pluggable, so need to set here.. 60 | auth := &auth.Plain{ 61 | Username: "derek", 62 | Password: "foo", 63 | } 64 | s.SetClientAuthMethod(auth) 65 | 66 | defer s.Shutdown() 67 | 68 | copts := nats.DefaultOptions 69 | copts.Url = "nats://localhost:8232" 70 | receivedDisconnectCB := int32(0) 71 | copts.DisconnectedCB = func(nc *nats.Conn) { 72 | atomic.AddInt32(&receivedDisconnectCB, 1) 73 | } 74 | 75 | _, err := copts.Connect() 76 | if err == nil { 77 | t.Fatal("Should have received an error while trying to connect") 78 | } 79 | if atomic.LoadInt32(&receivedDisconnectCB) > 0 { 80 | t.Fatal("Should not have received a disconnect callback on auth failure") 81 | } 82 | } 83 | 84 | func TestAuthFailAllowReconnect(t *testing.T) { 85 | ts := RunServerOnPort(23232) 86 | defer ts.Shutdown() 87 | 88 | var servers = []string{ 89 | "nats://localhost:23232", 90 | "nats://localhost:23233", 91 | "nats://localhost:23234", 92 | } 93 | 94 | ts2 := RunServerOnPort(23233) 95 | // Auth is pluggable, so need to set here.. 96 | auth := &auth.Plain{ 97 | Username: "ivan", 98 | Password: "foo", 99 | } 100 | ts2.SetClientAuthMethod(auth) 101 | defer ts2.Shutdown() 102 | 103 | ts3 := RunServerOnPort(23234) 104 | defer ts3.Shutdown() 105 | 106 | reconnectch := make(chan bool) 107 | 108 | opts := nats.DefaultOptions 109 | opts.Servers = servers 110 | opts.AllowReconnect = true 111 | opts.NoRandomize = true 112 | opts.MaxReconnect = 10 113 | opts.ReconnectWait = 100 * time.Millisecond 114 | 115 | opts.ReconnectedCB = func(_ *nats.Conn) { 116 | reconnectch <- true 117 | } 118 | 119 | // Connect 120 | nc, err := opts.Connect() 121 | if err != nil { 122 | t.Fatalf("Should have connected ok: %v", err) 123 | } 124 | defer nc.Close() 125 | 126 | // Stop the server 127 | ts.Shutdown() 128 | 129 | // The client will try to connect to the second server, and that 130 | // should fail. It should then try to connect to the third and succeed. 131 | 132 | // Wait for the reconnect CB. 133 | if e := Wait(reconnectch); e != nil { 134 | t.Fatal("Reconnect callback should have been triggered") 135 | } 136 | 137 | if nc.IsClosed() { 138 | t.Fatal("Should have reconnected") 139 | } 140 | 141 | if nc.ConnectedUrl() != servers[2] { 142 | t.Fatalf("Should have reconnected to %s, reconnected to %s instead", servers[2], nc.ConnectedUrl()) 143 | } 144 | } 145 | 146 | func TestTokenAuth(t *testing.T) { 147 | s := RunServerOnPort(8232) 148 | 149 | secret := "S3Cr3T0k3n!" 150 | // Auth is pluggable, so need to set here.. 151 | auth := &auth.Token{Token: secret} 152 | s.SetClientAuthMethod(auth) 153 | 154 | defer s.Shutdown() 155 | 156 | _, err := nats.Connect("nats://localhost:8232") 157 | if err == nil { 158 | t.Fatal("Should have received an error while trying to connect") 159 | } 160 | 161 | tokenURL := fmt.Sprintf("nats://%s@localhost:8232", secret) 162 | nc, err := nats.Connect(tokenURL) 163 | if err != nil { 164 | t.Fatal("Should have connected successfully") 165 | } 166 | nc.Close() 167 | 168 | // Use Options 169 | nc, err = nats.Connect("nats://localhost:8232", nats.Token(secret)) 170 | if err != nil { 171 | t.Fatalf("Should have connected successfully: %v", err) 172 | } 173 | nc.Close() 174 | // Verify that token in the URL takes precedence. 175 | nc, err = nats.Connect(tokenURL, nats.Token("badtoken")) 176 | if err != nil { 177 | t.Fatalf("Should have connected successfully: %v", err) 178 | } 179 | nc.Close() 180 | } 181 | -------------------------------------------------------------------------------- /encoders/builtin/json_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Apcera Inc. All rights reserved. 2 | 3 | package builtin_test 4 | 5 | import ( 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/nats-io/go-nats" 11 | "github.com/nats-io/go-nats/encoders/builtin" 12 | "github.com/nats-io/go-nats/test" 13 | ) 14 | 15 | func NewJsonEncodedConn(tl test.TestLogger) *nats.EncodedConn { 16 | ec, err := nats.NewEncodedConn(test.NewConnection(tl, TEST_PORT), nats.JSON_ENCODER) 17 | if err != nil { 18 | tl.Fatalf("Failed to create an encoded connection: %v\n", err) 19 | } 20 | return ec 21 | } 22 | 23 | func TestJsonMarshalString(t *testing.T) { 24 | s := test.RunServerOnPort(TEST_PORT) 25 | defer s.Shutdown() 26 | 27 | ec := NewJsonEncodedConn(t) 28 | defer ec.Close() 29 | ch := make(chan bool) 30 | 31 | testString := "Hello World!" 32 | 33 | ec.Subscribe("json_string", func(s string) { 34 | if s != testString { 35 | t.Fatalf("Received test string of '%s', wanted '%s'\n", s, testString) 36 | } 37 | ch <- true 38 | }) 39 | ec.Publish("json_string", testString) 40 | if e := test.Wait(ch); e != nil { 41 | t.Fatal("Did not receive the message") 42 | } 43 | } 44 | 45 | func TestJsonMarshalInt(t *testing.T) { 46 | s := test.RunServerOnPort(TEST_PORT) 47 | defer s.Shutdown() 48 | 49 | ec := NewJsonEncodedConn(t) 50 | defer ec.Close() 51 | ch := make(chan bool) 52 | 53 | testN := 22 54 | 55 | ec.Subscribe("json_int", func(n int) { 56 | if n != testN { 57 | t.Fatalf("Received test int of '%d', wanted '%d'\n", n, testN) 58 | } 59 | ch <- true 60 | }) 61 | ec.Publish("json_int", testN) 62 | if e := test.Wait(ch); e != nil { 63 | t.Fatal("Did not receive the message") 64 | } 65 | } 66 | 67 | type person struct { 68 | Name string 69 | Address string 70 | Age int 71 | Children map[string]*person 72 | Assets map[string]uint 73 | } 74 | 75 | func TestJsonMarshalStruct(t *testing.T) { 76 | s := test.RunServerOnPort(TEST_PORT) 77 | defer s.Shutdown() 78 | 79 | ec := NewJsonEncodedConn(t) 80 | defer ec.Close() 81 | ch := make(chan bool) 82 | 83 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 84 | me.Children = make(map[string]*person) 85 | 86 | me.Children["sam"] = &person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 87 | me.Children["meg"] = &person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 88 | 89 | me.Assets = make(map[string]uint) 90 | me.Assets["house"] = 1000 91 | me.Assets["car"] = 100 92 | 93 | ec.Subscribe("json_struct", func(p *person) { 94 | if !reflect.DeepEqual(p, me) { 95 | t.Fatal("Did not receive the correct struct response") 96 | } 97 | ch <- true 98 | }) 99 | 100 | ec.Publish("json_struct", me) 101 | if e := test.Wait(ch); e != nil { 102 | t.Fatal("Did not receive the message") 103 | } 104 | } 105 | 106 | func BenchmarkJsonMarshalStruct(b *testing.B) { 107 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 108 | me.Children = make(map[string]*person) 109 | 110 | me.Children["sam"] = &person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 111 | me.Children["meg"] = &person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 112 | 113 | encoder := &builtin.JsonEncoder{} 114 | for n := 0; n < b.N; n++ { 115 | if _, err := encoder.Encode("protobuf_test", me); err != nil { 116 | b.Fatal("Couldn't serialize object", err) 117 | } 118 | } 119 | } 120 | 121 | func BenchmarkPublishJsonStruct(b *testing.B) { 122 | // stop benchmark for set-up 123 | b.StopTimer() 124 | 125 | s := test.RunServerOnPort(TEST_PORT) 126 | defer s.Shutdown() 127 | 128 | ec := NewJsonEncodedConn(b) 129 | defer ec.Close() 130 | ch := make(chan bool) 131 | 132 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery St"} 133 | me.Children = make(map[string]*person) 134 | 135 | me.Children["sam"] = &person{Name: "sam", Age: 19, Address: "140 New Montgomery St"} 136 | me.Children["meg"] = &person{Name: "meg", Age: 17, Address: "140 New Montgomery St"} 137 | 138 | ec.Subscribe("json_struct", func(p *person) { 139 | if !reflect.DeepEqual(p, me) { 140 | b.Fatalf("Did not receive the correct struct response") 141 | } 142 | ch <- true 143 | }) 144 | 145 | // resume benchmark 146 | b.StartTimer() 147 | 148 | for n := 0; n < b.N; n++ { 149 | ec.Publish("json_struct", me) 150 | if e := test.Wait(ch); e != nil { 151 | b.Fatal("Did not receive the message") 152 | } 153 | } 154 | 155 | } 156 | 157 | func TestNotMarshableToJson(t *testing.T) { 158 | je := &builtin.JsonEncoder{} 159 | ch := make(chan bool) 160 | _, err := je.Encode("foo", ch) 161 | if err == nil { 162 | t.Fatal("Expected an error when failing encoding") 163 | } 164 | } 165 | 166 | func TestFailedEncodedPublish(t *testing.T) { 167 | s := test.RunServerOnPort(TEST_PORT) 168 | defer s.Shutdown() 169 | 170 | ec := NewJsonEncodedConn(t) 171 | defer ec.Close() 172 | 173 | ch := make(chan bool) 174 | err := ec.Publish("foo", ch) 175 | if err == nil { 176 | t.Fatal("Expected an error trying to publish a channel") 177 | } 178 | err = ec.PublishRequest("foo", "bar", ch) 179 | if err == nil { 180 | t.Fatal("Expected an error trying to publish a channel") 181 | } 182 | var cr chan bool 183 | err = ec.Request("foo", ch, &cr, 1*time.Second) 184 | if err == nil { 185 | t.Fatal("Expected an error trying to publish a channel") 186 | } 187 | err = ec.LastError() 188 | if err != nil { 189 | t.Fatalf("Expected LastError to be nil: %q ", err) 190 | } 191 | } 192 | 193 | func TestDecodeConditionals(t *testing.T) { 194 | je := &builtin.JsonEncoder{} 195 | 196 | b, err := je.Encode("foo", 22) 197 | if err != nil { 198 | t.Fatalf("Expected no error when encoding, got %v\n", err) 199 | } 200 | var foo string 201 | var bar []byte 202 | err = je.Decode("foo", b, &foo) 203 | if err != nil { 204 | t.Fatalf("Expected no error when decoding, got %v\n", err) 205 | } 206 | err = je.Decode("foo", b, &bar) 207 | if err != nil { 208 | t.Fatalf("Expected no error when decoding, got %v\n", err) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /bench/benchlib_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | "time" 8 | 9 | "github.com/nats-io/go-nats" 10 | ) 11 | 12 | const ( 13 | MsgSize = 8 14 | Million = 1000 * 1000 15 | ) 16 | 17 | var baseTime = time.Now() 18 | 19 | func millionMessagesSecondSample(seconds int) *Sample { 20 | messages := Million * seconds 21 | start := baseTime 22 | end := start.Add(time.Second * time.Duration(seconds)) 23 | nc := new(nats.Conn) 24 | 25 | s := NewSample(messages, MsgSize, start, end, nc) 26 | s.MsgCnt = uint64(messages) 27 | s.MsgBytes = uint64(messages * MsgSize) 28 | s.IOBytes = s.MsgBytes 29 | return s 30 | } 31 | 32 | func TestDuration(t *testing.T) { 33 | s := millionMessagesSecondSample(1) 34 | duration := s.End.Sub(s.Start) 35 | if duration != s.Duration() || duration != time.Second { 36 | t.Fatal("Expected sample duration to be 1 second") 37 | } 38 | } 39 | 40 | func TestSeconds(t *testing.T) { 41 | s := millionMessagesSecondSample(1) 42 | seconds := s.End.Sub(s.Start).Seconds() 43 | if seconds != s.Seconds() || seconds != 1.0 { 44 | t.Fatal("Expected sample seconds to be 1 second") 45 | } 46 | } 47 | 48 | func TestRate(t *testing.T) { 49 | s := millionMessagesSecondSample(60) 50 | if s.Rate() != Million { 51 | t.Fatal("Expected rate at 1 million msgs") 52 | } 53 | } 54 | 55 | func TestThoughput(t *testing.T) { 56 | s := millionMessagesSecondSample(60) 57 | if s.Throughput() != Million*MsgSize { 58 | t.Fatalf("Expected throughput at %d million bytes/sec", MsgSize) 59 | } 60 | } 61 | 62 | func TestStrings(t *testing.T) { 63 | s := millionMessagesSecondSample(60) 64 | if len(s.String()) == 0 { 65 | t.Fatal("Sample didn't provide a String") 66 | } 67 | } 68 | 69 | func TestGroupDuration(t *testing.T) { 70 | sg := NewSampleGroup() 71 | sg.AddSample(millionMessagesSecondSample(1)) 72 | sg.AddSample(millionMessagesSecondSample(2)) 73 | duration := sg.End.Sub(sg.Start) 74 | if duration != sg.Duration() || duration != time.Duration(2)*time.Second { 75 | t.Fatal("Expected aggregate duration to be 2.0 seconds") 76 | } 77 | } 78 | 79 | func TestGroupSeconds(t *testing.T) { 80 | sg := NewSampleGroup() 81 | sg.AddSample(millionMessagesSecondSample(1)) 82 | sg.AddSample(millionMessagesSecondSample(2)) 83 | sg.AddSample(millionMessagesSecondSample(3)) 84 | seconds := sg.End.Sub(sg.Start).Seconds() 85 | if seconds != sg.Seconds() || seconds != 3.0 { 86 | t.Fatal("Expected aggregate seconds to be 3.0 seconds") 87 | } 88 | } 89 | 90 | func TestGroupRate(t *testing.T) { 91 | sg := NewSampleGroup() 92 | sg.AddSample(millionMessagesSecondSample(1)) 93 | sg.AddSample(millionMessagesSecondSample(2)) 94 | sg.AddSample(millionMessagesSecondSample(3)) 95 | if sg.Rate() != Million*2 { 96 | t.Fatal("Expected MsgRate at 2 million msg/sec") 97 | } 98 | } 99 | 100 | func TestGroupThoughput(t *testing.T) { 101 | sg := NewSampleGroup() 102 | sg.AddSample(millionMessagesSecondSample(1)) 103 | sg.AddSample(millionMessagesSecondSample(2)) 104 | sg.AddSample(millionMessagesSecondSample(3)) 105 | if sg.Throughput() != 2*Million*MsgSize { 106 | t.Fatalf("Expected througput at %d million bytes/sec", 2*MsgSize) 107 | } 108 | } 109 | 110 | func TestMinMaxRate(t *testing.T) { 111 | sg := NewSampleGroup() 112 | sg.AddSample(millionMessagesSecondSample(1)) 113 | sg.AddSample(millionMessagesSecondSample(2)) 114 | sg.AddSample(millionMessagesSecondSample(3)) 115 | if sg.MinRate() != sg.MaxRate() { 116 | t.Fatal("Expected MinRate == MaxRate") 117 | } 118 | } 119 | 120 | func TestAvgRate(t *testing.T) { 121 | sg := NewSampleGroup() 122 | sg.AddSample(millionMessagesSecondSample(1)) 123 | sg.AddSample(millionMessagesSecondSample(2)) 124 | sg.AddSample(millionMessagesSecondSample(3)) 125 | if sg.MinRate() != sg.AvgRate() { 126 | t.Fatal("Expected MinRate == AvgRate") 127 | } 128 | } 129 | 130 | func TestStdDev(t *testing.T) { 131 | sg := NewSampleGroup() 132 | sg.AddSample(millionMessagesSecondSample(1)) 133 | sg.AddSample(millionMessagesSecondSample(2)) 134 | sg.AddSample(millionMessagesSecondSample(3)) 135 | if sg.StdDev() != 0.0 { 136 | t.Fatal("Expected stddev to be zero") 137 | } 138 | } 139 | 140 | func TestBenchSetup(t *testing.T) { 141 | bench := NewBenchmark("test", 1, 1) 142 | bench.AddSubSample(millionMessagesSecondSample(1)) 143 | bench.AddPubSample(millionMessagesSecondSample(1)) 144 | bench.Close() 145 | if len(bench.RunID) == 0 { 146 | t.Fatal("Bench doesn't have a RunID") 147 | } 148 | if len(bench.Pubs.Samples) != 1 { 149 | t.Fatal("Expected one publisher") 150 | } 151 | if len(bench.Subs.Samples) != 1 { 152 | t.Fatal("Expected one subscriber") 153 | } 154 | if bench.MsgCnt != 2*Million { 155 | t.Fatal("Expected 2 million msgs") 156 | } 157 | if bench.IOBytes != 2*Million*MsgSize { 158 | t.Fatalf("Expected %d million bytes", 2*MsgSize) 159 | } 160 | if bench.Duration() != time.Second { 161 | t.Fatal("Expected duration to be 1 second") 162 | } 163 | } 164 | 165 | func makeBench(subs, pubs int) *Benchmark { 166 | bench := NewBenchmark("test", subs, pubs) 167 | for i := 0; i < subs; i++ { 168 | bench.AddSubSample(millionMessagesSecondSample(1)) 169 | } 170 | for i := 0; i < pubs; i++ { 171 | bench.AddPubSample(millionMessagesSecondSample(1)) 172 | } 173 | bench.Close() 174 | return bench 175 | } 176 | 177 | func TestCsv(t *testing.T) { 178 | bench := makeBench(1, 1) 179 | csv := bench.CSV() 180 | lines := strings.Split(csv, "\n") 181 | if len(lines) != 4 { 182 | t.Fatal("Expected 4 lines of output from the CSV string") 183 | } 184 | 185 | fields := strings.Split(lines[1], ",") 186 | if len(fields) != 7 { 187 | t.Fatal("Expected 7 fields") 188 | } 189 | } 190 | 191 | func TestBenchStrings(t *testing.T) { 192 | bench := makeBench(1, 1) 193 | s := bench.Report() 194 | lines := strings.Split(s, "\n") 195 | if len(lines) != 4 { 196 | t.Fatal("Expected 3 lines of output: header, pub, sub, empty") 197 | } 198 | 199 | bench = makeBench(2, 2) 200 | s = bench.Report() 201 | lines = strings.Split(s, "\n") 202 | if len(lines) != 10 { 203 | fmt.Printf("%q\n", s) 204 | 205 | t.Fatal("Expected 11 lines of output: header, pub header, pub x 2, stats, sub headers, sub x 2, stats, empty") 206 | } 207 | } 208 | 209 | func TestMsgsPerClient(t *testing.T) { 210 | zero := MsgsPerClient(0, 0) 211 | if len(zero) != 0 { 212 | t.Fatal("Expected 0 length for 0 clients") 213 | } 214 | onetwo := MsgsPerClient(1, 2) 215 | if len(onetwo) != 2 || onetwo[0] != 1 || onetwo[1] != 0 { 216 | t.Fatal("Expected uneven distribution") 217 | } 218 | twotwo := MsgsPerClient(2, 2) 219 | if len(twotwo) != 2 || twotwo[0] != 1 || twotwo[1] != 1 { 220 | t.Fatal("Expected even distribution") 221 | } 222 | threetwo := MsgsPerClient(3, 2) 223 | if len(threetwo) != 2 || threetwo[0] != 2 || threetwo[1] != 1 { 224 | t.Fatal("Expected uneven distribution") 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package nats_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/nats-io/go-nats" 8 | ) 9 | 10 | // Shows different ways to create a Conn 11 | func ExampleConnect() { 12 | 13 | nc, _ := nats.Connect(nats.DefaultURL) 14 | nc.Close() 15 | 16 | nc, _ = nats.Connect("nats://derek:secretpassword@demo.nats.io:4222") 17 | nc.Close() 18 | 19 | nc, _ = nats.Connect("tls://derek:secretpassword@demo.nats.io:4443") 20 | nc.Close() 21 | 22 | opts := nats.Options{ 23 | AllowReconnect: true, 24 | MaxReconnect: 10, 25 | ReconnectWait: 5 * time.Second, 26 | Timeout: 1 * time.Second, 27 | } 28 | 29 | nc, _ = opts.Connect() 30 | nc.Close() 31 | } 32 | 33 | // This Example shows an asynchronous subscriber. 34 | func ExampleConn_Subscribe() { 35 | nc, _ := nats.Connect(nats.DefaultURL) 36 | defer nc.Close() 37 | 38 | nc.Subscribe("foo", func(m *nats.Msg) { 39 | fmt.Printf("Received a message: %s\n", string(m.Data)) 40 | }) 41 | } 42 | 43 | // This Example shows a synchronous subscriber. 44 | func ExampleConn_SubscribeSync() { 45 | nc, _ := nats.Connect(nats.DefaultURL) 46 | defer nc.Close() 47 | 48 | sub, _ := nc.SubscribeSync("foo") 49 | m, err := sub.NextMsg(1 * time.Second) 50 | if err == nil { 51 | fmt.Printf("Received a message: %s\n", string(m.Data)) 52 | } else { 53 | fmt.Println("NextMsg timed out.") 54 | } 55 | } 56 | 57 | func ExampleSubscription_NextMsg() { 58 | nc, _ := nats.Connect(nats.DefaultURL) 59 | defer nc.Close() 60 | 61 | sub, _ := nc.SubscribeSync("foo") 62 | m, err := sub.NextMsg(1 * time.Second) 63 | if err == nil { 64 | fmt.Printf("Received a message: %s\n", string(m.Data)) 65 | } else { 66 | fmt.Println("NextMsg timed out.") 67 | } 68 | } 69 | 70 | func ExampleSubscription_Unsubscribe() { 71 | nc, _ := nats.Connect(nats.DefaultURL) 72 | defer nc.Close() 73 | 74 | sub, _ := nc.SubscribeSync("foo") 75 | // ... 76 | sub.Unsubscribe() 77 | } 78 | 79 | func ExampleConn_Publish() { 80 | nc, _ := nats.Connect(nats.DefaultURL) 81 | defer nc.Close() 82 | 83 | nc.Publish("foo", []byte("Hello World!")) 84 | } 85 | 86 | func ExampleConn_PublishMsg() { 87 | nc, _ := nats.Connect(nats.DefaultURL) 88 | defer nc.Close() 89 | 90 | msg := &nats.Msg{Subject: "foo", Reply: "bar", Data: []byte("Hello World!")} 91 | nc.PublishMsg(msg) 92 | } 93 | 94 | func ExampleConn_Flush() { 95 | nc, _ := nats.Connect(nats.DefaultURL) 96 | defer nc.Close() 97 | 98 | msg := &nats.Msg{Subject: "foo", Reply: "bar", Data: []byte("Hello World!")} 99 | for i := 0; i < 1000; i++ { 100 | nc.PublishMsg(msg) 101 | } 102 | err := nc.Flush() 103 | if err == nil { 104 | // Everything has been processed by the server for nc *Conn. 105 | } 106 | } 107 | 108 | func ExampleConn_FlushTimeout() { 109 | nc, _ := nats.Connect(nats.DefaultURL) 110 | defer nc.Close() 111 | 112 | msg := &nats.Msg{Subject: "foo", Reply: "bar", Data: []byte("Hello World!")} 113 | for i := 0; i < 1000; i++ { 114 | nc.PublishMsg(msg) 115 | } 116 | // Only wait for up to 1 second for Flush 117 | err := nc.FlushTimeout(1 * time.Second) 118 | if err == nil { 119 | // Everything has been processed by the server for nc *Conn. 120 | } 121 | } 122 | 123 | func ExampleConn_Request() { 124 | nc, _ := nats.Connect(nats.DefaultURL) 125 | defer nc.Close() 126 | 127 | nc.Subscribe("foo", func(m *nats.Msg) { 128 | nc.Publish(m.Reply, []byte("I will help you")) 129 | }) 130 | nc.Request("foo", []byte("help"), 50*time.Millisecond) 131 | } 132 | 133 | func ExampleConn_QueueSubscribe() { 134 | nc, _ := nats.Connect(nats.DefaultURL) 135 | defer nc.Close() 136 | 137 | received := 0 138 | 139 | nc.QueueSubscribe("foo", "worker_group", func(_ *nats.Msg) { 140 | received++ 141 | }) 142 | } 143 | 144 | func ExampleSubscription_AutoUnsubscribe() { 145 | nc, _ := nats.Connect(nats.DefaultURL) 146 | defer nc.Close() 147 | 148 | received, wanted, total := 0, 10, 100 149 | 150 | sub, _ := nc.Subscribe("foo", func(_ *nats.Msg) { 151 | received++ 152 | }) 153 | sub.AutoUnsubscribe(wanted) 154 | 155 | for i := 0; i < total; i++ { 156 | nc.Publish("foo", []byte("Hello")) 157 | } 158 | nc.Flush() 159 | 160 | fmt.Printf("Received = %d", received) 161 | } 162 | 163 | func ExampleConn_Close() { 164 | nc, _ := nats.Connect(nats.DefaultURL) 165 | nc.Close() 166 | } 167 | 168 | // Shows how to wrap a Conn into an EncodedConn 169 | func ExampleNewEncodedConn() { 170 | nc, _ := nats.Connect(nats.DefaultURL) 171 | c, _ := nats.NewEncodedConn(nc, "json") 172 | c.Close() 173 | } 174 | 175 | // EncodedConn can publish virtually anything just 176 | // by passing it in. The encoder will be used to properly 177 | // encode the raw Go type 178 | func ExampleEncodedConn_Publish() { 179 | nc, _ := nats.Connect(nats.DefaultURL) 180 | c, _ := nats.NewEncodedConn(nc, "json") 181 | defer c.Close() 182 | 183 | type person struct { 184 | Name string 185 | Address string 186 | Age int 187 | } 188 | 189 | me := &person{Name: "derek", Age: 22, Address: "85 Second St"} 190 | c.Publish("hello", me) 191 | } 192 | 193 | // EncodedConn's subscribers will automatically decode the 194 | // wire data into the requested Go type using the Decode() 195 | // method of the registered Encoder. The callback signature 196 | // can also vary to include additional data, such as subject 197 | // and reply subjects. 198 | func ExampleEncodedConn_Subscribe() { 199 | nc, _ := nats.Connect(nats.DefaultURL) 200 | c, _ := nats.NewEncodedConn(nc, "json") 201 | defer c.Close() 202 | 203 | type person struct { 204 | Name string 205 | Address string 206 | Age int 207 | } 208 | 209 | c.Subscribe("hello", func(p *person) { 210 | fmt.Printf("Received a person! %+v\n", p) 211 | }) 212 | 213 | c.Subscribe("hello", func(subj, reply string, p *person) { 214 | fmt.Printf("Received a person on subject %s! %+v\n", subj, p) 215 | }) 216 | 217 | me := &person{Name: "derek", Age: 22, Address: "85 Second St"} 218 | c.Publish("hello", me) 219 | } 220 | 221 | // BindSendChan() allows binding of a Go channel to a nats 222 | // subject for publish operations. The Encoder attached to the 223 | // EncodedConn will be used for marshalling. 224 | func ExampleEncodedConn_BindSendChan() { 225 | nc, _ := nats.Connect(nats.DefaultURL) 226 | c, _ := nats.NewEncodedConn(nc, "json") 227 | defer c.Close() 228 | 229 | type person struct { 230 | Name string 231 | Address string 232 | Age int 233 | } 234 | 235 | ch := make(chan *person) 236 | c.BindSendChan("hello", ch) 237 | 238 | me := &person{Name: "derek", Age: 22, Address: "85 Second St"} 239 | ch <- me 240 | } 241 | 242 | // BindRecvChan() allows binding of a Go channel to a nats 243 | // subject for subscribe operations. The Encoder attached to the 244 | // EncodedConn will be used for un-marshalling. 245 | func ExampleEncodedConn_BindRecvChan() { 246 | nc, _ := nats.Connect(nats.DefaultURL) 247 | c, _ := nats.NewEncodedConn(nc, "json") 248 | defer c.Close() 249 | 250 | type person struct { 251 | Name string 252 | Address string 253 | Age int 254 | } 255 | 256 | ch := make(chan *person) 257 | c.BindRecvChan("hello", ch) 258 | 259 | me := &person{Name: "derek", Age: 22, Address: "85 Second St"} 260 | c.Publish("hello", me) 261 | 262 | // Receive the publish directly on a channel 263 | who := <-ch 264 | 265 | fmt.Printf("%v says hello!\n", who) 266 | } 267 | -------------------------------------------------------------------------------- /enc_test.go: -------------------------------------------------------------------------------- 1 | package nats_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/nats-io/go-nats" 9 | "github.com/nats-io/go-nats/encoders/protobuf" 10 | "github.com/nats-io/go-nats/encoders/protobuf/testdata" 11 | ) 12 | 13 | // Since we import above nats packages, we need to have a different 14 | // const name than TEST_PORT that we used on the other packages. 15 | const ENC_TEST_PORT = 8268 16 | 17 | var options = Options{ 18 | Url: fmt.Sprintf("nats://localhost:%d", ENC_TEST_PORT), 19 | AllowReconnect: true, 20 | MaxReconnect: 10, 21 | ReconnectWait: 100 * time.Millisecond, 22 | Timeout: DefaultTimeout, 23 | } 24 | 25 | //////////////////////////////////////////////////////////////////////////////// 26 | // Encoded connection tests 27 | //////////////////////////////////////////////////////////////////////////////// 28 | 29 | func TestPublishErrorAfterSubscribeDecodeError(t *testing.T) { 30 | ts := RunServerOnPort(ENC_TEST_PORT) 31 | defer ts.Shutdown() 32 | opts := options 33 | nc, _ := opts.Connect() 34 | defer nc.Close() 35 | c, _ := NewEncodedConn(nc, JSON_ENCODER) 36 | 37 | //Test message type 38 | type Message struct { 39 | Message string 40 | } 41 | const testSubj = "test" 42 | 43 | c.Subscribe(testSubj, func(msg *Message) {}) 44 | 45 | //Publish invalid json to catch decode error in subscription callback 46 | c.Publish(testSubj, `foo`) 47 | c.Flush() 48 | 49 | //Next publish should be successful 50 | if err := c.Publish(testSubj, Message{"2"}); err != nil { 51 | t.Error("Fail to send correct json message after decode error in subscription") 52 | } 53 | } 54 | 55 | func TestPublishErrorAfterInvalidPublishMessage(t *testing.T) { 56 | ts := RunServerOnPort(ENC_TEST_PORT) 57 | defer ts.Shutdown() 58 | opts := options 59 | nc, _ := opts.Connect() 60 | defer nc.Close() 61 | c, _ := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) 62 | const testSubj = "test" 63 | 64 | c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}) 65 | 66 | //Publish invalid protobuff message to catch decode error 67 | c.Publish(testSubj, "foo") 68 | 69 | //Next publish with valid protobuf message should be successful 70 | if err := c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}); err != nil { 71 | t.Error("Fail to send correct protobuf message after invalid message publishing", err) 72 | } 73 | } 74 | 75 | func TestVariousFailureConditions(t *testing.T) { 76 | ts := RunServerOnPort(ENC_TEST_PORT) 77 | defer ts.Shutdown() 78 | 79 | dch := make(chan bool) 80 | 81 | opts := options 82 | opts.AsyncErrorCB = func(_ *Conn, _ *Subscription, e error) { 83 | dch <- true 84 | } 85 | nc, _ := opts.Connect() 86 | nc.Close() 87 | 88 | if _, err := NewEncodedConn(nil, protobuf.PROTOBUF_ENCODER); err == nil { 89 | t.Fatal("Expected an error") 90 | } 91 | 92 | if _, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER); err == nil || err != ErrConnectionClosed { 93 | t.Fatalf("Wrong error: %v instead of %v", err, ErrConnectionClosed) 94 | } 95 | 96 | nc, _ = opts.Connect() 97 | defer nc.Close() 98 | 99 | if _, err := NewEncodedConn(nc, "foo"); err == nil { 100 | t.Fatal("Expected an error") 101 | } 102 | 103 | c, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) 104 | if err != nil { 105 | t.Fatalf("Unable to create encoded connection: %v", err) 106 | } 107 | defer c.Close() 108 | 109 | if _, err := c.Subscribe("bar", func(subj, obj string) {}); err != nil { 110 | t.Fatalf("Unable to create subscription: %v", err) 111 | } 112 | 113 | if err := c.Publish("bar", &testdata.Person{Name: "Ivan"}); err != nil { 114 | t.Fatalf("Unable to publish: %v", err) 115 | } 116 | 117 | if err := Wait(dch); err != nil { 118 | t.Fatal("Did not get the async error callback") 119 | } 120 | 121 | if err := c.PublishRequest("foo", "bar", "foo"); err == nil { 122 | t.Fatal("Expected an error") 123 | } 124 | 125 | if err := c.Request("foo", "foo", nil, 2*time.Second); err == nil { 126 | t.Fatal("Expected an error") 127 | } 128 | 129 | nc.Close() 130 | 131 | if err := c.PublishRequest("foo", "bar", &testdata.Person{Name: "Ivan"}); err == nil { 132 | t.Fatal("Expected an error") 133 | } 134 | 135 | resp := &testdata.Person{} 136 | if err := c.Request("foo", &testdata.Person{Name: "Ivan"}, resp, 2*time.Second); err == nil { 137 | t.Fatal("Expected an error") 138 | } 139 | 140 | if _, err := c.Subscribe("foo", nil); err == nil { 141 | t.Fatal("Expected an error") 142 | } 143 | 144 | if _, err := c.Subscribe("foo", func() {}); err == nil { 145 | t.Fatal("Expected an error") 146 | } 147 | 148 | func() { 149 | defer func() { 150 | if r := recover(); r == nil { 151 | t.Fatal("Expected an error") 152 | } 153 | }() 154 | if _, err := c.Subscribe("foo", "bar"); err == nil { 155 | t.Fatal("Expected an error") 156 | } 157 | }() 158 | } 159 | 160 | func TestRequest(t *testing.T) { 161 | ts := RunServerOnPort(ENC_TEST_PORT) 162 | defer ts.Shutdown() 163 | 164 | dch := make(chan bool) 165 | 166 | opts := options 167 | nc, _ := opts.Connect() 168 | defer nc.Close() 169 | 170 | c, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) 171 | if err != nil { 172 | t.Fatalf("Unable to create encoded connection: %v", err) 173 | } 174 | defer c.Close() 175 | 176 | sentName := "Ivan" 177 | recvName := "Kozlovic" 178 | 179 | if _, err := c.Subscribe("foo", func(_, reply string, p *testdata.Person) { 180 | if p.Name != sentName { 181 | t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName) 182 | } 183 | c.Publish(reply, &testdata.Person{Name: recvName}) 184 | dch <- true 185 | }); err != nil { 186 | t.Fatalf("Unable to create subscription: %v", err) 187 | } 188 | if _, err := c.Subscribe("foo", func(_ string, p *testdata.Person) { 189 | if p.Name != sentName { 190 | t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName) 191 | } 192 | dch <- true 193 | }); err != nil { 194 | t.Fatalf("Unable to create subscription: %v", err) 195 | } 196 | 197 | if err := c.Publish("foo", &testdata.Person{Name: sentName}); err != nil { 198 | t.Fatalf("Unable to publish: %v", err) 199 | } 200 | 201 | if err := Wait(dch); err != nil { 202 | t.Fatal("Did not get message") 203 | } 204 | if err := Wait(dch); err != nil { 205 | t.Fatal("Did not get message") 206 | } 207 | 208 | response := &testdata.Person{} 209 | if err := c.Request("foo", &testdata.Person{Name: sentName}, response, 2*time.Second); err != nil { 210 | t.Fatalf("Unable to publish: %v", err) 211 | } 212 | if response == nil { 213 | t.Fatal("No response received") 214 | } else if response.Name != recvName { 215 | t.Fatalf("Wrong response: %v instead of %v", response.Name, recvName) 216 | } 217 | 218 | if err := Wait(dch); err != nil { 219 | t.Fatal("Did not get message") 220 | } 221 | if err := Wait(dch); err != nil { 222 | t.Fatal("Did not get message") 223 | } 224 | 225 | c2, err := NewEncodedConn(nc, GOB_ENCODER) 226 | if err != nil { 227 | t.Fatalf("Unable to create encoded connection: %v", err) 228 | } 229 | defer c2.Close() 230 | 231 | if _, err := c2.QueueSubscribe("bar", "baz", func(m *Msg) { 232 | response := &Msg{Subject: m.Reply, Data: []byte(recvName)} 233 | c2.Conn.PublishMsg(response) 234 | dch <- true 235 | }); err != nil { 236 | t.Fatalf("Unable to create subscription: %v", err) 237 | } 238 | 239 | mReply := Msg{} 240 | if err := c2.Request("bar", &Msg{Data: []byte(sentName)}, &mReply, 2*time.Second); err != nil { 241 | t.Fatalf("Unable to send request: %v", err) 242 | } 243 | if string(mReply.Data) != recvName { 244 | t.Fatalf("Wrong reply: %v instead of %v", string(mReply.Data), recvName) 245 | } 246 | 247 | if err := Wait(dch); err != nil { 248 | t.Fatal("Did not get message") 249 | } 250 | 251 | if c.LastError() != nil { 252 | t.Fatalf("Unexpected connection error: %v", c.LastError()) 253 | } 254 | if c2.LastError() != nil { 255 | t.Fatalf("Unexpected connection error: %v", c2.LastError()) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /enc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Apcera Inc. All rights reserved. 2 | 3 | package nats 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "reflect" 9 | "sync" 10 | "time" 11 | 12 | // Default Encoders 13 | . "github.com/nats-io/go-nats/encoders/builtin" 14 | ) 15 | 16 | // Encoder interface is for all register encoders 17 | type Encoder interface { 18 | Encode(subject string, v interface{}) ([]byte, error) 19 | Decode(subject string, data []byte, vPtr interface{}) error 20 | } 21 | 22 | var encMap map[string]Encoder 23 | var encLock sync.Mutex 24 | 25 | // Indexe names into the Registered Encoders. 26 | const ( 27 | JSON_ENCODER = "json" 28 | GOB_ENCODER = "gob" 29 | DEFAULT_ENCODER = "default" 30 | ) 31 | 32 | func init() { 33 | encMap = make(map[string]Encoder) 34 | // Register json, gob and default encoder 35 | RegisterEncoder(JSON_ENCODER, &JsonEncoder{}) 36 | RegisterEncoder(GOB_ENCODER, &GobEncoder{}) 37 | RegisterEncoder(DEFAULT_ENCODER, &DefaultEncoder{}) 38 | } 39 | 40 | // EncodedConn are the preferred way to interface with NATS. They wrap a bare connection to 41 | // a nats server and have an extendable encoder system that will encode and decode messages 42 | // from raw Go types. 43 | type EncodedConn struct { 44 | Conn *Conn 45 | Enc Encoder 46 | } 47 | 48 | // NewEncodedConn will wrap an existing Connection and utilize the appropriate registered 49 | // encoder. 50 | func NewEncodedConn(c *Conn, encType string) (*EncodedConn, error) { 51 | if c == nil { 52 | return nil, errors.New("nats: Nil Connection") 53 | } 54 | if c.IsClosed() { 55 | return nil, ErrConnectionClosed 56 | } 57 | ec := &EncodedConn{Conn: c, Enc: EncoderForType(encType)} 58 | if ec.Enc == nil { 59 | return nil, fmt.Errorf("No encoder registered for '%s'", encType) 60 | } 61 | return ec, nil 62 | } 63 | 64 | // RegisterEncoder will register the encType with the given Encoder. Useful for customization. 65 | func RegisterEncoder(encType string, enc Encoder) { 66 | encLock.Lock() 67 | defer encLock.Unlock() 68 | encMap[encType] = enc 69 | } 70 | 71 | // EncoderForType will return the registered Encoder for the encType. 72 | func EncoderForType(encType string) Encoder { 73 | encLock.Lock() 74 | defer encLock.Unlock() 75 | return encMap[encType] 76 | } 77 | 78 | // Publish publishes the data argument to the given subject. The data argument 79 | // will be encoded using the associated encoder. 80 | func (c *EncodedConn) Publish(subject string, v interface{}) error { 81 | b, err := c.Enc.Encode(subject, v) 82 | if err != nil { 83 | return err 84 | } 85 | return c.Conn.publish(subject, _EMPTY_, b) 86 | } 87 | 88 | // PublishRequest will perform a Publish() expecting a response on the 89 | // reply subject. Use Request() for automatically waiting for a response 90 | // inline. 91 | func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error { 92 | b, err := c.Enc.Encode(subject, v) 93 | if err != nil { 94 | return err 95 | } 96 | return c.Conn.publish(subject, reply, b) 97 | } 98 | 99 | // Request will create an Inbox and perform a Request() call 100 | // with the Inbox reply for the data v. A response will be 101 | // decoded into the vPtrResponse. 102 | func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error { 103 | b, err := c.Enc.Encode(subject, v) 104 | if err != nil { 105 | return err 106 | } 107 | m, err := c.Conn.Request(subject, b, timeout) 108 | if err != nil { 109 | return err 110 | } 111 | if reflect.TypeOf(vPtr) == emptyMsgType { 112 | mPtr := vPtr.(*Msg) 113 | *mPtr = *m 114 | } else { 115 | err = c.Enc.Decode(m.Subject, m.Data, vPtr) 116 | } 117 | return err 118 | } 119 | 120 | // Handler is a specific callback used for Subscribe. It is generalized to 121 | // an interface{}, but we will discover its format and arguments at runtime 122 | // and perform the correct callback, including de-marshalling JSON strings 123 | // back into the appropriate struct based on the signature of the Handler. 124 | // 125 | // Handlers are expected to have one of four signatures. 126 | // 127 | // type person struct { 128 | // Name string `json:"name,omitempty"` 129 | // Age uint `json:"age,omitempty"` 130 | // } 131 | // 132 | // handler := func(m *Msg) 133 | // handler := func(p *person) 134 | // handler := func(subject string, o *obj) 135 | // handler := func(subject, reply string, o *obj) 136 | // 137 | // These forms allow a callback to request a raw Msg ptr, where the processing 138 | // of the message from the wire is untouched. Process a JSON representation 139 | // and demarshal it into the given struct, e.g. person. 140 | // There are also variants where the callback wants either the subject, or the 141 | // subject and the reply subject. 142 | type Handler interface{} 143 | 144 | // Dissect the cb Handler's signature 145 | func argInfo(cb Handler) (reflect.Type, int) { 146 | cbType := reflect.TypeOf(cb) 147 | if cbType.Kind() != reflect.Func { 148 | panic("nats: Handler needs to be a func") 149 | } 150 | numArgs := cbType.NumIn() 151 | if numArgs == 0 { 152 | return nil, numArgs 153 | } 154 | return cbType.In(numArgs - 1), numArgs 155 | } 156 | 157 | var emptyMsgType = reflect.TypeOf(&Msg{}) 158 | 159 | // Subscribe will create a subscription on the given subject and process incoming 160 | // messages using the specified Handler. The Handler should be a func that matches 161 | // a signature from the description of Handler from above. 162 | func (c *EncodedConn) Subscribe(subject string, cb Handler) (*Subscription, error) { 163 | return c.subscribe(subject, _EMPTY_, cb) 164 | } 165 | 166 | // QueueSubscribe will create a queue subscription on the given subject and process 167 | // incoming messages using the specified Handler. The Handler should be a func that 168 | // matches a signature from the description of Handler from above. 169 | func (c *EncodedConn) QueueSubscribe(subject, queue string, cb Handler) (*Subscription, error) { 170 | return c.subscribe(subject, queue, cb) 171 | } 172 | 173 | // Internal implementation that all public functions will use. 174 | func (c *EncodedConn) subscribe(subject, queue string, cb Handler) (*Subscription, error) { 175 | if cb == nil { 176 | return nil, errors.New("nats: Handler required for EncodedConn Subscription") 177 | } 178 | argType, numArgs := argInfo(cb) 179 | if argType == nil { 180 | return nil, errors.New("nats: Handler requires at least one argument") 181 | } 182 | 183 | cbValue := reflect.ValueOf(cb) 184 | wantsRaw := (argType == emptyMsgType) 185 | 186 | natsCB := func(m *Msg) { 187 | var oV []reflect.Value 188 | if wantsRaw { 189 | oV = []reflect.Value{reflect.ValueOf(m)} 190 | } else { 191 | var oPtr reflect.Value 192 | if argType.Kind() != reflect.Ptr { 193 | oPtr = reflect.New(argType) 194 | } else { 195 | oPtr = reflect.New(argType.Elem()) 196 | } 197 | if err := c.Enc.Decode(m.Subject, m.Data, oPtr.Interface()); err != nil { 198 | if c.Conn.Opts.AsyncErrorCB != nil { 199 | c.Conn.ach <- func() { 200 | c.Conn.Opts.AsyncErrorCB(c.Conn, m.Sub, errors.New("nats: Got an error trying to unmarshal: "+err.Error())) 201 | } 202 | } 203 | return 204 | } 205 | if argType.Kind() != reflect.Ptr { 206 | oPtr = reflect.Indirect(oPtr) 207 | } 208 | 209 | // Callback Arity 210 | switch numArgs { 211 | case 1: 212 | oV = []reflect.Value{oPtr} 213 | case 2: 214 | subV := reflect.ValueOf(m.Subject) 215 | oV = []reflect.Value{subV, oPtr} 216 | case 3: 217 | subV := reflect.ValueOf(m.Subject) 218 | replyV := reflect.ValueOf(m.Reply) 219 | oV = []reflect.Value{subV, replyV, oPtr} 220 | } 221 | 222 | } 223 | cbValue.Call(oV) 224 | } 225 | 226 | return c.Conn.subscribe(subject, queue, natsCB, nil) 227 | } 228 | 229 | // FlushTimeout allows a Flush operation to have an associated timeout. 230 | func (c *EncodedConn) FlushTimeout(timeout time.Duration) (err error) { 231 | return c.Conn.FlushTimeout(timeout) 232 | } 233 | 234 | // Flush will perform a round trip to the server and return when it 235 | // receives the internal reply. 236 | func (c *EncodedConn) Flush() error { 237 | return c.Conn.Flush() 238 | } 239 | 240 | // Close will close the connection to the server. This call will release 241 | // all blocking calls, such as Flush(), etc. 242 | func (c *EncodedConn) Close() { 243 | c.Conn.Close() 244 | } 245 | 246 | // LastError reports the last error encountered via the Connection. 247 | func (c *EncodedConn) LastError() error { 248 | return c.Conn.err 249 | } 250 | -------------------------------------------------------------------------------- /test/netchan_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | 8 | "github.com/nats-io/go-nats" 9 | ) 10 | 11 | func TestBadChan(t *testing.T) { 12 | s := RunDefaultServer() 13 | defer s.Shutdown() 14 | 15 | ec := NewEConn(t) 16 | defer ec.Close() 17 | 18 | if err := ec.BindSendChan("foo", "not a chan"); err == nil { 19 | t.Fatalf("Expected an Error when sending a non-channel\n") 20 | } 21 | 22 | if _, err := ec.BindRecvChan("foo", "not a chan"); err == nil { 23 | t.Fatalf("Expected an Error when sending a non-channel\n") 24 | } 25 | 26 | if err := ec.BindSendChan("foo", "not a chan"); err != nats.ErrChanArg { 27 | t.Fatalf("Expected an ErrChanArg when sending a non-channel\n") 28 | } 29 | 30 | if _, err := ec.BindRecvChan("foo", "not a chan"); err != nats.ErrChanArg { 31 | t.Fatalf("Expected an ErrChanArg when sending a non-channel\n") 32 | } 33 | } 34 | 35 | func TestSimpleSendChan(t *testing.T) { 36 | s := RunDefaultServer() 37 | defer s.Shutdown() 38 | 39 | ec := NewEConn(t) 40 | defer ec.Close() 41 | 42 | recv := make(chan bool) 43 | 44 | numSent := int32(22) 45 | ch := make(chan int32) 46 | 47 | if err := ec.BindSendChan("foo", ch); err != nil { 48 | t.Fatalf("Failed to bind to a send channel: %v\n", err) 49 | } 50 | 51 | ec.Subscribe("foo", func(num int32) { 52 | if num != numSent { 53 | t.Fatalf("Failed to receive correct value: %d vs %d\n", num, numSent) 54 | } 55 | recv <- true 56 | }) 57 | 58 | // Send to 'foo' 59 | ch <- numSent 60 | 61 | if e := Wait(recv); e != nil { 62 | if ec.LastError() != nil { 63 | e = ec.LastError() 64 | } 65 | t.Fatalf("Did not receive the message: %s", e) 66 | } 67 | close(ch) 68 | } 69 | 70 | func TestFailedChannelSend(t *testing.T) { 71 | s := RunDefaultServer() 72 | defer s.Shutdown() 73 | 74 | ec := NewEConn(t) 75 | defer ec.Close() 76 | 77 | nc := ec.Conn 78 | ch := make(chan bool) 79 | wch := make(chan bool) 80 | 81 | nc.Opts.AsyncErrorCB = func(c *nats.Conn, s *nats.Subscription, e error) { 82 | wch <- true 83 | } 84 | 85 | if err := ec.BindSendChan("foo", ch); err != nil { 86 | t.Fatalf("Failed to bind to a receive channel: %v\n", err) 87 | } 88 | 89 | nc.Flush() 90 | 91 | go func() { 92 | time.Sleep(100 * time.Millisecond) 93 | nc.Close() 94 | }() 95 | 96 | func() { 97 | for { 98 | select { 99 | case ch <- true: 100 | case <-wch: 101 | return 102 | case <-time.After(time.Second): 103 | t.Fatal("Failed to get async error cb") 104 | } 105 | } 106 | }() 107 | 108 | ec = NewEConn(t) 109 | defer ec.Close() 110 | 111 | nc = ec.Conn 112 | bch := make(chan []byte) 113 | 114 | nc.Opts.AsyncErrorCB = func(c *nats.Conn, s *nats.Subscription, e error) { 115 | wch <- true 116 | } 117 | 118 | if err := ec.BindSendChan("foo", bch); err != nil { 119 | t.Fatalf("Failed to bind to a receive channel: %v\n", err) 120 | } 121 | 122 | buf := make([]byte, 2*1024*1024) 123 | bch <- buf 124 | 125 | if e := Wait(wch); e != nil { 126 | t.Fatal("Failed to call async err handler") 127 | } 128 | } 129 | 130 | func TestSimpleRecvChan(t *testing.T) { 131 | s := RunDefaultServer() 132 | defer s.Shutdown() 133 | 134 | ec := NewEConn(t) 135 | defer ec.Close() 136 | 137 | numSent := int32(22) 138 | ch := make(chan int32) 139 | 140 | if _, err := ec.BindRecvChan("foo", ch); err != nil { 141 | t.Fatalf("Failed to bind to a receive channel: %v\n", err) 142 | } 143 | 144 | ec.Publish("foo", numSent) 145 | 146 | // Receive from 'foo' 147 | select { 148 | case num := <-ch: 149 | if num != numSent { 150 | t.Fatalf("Failed to receive correct value: %d vs %d\n", num, numSent) 151 | } 152 | case <-time.After(1 * time.Second): 153 | t.Fatalf("Failed to receive a value, timed-out\n") 154 | } 155 | close(ch) 156 | } 157 | 158 | func TestQueueRecvChan(t *testing.T) { 159 | s := RunDefaultServer() 160 | defer s.Shutdown() 161 | 162 | ec := NewEConn(t) 163 | defer ec.Close() 164 | 165 | numSent := int32(22) 166 | ch := make(chan int32) 167 | 168 | if _, err := ec.BindRecvQueueChan("foo", "bar", ch); err != nil { 169 | t.Fatalf("Failed to bind to a queue receive channel: %v\n", err) 170 | } 171 | 172 | ec.Publish("foo", numSent) 173 | 174 | // Receive from 'foo' 175 | select { 176 | case num := <-ch: 177 | if num != numSent { 178 | t.Fatalf("Failed to receive correct value: %d vs %d\n", num, numSent) 179 | } 180 | case <-time.After(1 * time.Second): 181 | t.Fatalf("Failed to receive a value, timed-out\n") 182 | } 183 | close(ch) 184 | } 185 | 186 | func TestDecoderErrRecvChan(t *testing.T) { 187 | s := RunDefaultServer() 188 | defer s.Shutdown() 189 | 190 | ec := NewEConn(t) 191 | defer ec.Close() 192 | nc := ec.Conn 193 | wch := make(chan bool) 194 | 195 | nc.Opts.AsyncErrorCB = func(c *nats.Conn, s *nats.Subscription, e error) { 196 | wch <- true 197 | } 198 | 199 | ch := make(chan *int32) 200 | 201 | if _, err := ec.BindRecvChan("foo", ch); err != nil { 202 | t.Fatalf("Failed to bind to a send channel: %v\n", err) 203 | } 204 | 205 | ec.Publish("foo", "Hello World") 206 | 207 | if e := Wait(wch); e != nil { 208 | t.Fatal("Failed to call async err handler") 209 | } 210 | } 211 | 212 | func TestRecvChanPanicOnClosedChan(t *testing.T) { 213 | s := RunDefaultServer() 214 | defer s.Shutdown() 215 | 216 | ec := NewEConn(t) 217 | defer ec.Close() 218 | 219 | ch := make(chan int) 220 | 221 | if _, err := ec.BindRecvChan("foo", ch); err != nil { 222 | t.Fatalf("Failed to bind to a send channel: %v\n", err) 223 | } 224 | 225 | close(ch) 226 | ec.Publish("foo", 22) 227 | ec.Flush() 228 | } 229 | 230 | func TestRecvChanAsyncLeakGoRoutines(t *testing.T) { 231 | s := RunDefaultServer() 232 | defer s.Shutdown() 233 | 234 | ec := NewEConn(t) 235 | defer ec.Close() 236 | 237 | // Call this to make sure that we have everything setup connection wise 238 | ec.Flush() 239 | 240 | before := runtime.NumGoroutine() 241 | 242 | ch := make(chan int) 243 | 244 | if _, err := ec.BindRecvChan("foo", ch); err != nil { 245 | t.Fatalf("Failed to bind to a send channel: %v\n", err) 246 | } 247 | 248 | // Close the receive Channel 249 | close(ch) 250 | 251 | // The publish will trigger the close and shutdown of the Go routines 252 | ec.Publish("foo", 22) 253 | ec.Flush() 254 | 255 | time.Sleep(100 * time.Millisecond) 256 | 257 | delta := (runtime.NumGoroutine() - before) 258 | 259 | if delta > 0 { 260 | t.Fatalf("Leaked Go routine(s) : %d, closing channel should have closed them\n", delta) 261 | } 262 | } 263 | 264 | func TestRecvChanLeakGoRoutines(t *testing.T) { 265 | s := RunDefaultServer() 266 | defer s.Shutdown() 267 | 268 | ec := NewEConn(t) 269 | defer ec.Close() 270 | 271 | // Call this to make sure that we have everything setup connection wise 272 | ec.Flush() 273 | 274 | before := runtime.NumGoroutine() 275 | 276 | ch := make(chan int) 277 | 278 | sub, err := ec.BindRecvChan("foo", ch) 279 | if err != nil { 280 | t.Fatalf("Failed to bind to a send channel: %v\n", err) 281 | } 282 | sub.Unsubscribe() 283 | 284 | // Sleep a bit to wait for the Go routine to exit. 285 | time.Sleep(500 * time.Millisecond) 286 | 287 | delta := (runtime.NumGoroutine() - before) 288 | 289 | if delta > 0 { 290 | t.Fatalf("Leaked Go routine(s) : %d, closing channel should have closed them\n", delta) 291 | } 292 | } 293 | 294 | func TestRecvChanMultipleMessages(t *testing.T) { 295 | // Make sure we can receive more than one message. 296 | // In response to #25, which is a bug from fixing #22. 297 | 298 | s := RunDefaultServer() 299 | defer s.Shutdown() 300 | 301 | ec := NewEConn(t) 302 | defer ec.Close() 303 | 304 | // Num to send, should == len of messages queued. 305 | size := 10 306 | 307 | ch := make(chan int, size) 308 | 309 | if _, err := ec.BindRecvChan("foo", ch); err != nil { 310 | t.Fatalf("Failed to bind to a send channel: %v\n", err) 311 | } 312 | 313 | for i := 0; i < size; i++ { 314 | ec.Publish("foo", 22) 315 | } 316 | ec.Flush() 317 | time.Sleep(10 * time.Millisecond) 318 | 319 | if lch := len(ch); lch != size { 320 | t.Fatalf("Expected %d messages queued, got %d.", size, lch) 321 | } 322 | } 323 | 324 | func BenchmarkPublishSpeedViaChan(b *testing.B) { 325 | b.StopTimer() 326 | 327 | s := RunDefaultServer() 328 | defer s.Shutdown() 329 | 330 | nc, err := nats.Connect(nats.DefaultURL) 331 | if err != nil { 332 | b.Fatalf("Could not connect: %v\n", err) 333 | } 334 | ec, err := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER) 335 | if err != nil { 336 | b.Fatalf("Failed creating encoded connection: %v\n", err) 337 | } 338 | defer ec.Close() 339 | 340 | ch := make(chan int32, 1024) 341 | if err := ec.BindSendChan("foo", ch); err != nil { 342 | b.Fatalf("Failed to bind to a send channel: %v\n", err) 343 | } 344 | 345 | b.StartTimer() 346 | 347 | num := int32(22) 348 | 349 | for i := 0; i < b.N; i++ { 350 | ch <- num 351 | } 352 | // Make sure they are all processed. 353 | nc.Flush() 354 | b.StopTimer() 355 | } 356 | -------------------------------------------------------------------------------- /bench/bench.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Apcera Inc. All rights reserved. 2 | 3 | package bench 4 | 5 | import ( 6 | "bytes" 7 | "encoding/csv" 8 | "fmt" 9 | "log" 10 | "math" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/nats-io/go-nats" 15 | "github.com/nats-io/nuid" 16 | ) 17 | 18 | // A Sample for a particular client 19 | type Sample struct { 20 | JobMsgCnt int 21 | MsgCnt uint64 22 | MsgBytes uint64 23 | IOBytes uint64 24 | Start time.Time 25 | End time.Time 26 | } 27 | 28 | // SampleGroup for a number of samples, the group is a Sample itself agregating the values the Samples 29 | type SampleGroup struct { 30 | Sample 31 | Samples []*Sample 32 | } 33 | 34 | // Benchmark to hold the various Samples organized by publishers and subscribers 35 | type Benchmark struct { 36 | Sample 37 | Name string 38 | RunID string 39 | Pubs *SampleGroup 40 | Subs *SampleGroup 41 | subChannel chan *Sample 42 | pubChannel chan *Sample 43 | } 44 | 45 | // NewBenchmark initializes a Benchmark. After creating a bench call AddSubSample/AddPubSample. 46 | // When done collecting samples, call EndBenchmark 47 | func NewBenchmark(name string, subCnt, pubCnt int) *Benchmark { 48 | bm := Benchmark{Name: name, RunID: nuid.Next()} 49 | bm.Subs = NewSampleGroup() 50 | bm.Pubs = NewSampleGroup() 51 | bm.subChannel = make(chan *Sample, subCnt) 52 | bm.pubChannel = make(chan *Sample, pubCnt) 53 | return &bm 54 | } 55 | 56 | // Close organizes collected Samples and calculates aggregates. After Close(), no more samples can be added. 57 | func (bm *Benchmark) Close() { 58 | close(bm.subChannel) 59 | close(bm.pubChannel) 60 | 61 | for s := range bm.subChannel { 62 | bm.Subs.AddSample(s) 63 | } 64 | for s := range bm.pubChannel { 65 | bm.Pubs.AddSample(s) 66 | } 67 | 68 | if bm.Subs.HasSamples() { 69 | bm.Start = bm.Subs.Start 70 | bm.End = bm.Subs.End 71 | } else { 72 | bm.Start = bm.Pubs.Start 73 | bm.End = bm.Pubs.End 74 | } 75 | 76 | if bm.Subs.HasSamples() && bm.Pubs.HasSamples() { 77 | if bm.Start.After(bm.Subs.Start) { 78 | bm.Start = bm.Subs.Start 79 | } 80 | if bm.Start.After(bm.Pubs.Start) { 81 | bm.Start = bm.Pubs.Start 82 | } 83 | 84 | if bm.End.Before(bm.Subs.End) { 85 | bm.End = bm.Subs.End 86 | } 87 | if bm.End.Before(bm.Pubs.End) { 88 | bm.End = bm.Pubs.End 89 | } 90 | } 91 | 92 | bm.MsgBytes = bm.Pubs.MsgBytes + bm.Subs.MsgBytes 93 | bm.IOBytes = bm.Pubs.IOBytes + bm.Subs.IOBytes 94 | bm.MsgCnt = bm.Pubs.MsgCnt + bm.Subs.MsgCnt 95 | bm.JobMsgCnt = bm.Pubs.JobMsgCnt + bm.Subs.JobMsgCnt 96 | } 97 | 98 | // AddSubSample to the benchmark 99 | func (bm *Benchmark) AddSubSample(s *Sample) { 100 | bm.subChannel <- s 101 | } 102 | 103 | // AddPubSample to the benchmark 104 | func (bm *Benchmark) AddPubSample(s *Sample) { 105 | bm.pubChannel <- s 106 | } 107 | 108 | // CSV generates a csv report of all the samples collected 109 | func (bm *Benchmark) CSV() string { 110 | var buffer bytes.Buffer 111 | writer := csv.NewWriter(&buffer) 112 | headers := []string{"#RunID", "ClientID", "MsgCount", "MsgBytes", "MsgsPerSec", "BytesPerSec", "DurationSecs"} 113 | if err := writer.Write(headers); err != nil { 114 | log.Fatalf("Error while serializing headers %q: %v", headers, err) 115 | } 116 | groups := []*SampleGroup{bm.Subs, bm.Pubs} 117 | pre := "S" 118 | for i, g := range groups { 119 | if i == 1 { 120 | pre = "P" 121 | } 122 | for j, c := range g.Samples { 123 | r := []string{bm.RunID, fmt.Sprintf("%s%d", pre, j), fmt.Sprintf("%d", c.MsgCnt), fmt.Sprintf("%d", c.MsgBytes), fmt.Sprintf("%d", c.Rate()), fmt.Sprintf("%f", c.Throughput()), fmt.Sprintf("%f", c.Duration().Seconds())} 124 | if err := writer.Write(r); err != nil { 125 | log.Fatalf("Error while serializing %v: %v", c, err) 126 | } 127 | } 128 | } 129 | 130 | writer.Flush() 131 | return buffer.String() 132 | } 133 | 134 | // NewSample creates a new Sample initialized to the provided values. The nats.Conn information captured 135 | func NewSample(jobCount int, msgSize int, start, end time.Time, nc *nats.Conn) *Sample { 136 | s := Sample{JobMsgCnt: jobCount, Start: start, End: end} 137 | s.MsgBytes = uint64(msgSize * jobCount) 138 | s.MsgCnt = nc.OutMsgs + nc.InMsgs 139 | s.IOBytes = nc.OutBytes + nc.InBytes 140 | return &s 141 | } 142 | 143 | // Throughput of bytes per second 144 | func (s *Sample) Throughput() float64 { 145 | return float64(s.MsgBytes) / s.Duration().Seconds() 146 | } 147 | 148 | // Rate of meessages in the job per second 149 | func (s *Sample) Rate() int64 { 150 | return int64(float64(s.JobMsgCnt) / s.Duration().Seconds()) 151 | } 152 | 153 | func (s *Sample) String() string { 154 | rate := commaFormat(s.Rate()) 155 | throughput := HumanBytes(s.Throughput(), false) 156 | return fmt.Sprintf("%s msgs/sec ~ %s/sec", rate, throughput) 157 | } 158 | 159 | // Duration that the sample was active 160 | func (s *Sample) Duration() time.Duration { 161 | return s.End.Sub(s.Start) 162 | } 163 | 164 | // Seconds that the sample or samples were active 165 | func (s *Sample) Seconds() float64 { 166 | return s.Duration().Seconds() 167 | } 168 | 169 | // NewSampleGroup initializer 170 | func NewSampleGroup() *SampleGroup { 171 | s := new(SampleGroup) 172 | s.Samples = make([]*Sample, 0, 0) 173 | return s 174 | } 175 | 176 | // Statistics information of the sample group (min, average, max and standard deviation) 177 | func (sg *SampleGroup) Statistics() string { 178 | return fmt.Sprintf("min %s | avg %s | max %s | stddev %s msgs", commaFormat(sg.MinRate()), commaFormat(sg.AvgRate()), commaFormat(sg.MaxRate()), commaFormat(int64(sg.StdDev()))) 179 | } 180 | 181 | // MinRate returns the smallest message rate in the SampleGroup 182 | func (sg *SampleGroup) MinRate() int64 { 183 | m := int64(0) 184 | for i, s := range sg.Samples { 185 | if i == 0 { 186 | m = s.Rate() 187 | } 188 | m = min(m, s.Rate()) 189 | } 190 | return m 191 | } 192 | 193 | // MaxRate returns the largest message rate in the SampleGroup 194 | func (sg *SampleGroup) MaxRate() int64 { 195 | m := int64(0) 196 | for i, s := range sg.Samples { 197 | if i == 0 { 198 | m = s.Rate() 199 | } 200 | m = max(m, s.Rate()) 201 | } 202 | return m 203 | } 204 | 205 | // AvgRate returns the average of all the message rates in the SampleGroup 206 | func (sg *SampleGroup) AvgRate() int64 { 207 | sum := uint64(0) 208 | for _, s := range sg.Samples { 209 | sum += uint64(s.Rate()) 210 | } 211 | return int64(sum / uint64(len(sg.Samples))) 212 | } 213 | 214 | // StdDev returns the standard deviation the message rates in the SampleGroup 215 | func (sg *SampleGroup) StdDev() float64 { 216 | avg := float64(sg.AvgRate()) 217 | sum := float64(0) 218 | for _, c := range sg.Samples { 219 | sum += math.Pow(float64(c.Rate())-avg, 2) 220 | } 221 | variance := sum / float64(len(sg.Samples)) 222 | return math.Sqrt(variance) 223 | } 224 | 225 | // AddSample adds a Sample to the SampleGroup. After adding a Sample it shouldn't be modified. 226 | func (sg *SampleGroup) AddSample(e *Sample) { 227 | sg.Samples = append(sg.Samples, e) 228 | 229 | if len(sg.Samples) == 1 { 230 | sg.Start = e.Start 231 | sg.End = e.End 232 | } 233 | sg.IOBytes += e.IOBytes 234 | sg.JobMsgCnt += e.JobMsgCnt 235 | sg.MsgCnt += e.MsgCnt 236 | sg.MsgBytes += e.MsgBytes 237 | 238 | if e.Start.Before(sg.Start) { 239 | sg.Start = e.Start 240 | } 241 | 242 | if e.End.After(sg.End) { 243 | sg.End = e.End 244 | } 245 | } 246 | 247 | // HasSamples returns true if the group has samples 248 | func (sg *SampleGroup) HasSamples() bool { 249 | return len(sg.Samples) > 0 250 | } 251 | 252 | // Report returns a human readable report of the samples taken in the Benchmark 253 | func (bm *Benchmark) Report() string { 254 | var buffer bytes.Buffer 255 | 256 | indent := "" 257 | if !bm.Pubs.HasSamples() && !bm.Subs.HasSamples() { 258 | return "No publisher or subscribers. Nothing to report." 259 | } 260 | 261 | if bm.Pubs.HasSamples() && bm.Subs.HasSamples() { 262 | buffer.WriteString(fmt.Sprintf("%s Pub/Sub stats: %s\n", bm.Name, bm)) 263 | indent += " " 264 | } 265 | if bm.Pubs.HasSamples() { 266 | buffer.WriteString(fmt.Sprintf("%sPub stats: %s\n", indent, bm.Pubs)) 267 | if len(bm.Pubs.Samples) > 1 { 268 | for i, stat := range bm.Pubs.Samples { 269 | buffer.WriteString(fmt.Sprintf("%s [%d] %v (%d msgs)\n", indent, i+1, stat, stat.JobMsgCnt)) 270 | } 271 | buffer.WriteString(fmt.Sprintf("%s %s\n", indent, bm.Pubs.Statistics())) 272 | } 273 | } 274 | 275 | if bm.Subs.HasSamples() { 276 | buffer.WriteString(fmt.Sprintf("%sSub stats: %s\n", indent, bm.Subs)) 277 | if len(bm.Subs.Samples) > 1 { 278 | for i, stat := range bm.Subs.Samples { 279 | buffer.WriteString(fmt.Sprintf("%s [%d] %v (%d msgs)\n", indent, i+1, stat, stat.JobMsgCnt)) 280 | } 281 | buffer.WriteString(fmt.Sprintf("%s %s\n", indent, bm.Subs.Statistics())) 282 | } 283 | } 284 | return buffer.String() 285 | } 286 | 287 | func commaFormat(n int64) string { 288 | in := strconv.FormatInt(n, 10) 289 | out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3) 290 | if in[0] == '-' { 291 | in, out[0] = in[1:], '-' 292 | } 293 | for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 { 294 | out[j] = in[i] 295 | if i == 0 { 296 | return string(out) 297 | } 298 | if k++; k == 3 { 299 | j, k = j-1, 0 300 | out[j] = ',' 301 | } 302 | } 303 | } 304 | 305 | // HumanBytes formats bytes as a human readable string 306 | func HumanBytes(bytes float64, si bool) string { 307 | var base = 1024 308 | pre := []string{"K", "M", "G", "T", "P", "E"} 309 | var post = "B" 310 | if si { 311 | base = 1000 312 | pre = []string{"k", "M", "G", "T", "P", "E"} 313 | post = "iB" 314 | } 315 | if bytes < float64(base) { 316 | return fmt.Sprintf("%.2f B", bytes) 317 | } 318 | exp := int(math.Log(bytes) / math.Log(float64(base))) 319 | index := exp - 1 320 | units := pre[index] + post 321 | return fmt.Sprintf("%.2f %s", bytes/math.Pow(float64(base), float64(exp)), units) 322 | } 323 | 324 | func min(x, y int64) int64 { 325 | if x < y { 326 | return x 327 | } 328 | return y 329 | } 330 | 331 | func max(x, y int64) int64 { 332 | if x > y { 333 | return x 334 | } 335 | return y 336 | } 337 | 338 | // MsgsPerClient divides the number of messages by the number of clients and tries to distribute them as evenly as possible 339 | func MsgsPerClient(numMsgs, numClients int) []int { 340 | var counts []int 341 | if numClients == 0 || numMsgs == 0 { 342 | return counts 343 | } 344 | counts = make([]int, numClients) 345 | mc := numMsgs / numClients 346 | for i := 0; i < numClients; i++ { 347 | counts[i] = mc 348 | } 349 | extra := numMsgs % numClients 350 | for i := 0; i < extra; i++ { 351 | counts[i]++ 352 | } 353 | return counts 354 | } 355 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NATS - Go Client 2 | A [Go](http://golang.org) client for the [NATS messaging system](https://nats.io). 3 | 4 | [![License MIT](https://img.shields.io/npm/l/express.svg)](http://opensource.org/licenses/MIT) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/nats-io/go-nats)](https://goreportcard.com/report/github.com/nats-io/go-nats) [![Build Status](https://travis-ci.org/nats-io/go-nats.svg?branch=master)](http://travis-ci.org/nats-io/go-nats) [![GoDoc](https://godoc.org/github.com/nats-io/go-nats?status.svg)](http://godoc.org/github.com/nats-io/go-nats) [![Coverage Status](https://coveralls.io/repos/nats-io/go-nats/badge.svg?branch=master)](https://coveralls.io/r/nats-io/go-nats?branch=master) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | # Go client 11 | go get github.com/nats-io/go-nats 12 | 13 | # Server 14 | go get github.com/nats-io/gnatsd 15 | ``` 16 | 17 | ## Basic Usage 18 | 19 | ```go 20 | 21 | nc, _ := nats.Connect(nats.DefaultURL) 22 | 23 | // Simple Publisher 24 | nc.Publish("foo", []byte("Hello World")) 25 | 26 | // Simple Async Subscriber 27 | nc.Subscribe("foo", func(m *nats.Msg) { 28 | fmt.Printf("Received a message: %s\n", string(m.Data)) 29 | }) 30 | 31 | // Simple Sync Subscriber 32 | sub, err := nc.SubscribeSync("foo") 33 | m, err := sub.NextMsg(timeout) 34 | 35 | // Channel Subscriber 36 | ch := make(chan *nats.Msg, 64) 37 | sub, err := nc.ChanSubscribe("foo", ch) 38 | msg <- ch 39 | 40 | // Unsubscribe 41 | sub.Unsubscribe() 42 | 43 | // Requests 44 | msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond) 45 | 46 | // Replies 47 | nc.Subscribe("help", func(m *Msg) { 48 | nc.Publish(m.Reply, []byte("I can help!")) 49 | }) 50 | 51 | // Close connection 52 | nc := nats.Connect("nats://localhost:4222") 53 | nc.Close(); 54 | ``` 55 | 56 | ## Encoded Connections 57 | 58 | ```go 59 | 60 | nc, _ := nats.Connect(nats.DefaultURL) 61 | c, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER) 62 | defer c.Close() 63 | 64 | // Simple Publisher 65 | c.Publish("foo", "Hello World") 66 | 67 | // Simple Async Subscriber 68 | c.Subscribe("foo", func(s string) { 69 | fmt.Printf("Received a message: %s\n", s) 70 | }) 71 | 72 | // EncodedConn can Publish any raw Go type using the registered Encoder 73 | type person struct { 74 | Name string 75 | Address string 76 | Age int 77 | } 78 | 79 | // Go type Subscriber 80 | c.Subscribe("hello", func(p *person) { 81 | fmt.Printf("Received a person: %+v\n", p) 82 | }) 83 | 84 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery Street, San Francisco, CA"} 85 | 86 | // Go type Publisher 87 | c.Publish("hello", me) 88 | 89 | // Unsubscribe 90 | sub, err := c.Subscribe("foo", nil) 91 | ... 92 | sub.Unsubscribe() 93 | 94 | // Requests 95 | var response string 96 | err := c.Request("help", "help me", &response, 10*time.Millisecond) 97 | if err != nil { 98 | fmt.Printf("Request failed: %v\n", err) 99 | } 100 | 101 | // Replying 102 | c.Subscribe("help", func(subj, reply string, msg string) { 103 | c.Publish(reply, "I can help!") 104 | }) 105 | 106 | // Close connection 107 | c.Close(); 108 | ``` 109 | 110 | ## TLS 111 | 112 | ```go 113 | // tls as a scheme will enable secure connections by default. This will also verify the server name. 114 | nc, err := nats.Connect("tls://nats.demo.io:4443") 115 | 116 | // If you are using a self-signed certificate, you need to have a tls.Config with RootCAs setup. 117 | // We provide a helper method to make this case easier. 118 | nc, err = nats.Connect("tls://localhost:4443", nats.RootCAs("./configs/certs/ca.pem")) 119 | 120 | // If the server requires client certificate, there is an helper function for that too: 121 | cert := nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem") 122 | nc, err = nats.Connect("tls://localhost:4443", cert) 123 | 124 | // You can also supply a complete tls.Config 125 | 126 | certFile := "./configs/certs/client-cert.pem" 127 | keyFile := "./configs/certs/client-key.pem" 128 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 129 | if err != nil { 130 | t.Fatalf("error parsing X509 certificate/key pair: %v", err) 131 | } 132 | 133 | config := &tls.Config{ 134 | ServerName: opts.Host, 135 | Certificates: []tls.Certificate{cert}, 136 | RootCAs: pool, 137 | MinVersion: tls.VersionTLS12, 138 | } 139 | 140 | nc, err = nats.Connect("nats://localhost:4443", nats.Secure(config)) 141 | if err != nil { 142 | t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err) 143 | } 144 | 145 | ``` 146 | 147 | ## Using Go Channels (netchan) 148 | 149 | ```go 150 | nc, _ := nats.Connect(nats.DefaultURL) 151 | ec, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER) 152 | defer ec.Close() 153 | 154 | type person struct { 155 | Name string 156 | Address string 157 | Age int 158 | } 159 | 160 | recvCh := make(chan *person) 161 | ec.BindRecvChan("hello", recvCh) 162 | 163 | sendCh := make(chan *person) 164 | ec.BindSendChan("hello", sendCh) 165 | 166 | me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery Street"} 167 | 168 | // Send via Go channels 169 | sendCh <- me 170 | 171 | // Receive via Go channels 172 | who := <- recvCh 173 | ``` 174 | 175 | ## Wildcard Subscriptions 176 | 177 | ```go 178 | 179 | // "*" matches any token, at any level of the subject. 180 | nc.Subscribe("foo.*.baz", func(m *Msg) { 181 | fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)); 182 | }) 183 | 184 | nc.Subscribe("foo.bar.*", func(m *Msg) { 185 | fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)); 186 | }) 187 | 188 | // ">" matches any length of the tail of a subject, and can only be the last token 189 | // E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22' 190 | nc.Subscribe("foo.>", func(m *Msg) { 191 | fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)); 192 | }) 193 | 194 | // Matches all of the above 195 | nc.Publish("foo.bar.baz", []byte("Hello World")) 196 | 197 | ``` 198 | 199 | ## Queue Groups 200 | 201 | ```go 202 | // All subscriptions with the same queue name will form a queue group. 203 | // Each message will be delivered to only one subscriber per queue group, 204 | // using queuing semantics. You can have as many queue groups as you wish. 205 | // Normal subscribers will continue to work as expected. 206 | 207 | nc.QueueSubscribe("foo", "job_workers", func(_ *Msg) { 208 | received += 1; 209 | }) 210 | 211 | ``` 212 | 213 | ## Advanced Usage 214 | 215 | ```go 216 | 217 | // Flush connection to server, returns when all messages have been processed. 218 | nc.Flush() 219 | fmt.Println("All clear!") 220 | 221 | // FlushTimeout specifies a timeout value as well. 222 | err := nc.FlushTimeout(1*time.Second) 223 | if err != nil { 224 | fmt.Println("All clear!") 225 | } else { 226 | fmt.Println("Flushed timed out!") 227 | } 228 | 229 | // Auto-unsubscribe after MAX_WANTED messages received 230 | const MAX_WANTED = 10 231 | sub, err := nc.Subscribe("foo") 232 | sub.AutoUnsubscribe(MAX_WANTED) 233 | 234 | // Multiple connections 235 | nc1 := nats.Connect("nats://host1:4222") 236 | nc2 := nats.Connect("nats://host2:4222") 237 | 238 | nc1.Subscribe("foo", func(m *Msg) { 239 | fmt.Printf("Received a message: %s\n", string(m.Data)) 240 | }) 241 | 242 | nc2.Publish("foo", []byte("Hello World!")); 243 | 244 | ``` 245 | 246 | ## Clustered Usage 247 | 248 | ```go 249 | 250 | var servers = "nats://localhost:1222, nats://localhost:1223, nats://localhost:1224" 251 | 252 | nc, err := nats.Connect(servers) 253 | 254 | // Optionally set ReconnectWait and MaxReconnect attempts. 255 | // This example means 10 seconds total per backend. 256 | nc, err = nats.Connect(servers, nats.MaxReconnects(5), nats.ReconnectWait(2 * time.Second)) 257 | 258 | // Optionally disable randomization of the server pool 259 | nc, err = nats.Connect(servers, nats.DontRandomize()) 260 | 261 | // Setup callbacks to be notified on disconnects, reconnects and connection closed. 262 | nc, err = nats.Connect(servers, 263 | nats.DisconnectHandler(func(nc *nats.Conn) { 264 | fmt.Printf("Got disconnected!\n") 265 | }), 266 | nats.ReconnectHandler(func(_ *nats.Conn) { 267 | fmt.Printf("Got reconnected to %v!\n", nc.ConnectedUrl()) 268 | }), 269 | nats.ClosedHandler(func(nc *nats.Conn) { 270 | fmt.Printf("Connection closed. Reason: %q\n", nc.LastError()) 271 | }) 272 | ) 273 | 274 | // When connecting to a mesh of servers with auto-discovery capabilities, 275 | // you may need to provide a username/password or token in order to connect 276 | // to any server in that mesh when authentication is required. 277 | // Instead of providing the credentials in the initial URL, you will use 278 | // new option setters: 279 | nc, err = nats.Connect("nats://localhost:4222", nats.UserInfo("foo", "bar")) 280 | 281 | // For token based authentication: 282 | nc, err = nats.Connect("nats://localhost:4222", nats.Token("S3cretT0ken")) 283 | 284 | // You can even pass the two at the same time in case one of the server 285 | // in the mesh requires token instead of user name and password. 286 | nc, err = nats.Connect("nats://localhost:4222", 287 | nats.UserInfo("foo", "bar"), 288 | nats.Token("S3cretT0ken")) 289 | 290 | // Note that if credentials are specified in the initial URLs, they take 291 | // precedence on the credentials specfied through the options. 292 | // For instance, in the connect call below, the client library will use 293 | // the user "my" and password "pwd" to connect to locahost:4222, however, 294 | // it will use username "foo" and password "bar" when (re)connecting to 295 | // a different server URL that it got as part of the auto-discovery. 296 | nc, err = nats.Connect("nats://my:pwd@localhost:4222", nats.UserInfo("foo", "bar")) 297 | 298 | ``` 299 | 300 | ## License 301 | 302 | (The MIT License) 303 | 304 | Copyright (c) 2012-2016 Apcera Inc. 305 | 306 | Permission is hereby granted, free of charge, to any person obtaining a copy 307 | of this software and associated documentation files (the "Software"), to 308 | deal in the Software without restriction, including without limitation the 309 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 310 | sell copies of the Software, and to permit persons to whom the Software is 311 | furnished to do so, subject to the following conditions: 312 | 313 | The above copyright notice and this permission notice shall be included in 314 | all copies or substantial portions of the Software. 315 | 316 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 317 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 318 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 319 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 320 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 321 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 322 | IN THE SOFTWARE. 323 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2014 Apcera Inc. All rights reserved. 2 | 3 | package nats 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | type msgArg struct { 10 | subject []byte 11 | reply []byte 12 | sid int64 13 | size int 14 | } 15 | 16 | const MAX_CONTROL_LINE_SIZE = 1024 17 | 18 | type parseState struct { 19 | state int 20 | as int 21 | drop int 22 | ma msgArg 23 | argBuf []byte 24 | msgBuf []byte 25 | scratch [MAX_CONTROL_LINE_SIZE]byte 26 | } 27 | 28 | const ( 29 | OP_START = iota 30 | OP_PLUS 31 | OP_PLUS_O 32 | OP_PLUS_OK 33 | OP_MINUS 34 | OP_MINUS_E 35 | OP_MINUS_ER 36 | OP_MINUS_ERR 37 | OP_MINUS_ERR_SPC 38 | MINUS_ERR_ARG 39 | OP_M 40 | OP_MS 41 | OP_MSG 42 | OP_MSG_SPC 43 | MSG_ARG 44 | MSG_PAYLOAD 45 | MSG_END 46 | OP_P 47 | OP_PI 48 | OP_PIN 49 | OP_PING 50 | OP_PO 51 | OP_PON 52 | OP_PONG 53 | OP_I 54 | OP_IN 55 | OP_INF 56 | OP_INFO 57 | OP_INFO_SPC 58 | INFO_ARG 59 | ) 60 | 61 | // parse is the fast protocol parser engine. 62 | func (nc *Conn) parse(buf []byte) error { 63 | var i int 64 | var b byte 65 | 66 | // Move to loop instead of range syntax to allow jumping of i 67 | for i = 0; i < len(buf); i++ { 68 | b = buf[i] 69 | 70 | switch nc.ps.state { 71 | case OP_START: 72 | switch b { 73 | case 'M', 'm': 74 | nc.ps.state = OP_M 75 | case 'P', 'p': 76 | nc.ps.state = OP_P 77 | case '+': 78 | nc.ps.state = OP_PLUS 79 | case '-': 80 | nc.ps.state = OP_MINUS 81 | case 'I', 'i': 82 | nc.ps.state = OP_I 83 | default: 84 | goto parseErr 85 | } 86 | case OP_M: 87 | switch b { 88 | case 'S', 's': 89 | nc.ps.state = OP_MS 90 | default: 91 | goto parseErr 92 | } 93 | case OP_MS: 94 | switch b { 95 | case 'G', 'g': 96 | nc.ps.state = OP_MSG 97 | default: 98 | goto parseErr 99 | } 100 | case OP_MSG: 101 | switch b { 102 | case ' ', '\t': 103 | nc.ps.state = OP_MSG_SPC 104 | default: 105 | goto parseErr 106 | } 107 | case OP_MSG_SPC: 108 | switch b { 109 | case ' ', '\t': 110 | continue 111 | default: 112 | nc.ps.state = MSG_ARG 113 | nc.ps.as = i 114 | } 115 | case MSG_ARG: 116 | switch b { 117 | case '\r': 118 | nc.ps.drop = 1 119 | case '\n': 120 | var arg []byte 121 | if nc.ps.argBuf != nil { 122 | arg = nc.ps.argBuf 123 | } else { 124 | arg = buf[nc.ps.as : i-nc.ps.drop] 125 | } 126 | if err := nc.processMsgArgs(arg); err != nil { 127 | return err 128 | } 129 | nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, MSG_PAYLOAD 130 | 131 | // jump ahead with the index. If this overruns 132 | // what is left we fall out and process split 133 | // buffer. 134 | i = nc.ps.as + nc.ps.ma.size - 1 135 | default: 136 | if nc.ps.argBuf != nil { 137 | nc.ps.argBuf = append(nc.ps.argBuf, b) 138 | } 139 | } 140 | case MSG_PAYLOAD: 141 | if nc.ps.msgBuf != nil { 142 | if len(nc.ps.msgBuf) >= nc.ps.ma.size { 143 | nc.processMsg(nc.ps.msgBuf) 144 | nc.ps.argBuf, nc.ps.msgBuf, nc.ps.state = nil, nil, MSG_END 145 | } else { 146 | // copy as much as we can to the buffer and skip ahead. 147 | toCopy := nc.ps.ma.size - len(nc.ps.msgBuf) 148 | avail := len(buf) - i 149 | 150 | if avail < toCopy { 151 | toCopy = avail 152 | } 153 | 154 | if toCopy > 0 { 155 | start := len(nc.ps.msgBuf) 156 | // This is needed for copy to work. 157 | nc.ps.msgBuf = nc.ps.msgBuf[:start+toCopy] 158 | copy(nc.ps.msgBuf[start:], buf[i:i+toCopy]) 159 | // Update our index 160 | i = (i + toCopy) - 1 161 | } else { 162 | nc.ps.msgBuf = append(nc.ps.msgBuf, b) 163 | } 164 | } 165 | } else if i-nc.ps.as >= nc.ps.ma.size { 166 | nc.processMsg(buf[nc.ps.as:i]) 167 | nc.ps.argBuf, nc.ps.msgBuf, nc.ps.state = nil, nil, MSG_END 168 | } 169 | case MSG_END: 170 | switch b { 171 | case '\n': 172 | nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START 173 | default: 174 | continue 175 | } 176 | case OP_PLUS: 177 | switch b { 178 | case 'O', 'o': 179 | nc.ps.state = OP_PLUS_O 180 | default: 181 | goto parseErr 182 | } 183 | case OP_PLUS_O: 184 | switch b { 185 | case 'K', 'k': 186 | nc.ps.state = OP_PLUS_OK 187 | default: 188 | goto parseErr 189 | } 190 | case OP_PLUS_OK: 191 | switch b { 192 | case '\n': 193 | nc.processOK() 194 | nc.ps.drop, nc.ps.state = 0, OP_START 195 | } 196 | case OP_MINUS: 197 | switch b { 198 | case 'E', 'e': 199 | nc.ps.state = OP_MINUS_E 200 | default: 201 | goto parseErr 202 | } 203 | case OP_MINUS_E: 204 | switch b { 205 | case 'R', 'r': 206 | nc.ps.state = OP_MINUS_ER 207 | default: 208 | goto parseErr 209 | } 210 | case OP_MINUS_ER: 211 | switch b { 212 | case 'R', 'r': 213 | nc.ps.state = OP_MINUS_ERR 214 | default: 215 | goto parseErr 216 | } 217 | case OP_MINUS_ERR: 218 | switch b { 219 | case ' ', '\t': 220 | nc.ps.state = OP_MINUS_ERR_SPC 221 | default: 222 | goto parseErr 223 | } 224 | case OP_MINUS_ERR_SPC: 225 | switch b { 226 | case ' ', '\t': 227 | continue 228 | default: 229 | nc.ps.state = MINUS_ERR_ARG 230 | nc.ps.as = i 231 | } 232 | case MINUS_ERR_ARG: 233 | switch b { 234 | case '\r': 235 | nc.ps.drop = 1 236 | case '\n': 237 | var arg []byte 238 | if nc.ps.argBuf != nil { 239 | arg = nc.ps.argBuf 240 | nc.ps.argBuf = nil 241 | } else { 242 | arg = buf[nc.ps.as : i-nc.ps.drop] 243 | } 244 | nc.processErr(string(arg)) 245 | nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START 246 | default: 247 | if nc.ps.argBuf != nil { 248 | nc.ps.argBuf = append(nc.ps.argBuf, b) 249 | } 250 | } 251 | case OP_P: 252 | switch b { 253 | case 'I', 'i': 254 | nc.ps.state = OP_PI 255 | case 'O', 'o': 256 | nc.ps.state = OP_PO 257 | default: 258 | goto parseErr 259 | } 260 | case OP_PO: 261 | switch b { 262 | case 'N', 'n': 263 | nc.ps.state = OP_PON 264 | default: 265 | goto parseErr 266 | } 267 | case OP_PON: 268 | switch b { 269 | case 'G', 'g': 270 | nc.ps.state = OP_PONG 271 | default: 272 | goto parseErr 273 | } 274 | case OP_PONG: 275 | switch b { 276 | case '\n': 277 | nc.processPong() 278 | nc.ps.drop, nc.ps.state = 0, OP_START 279 | } 280 | case OP_PI: 281 | switch b { 282 | case 'N', 'n': 283 | nc.ps.state = OP_PIN 284 | default: 285 | goto parseErr 286 | } 287 | case OP_PIN: 288 | switch b { 289 | case 'G', 'g': 290 | nc.ps.state = OP_PING 291 | default: 292 | goto parseErr 293 | } 294 | case OP_PING: 295 | switch b { 296 | case '\n': 297 | nc.processPing() 298 | nc.ps.drop, nc.ps.state = 0, OP_START 299 | } 300 | case OP_I: 301 | switch b { 302 | case 'N', 'n': 303 | nc.ps.state = OP_IN 304 | default: 305 | goto parseErr 306 | } 307 | case OP_IN: 308 | switch b { 309 | case 'F', 'f': 310 | nc.ps.state = OP_INF 311 | default: 312 | goto parseErr 313 | } 314 | case OP_INF: 315 | switch b { 316 | case 'O', 'o': 317 | nc.ps.state = OP_INFO 318 | default: 319 | goto parseErr 320 | } 321 | case OP_INFO: 322 | switch b { 323 | case ' ', '\t': 324 | nc.ps.state = OP_INFO_SPC 325 | default: 326 | goto parseErr 327 | } 328 | case OP_INFO_SPC: 329 | switch b { 330 | case ' ', '\t': 331 | continue 332 | default: 333 | nc.ps.state = INFO_ARG 334 | nc.ps.as = i 335 | } 336 | case INFO_ARG: 337 | switch b { 338 | case '\r': 339 | nc.ps.drop = 1 340 | case '\n': 341 | var arg []byte 342 | if nc.ps.argBuf != nil { 343 | arg = nc.ps.argBuf 344 | nc.ps.argBuf = nil 345 | } else { 346 | arg = buf[nc.ps.as : i-nc.ps.drop] 347 | } 348 | nc.processAsyncInfo(arg) 349 | nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START 350 | default: 351 | if nc.ps.argBuf != nil { 352 | nc.ps.argBuf = append(nc.ps.argBuf, b) 353 | } 354 | } 355 | default: 356 | goto parseErr 357 | } 358 | } 359 | // Check for split buffer scenarios 360 | if (nc.ps.state == MSG_ARG || nc.ps.state == MINUS_ERR_ARG || nc.ps.state == INFO_ARG) && nc.ps.argBuf == nil { 361 | nc.ps.argBuf = nc.ps.scratch[:0] 362 | nc.ps.argBuf = append(nc.ps.argBuf, buf[nc.ps.as:i-nc.ps.drop]...) 363 | // FIXME, check max len 364 | } 365 | // Check for split msg 366 | if nc.ps.state == MSG_PAYLOAD && nc.ps.msgBuf == nil { 367 | // We need to clone the msgArg if it is still referencing the 368 | // read buffer and we are not able to process the msg. 369 | if nc.ps.argBuf == nil { 370 | nc.cloneMsgArg() 371 | } 372 | 373 | // If we will overflow the scratch buffer, just create a 374 | // new buffer to hold the split message. 375 | if nc.ps.ma.size > cap(nc.ps.scratch)-len(nc.ps.argBuf) { 376 | lrem := len(buf[nc.ps.as:]) 377 | 378 | nc.ps.msgBuf = make([]byte, lrem, nc.ps.ma.size) 379 | copy(nc.ps.msgBuf, buf[nc.ps.as:]) 380 | } else { 381 | nc.ps.msgBuf = nc.ps.scratch[len(nc.ps.argBuf):len(nc.ps.argBuf)] 382 | nc.ps.msgBuf = append(nc.ps.msgBuf, (buf[nc.ps.as:])...) 383 | } 384 | } 385 | 386 | return nil 387 | 388 | parseErr: 389 | return fmt.Errorf("nats: Parse Error [%d]: '%s'", nc.ps.state, buf[i:]) 390 | } 391 | 392 | // cloneMsgArg is used when the split buffer scenario has the pubArg in the existing read buffer, but 393 | // we need to hold onto it into the next read. 394 | func (nc *Conn) cloneMsgArg() { 395 | nc.ps.argBuf = nc.ps.scratch[:0] 396 | nc.ps.argBuf = append(nc.ps.argBuf, nc.ps.ma.subject...) 397 | nc.ps.argBuf = append(nc.ps.argBuf, nc.ps.ma.reply...) 398 | nc.ps.ma.subject = nc.ps.argBuf[:len(nc.ps.ma.subject)] 399 | if nc.ps.ma.reply != nil { 400 | nc.ps.ma.reply = nc.ps.argBuf[len(nc.ps.ma.subject):] 401 | } 402 | } 403 | 404 | const argsLenMax = 4 405 | 406 | func (nc *Conn) processMsgArgs(arg []byte) error { 407 | // Unroll splitArgs to avoid runtime/heap issues 408 | a := [argsLenMax][]byte{} 409 | args := a[:0] 410 | start := -1 411 | for i, b := range arg { 412 | switch b { 413 | case ' ', '\t', '\r', '\n': 414 | if start >= 0 { 415 | args = append(args, arg[start:i]) 416 | start = -1 417 | } 418 | default: 419 | if start < 0 { 420 | start = i 421 | } 422 | } 423 | } 424 | if start >= 0 { 425 | args = append(args, arg[start:]) 426 | } 427 | 428 | switch len(args) { 429 | case 3: 430 | nc.ps.ma.subject = args[0] 431 | nc.ps.ma.sid = parseInt64(args[1]) 432 | nc.ps.ma.reply = nil 433 | nc.ps.ma.size = int(parseInt64(args[2])) 434 | case 4: 435 | nc.ps.ma.subject = args[0] 436 | nc.ps.ma.sid = parseInt64(args[1]) 437 | nc.ps.ma.reply = args[2] 438 | nc.ps.ma.size = int(parseInt64(args[3])) 439 | default: 440 | return fmt.Errorf("nats: processMsgArgs Parse Error: '%s'", arg) 441 | } 442 | if nc.ps.ma.sid < 0 { 443 | return fmt.Errorf("nats: processMsgArgs Bad or Missing Sid: '%s'", arg) 444 | } 445 | if nc.ps.ma.size < 0 { 446 | return fmt.Errorf("nats: processMsgArgs Bad or Missing Size: '%s'", arg) 447 | } 448 | return nil 449 | } 450 | 451 | // Ascii numbers 0-9 452 | const ( 453 | ascii_0 = 48 454 | ascii_9 = 57 455 | ) 456 | 457 | // parseInt64 expects decimal positive numbers. We 458 | // return -1 to signal error 459 | func parseInt64(d []byte) (n int64) { 460 | if len(d) == 0 { 461 | return -1 462 | } 463 | for _, dec := range d { 464 | if dec < ascii_0 || dec > ascii_9 { 465 | return -1 466 | } 467 | n = n*10 + (int64(dec) - ascii_0) 468 | } 469 | return n 470 | } 471 | -------------------------------------------------------------------------------- /encoders/builtin/enc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Apcera Inc. All rights reserved. 2 | 3 | package builtin_test 4 | 5 | import ( 6 | "bytes" 7 | "testing" 8 | "time" 9 | 10 | "github.com/nats-io/go-nats" 11 | "github.com/nats-io/go-nats/encoders/builtin" 12 | "github.com/nats-io/go-nats/test" 13 | ) 14 | 15 | const TEST_PORT = 8168 16 | 17 | func NewEConn(t *testing.T) *nats.EncodedConn { 18 | ec, err := nats.NewEncodedConn(test.NewConnection(t, TEST_PORT), nats.DEFAULT_ENCODER) 19 | if err != nil { 20 | t.Fatalf("Failed to create an encoded connection: %v\n", err) 21 | } 22 | return ec 23 | } 24 | 25 | func TestConstructorErrs(t *testing.T) { 26 | s := test.RunServerOnPort(TEST_PORT) 27 | defer s.Shutdown() 28 | 29 | c := test.NewConnection(t, TEST_PORT) 30 | _, err := nats.NewEncodedConn(nil, "default") 31 | if err == nil { 32 | t.Fatal("Expected err for nil connection") 33 | } 34 | _, err = nats.NewEncodedConn(c, "foo22") 35 | if err == nil { 36 | t.Fatal("Expected err for bad encoder") 37 | } 38 | c.Close() 39 | _, err = nats.NewEncodedConn(c, "default") 40 | if err == nil { 41 | t.Fatal("Expected err for closed connection") 42 | } 43 | 44 | } 45 | 46 | func TestMarshalString(t *testing.T) { 47 | s := test.RunServerOnPort(TEST_PORT) 48 | defer s.Shutdown() 49 | 50 | ec := NewEConn(t) 51 | defer ec.Close() 52 | ch := make(chan bool) 53 | 54 | testString := "Hello World!" 55 | 56 | ec.Subscribe("enc_string", func(s string) { 57 | if s != testString { 58 | t.Fatalf("Received test string of '%s', wanted '%s'\n", s, testString) 59 | } 60 | ch <- true 61 | }) 62 | ec.Publish("enc_string", testString) 63 | if e := test.Wait(ch); e != nil { 64 | if ec.LastError() != nil { 65 | e = ec.LastError() 66 | } 67 | t.Fatalf("Did not receive the message: %s", e) 68 | } 69 | } 70 | 71 | func TestMarshalBytes(t *testing.T) { 72 | s := test.RunServerOnPort(TEST_PORT) 73 | defer s.Shutdown() 74 | 75 | ec := NewEConn(t) 76 | defer ec.Close() 77 | ch := make(chan bool) 78 | 79 | testBytes := []byte("Hello World!") 80 | 81 | ec.Subscribe("enc_bytes", func(b []byte) { 82 | if !bytes.Equal(b, testBytes) { 83 | t.Fatalf("Received test bytes of '%s', wanted '%s'\n", b, testBytes) 84 | } 85 | ch <- true 86 | }) 87 | ec.Publish("enc_bytes", testBytes) 88 | if e := test.Wait(ch); e != nil { 89 | if ec.LastError() != nil { 90 | e = ec.LastError() 91 | } 92 | t.Fatalf("Did not receive the message: %s", e) 93 | } 94 | } 95 | 96 | func TestMarshalInt(t *testing.T) { 97 | s := test.RunServerOnPort(TEST_PORT) 98 | defer s.Shutdown() 99 | 100 | ec := NewEConn(t) 101 | defer ec.Close() 102 | ch := make(chan bool) 103 | 104 | testN := 22 105 | 106 | ec.Subscribe("enc_int", func(n int) { 107 | if n != testN { 108 | t.Fatalf("Received test number of %d, wanted %d\n", n, testN) 109 | } 110 | ch <- true 111 | }) 112 | ec.Publish("enc_int", testN) 113 | if e := test.Wait(ch); e != nil { 114 | if ec.LastError() != nil { 115 | e = ec.LastError() 116 | } 117 | t.Fatalf("Did not receive the message: %s", e) 118 | } 119 | } 120 | 121 | func TestMarshalInt32(t *testing.T) { 122 | s := test.RunServerOnPort(TEST_PORT) 123 | defer s.Shutdown() 124 | 125 | ec := NewEConn(t) 126 | defer ec.Close() 127 | ch := make(chan bool) 128 | 129 | testN := 22 130 | 131 | ec.Subscribe("enc_int", func(n int32) { 132 | if n != int32(testN) { 133 | t.Fatalf("Received test number of %d, wanted %d\n", n, testN) 134 | } 135 | ch <- true 136 | }) 137 | ec.Publish("enc_int", testN) 138 | if e := test.Wait(ch); e != nil { 139 | if ec.LastError() != nil { 140 | e = ec.LastError() 141 | } 142 | t.Fatalf("Did not receive the message: %s", e) 143 | } 144 | } 145 | 146 | func TestMarshalInt64(t *testing.T) { 147 | s := test.RunServerOnPort(TEST_PORT) 148 | defer s.Shutdown() 149 | 150 | ec := NewEConn(t) 151 | defer ec.Close() 152 | ch := make(chan bool) 153 | 154 | testN := 22 155 | 156 | ec.Subscribe("enc_int", func(n int64) { 157 | if n != int64(testN) { 158 | t.Fatalf("Received test number of %d, wanted %d\n", n, testN) 159 | } 160 | ch <- true 161 | }) 162 | ec.Publish("enc_int", testN) 163 | if e := test.Wait(ch); e != nil { 164 | if ec.LastError() != nil { 165 | e = ec.LastError() 166 | } 167 | t.Fatalf("Did not receive the message: %s", e) 168 | } 169 | } 170 | 171 | func TestMarshalFloat32(t *testing.T) { 172 | s := test.RunServerOnPort(TEST_PORT) 173 | defer s.Shutdown() 174 | 175 | ec := NewEConn(t) 176 | defer ec.Close() 177 | ch := make(chan bool) 178 | 179 | testN := float32(22) 180 | 181 | ec.Subscribe("enc_float", func(n float32) { 182 | if n != testN { 183 | t.Fatalf("Received test number of %f, wanted %f\n", n, testN) 184 | } 185 | ch <- true 186 | }) 187 | ec.Publish("enc_float", testN) 188 | if e := test.Wait(ch); e != nil { 189 | if ec.LastError() != nil { 190 | e = ec.LastError() 191 | } 192 | t.Fatalf("Did not receive the message: %s", e) 193 | } 194 | } 195 | 196 | func TestMarshalFloat64(t *testing.T) { 197 | s := test.RunServerOnPort(TEST_PORT) 198 | defer s.Shutdown() 199 | 200 | ec := NewEConn(t) 201 | defer ec.Close() 202 | ch := make(chan bool) 203 | 204 | testN := float64(22.22) 205 | 206 | ec.Subscribe("enc_float", func(n float64) { 207 | if n != testN { 208 | t.Fatalf("Received test number of %f, wanted %f\n", n, testN) 209 | } 210 | ch <- true 211 | }) 212 | ec.Publish("enc_float", testN) 213 | if e := test.Wait(ch); e != nil { 214 | if ec.LastError() != nil { 215 | e = ec.LastError() 216 | } 217 | t.Fatalf("Did not receive the message: %s", e) 218 | } 219 | } 220 | 221 | func TestMarshalBool(t *testing.T) { 222 | s := test.RunServerOnPort(TEST_PORT) 223 | defer s.Shutdown() 224 | 225 | ec := NewEConn(t) 226 | defer ec.Close() 227 | ch := make(chan bool) 228 | expected := make(chan bool, 1) 229 | 230 | ec.Subscribe("enc_bool", func(b bool) { 231 | val := <-expected 232 | if b != val { 233 | t.Fatal("Boolean values did not match") 234 | } 235 | ch <- true 236 | }) 237 | 238 | expected <- false 239 | ec.Publish("enc_bool", false) 240 | if e := test.Wait(ch); e != nil { 241 | if ec.LastError() != nil { 242 | e = ec.LastError() 243 | } 244 | t.Fatalf("Did not receive the message: %s", e) 245 | } 246 | 247 | expected <- true 248 | ec.Publish("enc_bool", true) 249 | if e := test.Wait(ch); e != nil { 250 | if ec.LastError() != nil { 251 | e = ec.LastError() 252 | } 253 | t.Fatalf("Did not receive the message: %s", e) 254 | } 255 | } 256 | 257 | func TestExtendedSubscribeCB(t *testing.T) { 258 | s := test.RunServerOnPort(TEST_PORT) 259 | defer s.Shutdown() 260 | 261 | ec := NewEConn(t) 262 | defer ec.Close() 263 | 264 | ch := make(chan bool) 265 | 266 | testString := "Hello World!" 267 | subject := "cb_args" 268 | 269 | ec.Subscribe(subject, func(subj, s string) { 270 | if s != testString { 271 | t.Fatalf("Received test string of '%s', wanted '%s'\n", s, testString) 272 | } 273 | if subj != subject { 274 | t.Fatalf("Received subject of '%s', wanted '%s'\n", subj, subject) 275 | } 276 | ch <- true 277 | }) 278 | ec.Publish(subject, testString) 279 | if e := test.Wait(ch); e != nil { 280 | if ec.LastError() != nil { 281 | e = ec.LastError() 282 | } 283 | t.Fatalf("Did not receive the message: %s", e) 284 | } 285 | } 286 | 287 | func TestExtendedSubscribeCB2(t *testing.T) { 288 | s := test.RunServerOnPort(TEST_PORT) 289 | defer s.Shutdown() 290 | 291 | ec := NewEConn(t) 292 | defer ec.Close() 293 | 294 | ch := make(chan bool) 295 | 296 | testString := "Hello World!" 297 | oSubj := "cb_args" 298 | oReply := "foobar" 299 | 300 | ec.Subscribe(oSubj, func(subj, reply, s string) { 301 | if s != testString { 302 | t.Fatalf("Received test string of '%s', wanted '%s'\n", s, testString) 303 | } 304 | if subj != oSubj { 305 | t.Fatalf("Received subject of '%s', wanted '%s'\n", subj, oSubj) 306 | } 307 | if reply != oReply { 308 | t.Fatalf("Received reply of '%s', wanted '%s'\n", reply, oReply) 309 | } 310 | ch <- true 311 | }) 312 | ec.PublishRequest(oSubj, oReply, testString) 313 | if e := test.Wait(ch); e != nil { 314 | if ec.LastError() != nil { 315 | e = ec.LastError() 316 | } 317 | t.Fatalf("Did not receive the message: %s", e) 318 | } 319 | } 320 | 321 | func TestRawMsgSubscribeCB(t *testing.T) { 322 | s := test.RunServerOnPort(TEST_PORT) 323 | defer s.Shutdown() 324 | 325 | ec := NewEConn(t) 326 | defer ec.Close() 327 | 328 | ch := make(chan bool) 329 | 330 | testString := "Hello World!" 331 | oSubj := "cb_args" 332 | oReply := "foobar" 333 | 334 | ec.Subscribe(oSubj, func(m *nats.Msg) { 335 | s := string(m.Data) 336 | if s != testString { 337 | t.Fatalf("Received test string of '%s', wanted '%s'\n", s, testString) 338 | } 339 | if m.Subject != oSubj { 340 | t.Fatalf("Received subject of '%s', wanted '%s'\n", m.Subject, oSubj) 341 | } 342 | if m.Reply != oReply { 343 | t.Fatalf("Received reply of '%s', wanted '%s'\n", m.Reply, oReply) 344 | } 345 | ch <- true 346 | }) 347 | ec.PublishRequest(oSubj, oReply, testString) 348 | if e := test.Wait(ch); e != nil { 349 | if ec.LastError() != nil { 350 | e = ec.LastError() 351 | } 352 | t.Fatalf("Did not receive the message: %s", e) 353 | } 354 | } 355 | 356 | func TestEncRequest(t *testing.T) { 357 | s := test.RunServerOnPort(TEST_PORT) 358 | defer s.Shutdown() 359 | 360 | ec := NewEConn(t) 361 | defer ec.Close() 362 | 363 | expectedResp := "I can help!" 364 | 365 | ec.Subscribe("help", func(subj, reply, req string) { 366 | ec.Publish(reply, expectedResp) 367 | }) 368 | 369 | var resp string 370 | 371 | err := ec.Request("help", "help me", &resp, 1*time.Second) 372 | if err != nil { 373 | t.Fatalf("Failed at receiving proper response: %v\n", err) 374 | } 375 | if resp != expectedResp { 376 | t.Fatalf("Received reply '%s', wanted '%s'\n", resp, expectedResp) 377 | } 378 | } 379 | 380 | func TestEncRequestReceivesMsg(t *testing.T) { 381 | s := test.RunServerOnPort(TEST_PORT) 382 | defer s.Shutdown() 383 | 384 | ec := NewEConn(t) 385 | defer ec.Close() 386 | 387 | expectedResp := "I can help!" 388 | 389 | ec.Subscribe("help", func(subj, reply, req string) { 390 | ec.Publish(reply, expectedResp) 391 | }) 392 | 393 | var resp nats.Msg 394 | 395 | err := ec.Request("help", "help me", &resp, 1*time.Second) 396 | if err != nil { 397 | t.Fatalf("Failed at receiving proper response: %v\n", err) 398 | } 399 | if string(resp.Data) != expectedResp { 400 | t.Fatalf("Received reply '%s', wanted '%s'\n", string(resp.Data), expectedResp) 401 | } 402 | } 403 | 404 | func TestAsyncMarshalErr(t *testing.T) { 405 | s := test.RunServerOnPort(TEST_PORT) 406 | defer s.Shutdown() 407 | 408 | ec := NewEConn(t) 409 | defer ec.Close() 410 | 411 | ch := make(chan bool) 412 | 413 | testString := "Hello World!" 414 | subject := "err_marshall" 415 | 416 | ec.Subscribe(subject, func(subj, num int) { 417 | // This will never get called. 418 | }) 419 | 420 | ec.Conn.Opts.AsyncErrorCB = func(c *nats.Conn, s *nats.Subscription, err error) { 421 | ch <- true 422 | } 423 | 424 | ec.Publish(subject, testString) 425 | if e := test.Wait(ch); e != nil { 426 | t.Fatalf("Did not receive the message: %s", e) 427 | } 428 | } 429 | 430 | func TestEncodeNil(t *testing.T) { 431 | de := &builtin.DefaultEncoder{} 432 | _, err := de.Encode("foo", nil) 433 | if err != nil { 434 | t.Fatalf("Expected no error encoding nil: %v", err) 435 | } 436 | } 437 | 438 | func TestDecodeDefault(t *testing.T) { 439 | de := &builtin.DefaultEncoder{} 440 | b, err := de.Encode("foo", 22) 441 | if err != nil { 442 | t.Fatalf("Expected no error encoding number: %v", err) 443 | } 444 | var c chan bool 445 | err = de.Decode("foo", b, &c) 446 | if err == nil { 447 | t.Fatalf("Expected an error decoding") 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /test/cluster_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "math" 5 | "regexp" 6 | "runtime" 7 | "strings" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/nats-io/gnatsd/auth" 13 | "github.com/nats-io/go-nats" 14 | ) 15 | 16 | var testServers = []string{ 17 | "nats://localhost:1222", 18 | "nats://localhost:1223", 19 | "nats://localhost:1224", 20 | "nats://localhost:1225", 21 | "nats://localhost:1226", 22 | "nats://localhost:1227", 23 | "nats://localhost:1228", 24 | } 25 | 26 | var servers = strings.Join(testServers, ",") 27 | 28 | func TestServersOption(t *testing.T) { 29 | opts := nats.DefaultOptions 30 | opts.NoRandomize = true 31 | 32 | _, err := opts.Connect() 33 | if err != nats.ErrNoServers { 34 | t.Fatalf("Wrong error: '%v'\n", err) 35 | } 36 | opts.Servers = testServers 37 | _, err = opts.Connect() 38 | if err == nil || err != nats.ErrNoServers { 39 | t.Fatalf("Did not receive proper error: %v\n", err) 40 | } 41 | 42 | // Make sure we can connect to first server if running 43 | s1 := RunServerOnPort(1222) 44 | // Do this in case some failure occurs before explicit shutdown 45 | defer s1.Shutdown() 46 | 47 | nc, err := opts.Connect() 48 | if err != nil { 49 | t.Fatalf("Could not connect: %v\n", err) 50 | } 51 | if nc.ConnectedUrl() != "nats://localhost:1222" { 52 | nc.Close() 53 | t.Fatalf("Does not report correct connection: %s\n", 54 | nc.ConnectedUrl()) 55 | } 56 | nc.Close() 57 | s1.Shutdown() 58 | 59 | // Make sure we can connect to a non first server if running 60 | s2 := RunServerOnPort(1223) 61 | // Do this in case some failure occurs before explicit shutdown 62 | defer s2.Shutdown() 63 | 64 | nc, err = opts.Connect() 65 | if err != nil { 66 | t.Fatalf("Could not connect: %v\n", err) 67 | } 68 | defer nc.Close() 69 | if nc.ConnectedUrl() != "nats://localhost:1223" { 70 | t.Fatalf("Does not report correct connection: %s\n", 71 | nc.ConnectedUrl()) 72 | } 73 | } 74 | 75 | func TestNewStyleServersOption(t *testing.T) { 76 | _, err := nats.Connect(nats.DefaultURL, nats.DontRandomize()) 77 | if err != nats.ErrNoServers { 78 | t.Fatalf("Wrong error: '%v'\n", err) 79 | } 80 | servers := strings.Join(testServers, ",") 81 | 82 | _, err = nats.Connect(servers, nats.DontRandomize()) 83 | if err == nil || err != nats.ErrNoServers { 84 | t.Fatalf("Did not receive proper error: %v\n", err) 85 | } 86 | 87 | // Make sure we can connect to first server if running 88 | s1 := RunServerOnPort(1222) 89 | // Do this in case some failure occurs before explicit shutdown 90 | defer s1.Shutdown() 91 | 92 | nc, err := nats.Connect(servers, nats.DontRandomize()) 93 | if err != nil { 94 | t.Fatalf("Could not connect: %v\n", err) 95 | } 96 | if nc.ConnectedUrl() != "nats://localhost:1222" { 97 | nc.Close() 98 | t.Fatalf("Does not report correct connection: %s\n", 99 | nc.ConnectedUrl()) 100 | } 101 | nc.Close() 102 | s1.Shutdown() 103 | 104 | // Make sure we can connect to a non-first server if running 105 | s2 := RunServerOnPort(1223) 106 | // Do this in case some failure occurs before explicit shutdown 107 | defer s2.Shutdown() 108 | 109 | nc, err = nats.Connect(servers, nats.DontRandomize()) 110 | if err != nil { 111 | t.Fatalf("Could not connect: %v\n", err) 112 | } 113 | defer nc.Close() 114 | if nc.ConnectedUrl() != "nats://localhost:1223" { 115 | t.Fatalf("Does not report correct connection: %s\n", 116 | nc.ConnectedUrl()) 117 | } 118 | } 119 | 120 | func TestAuthServers(t *testing.T) { 121 | var plainServers = []string{ 122 | "nats://localhost:1222", 123 | "nats://localhost:1224", 124 | } 125 | 126 | auth := &auth.Plain{ 127 | Username: "derek", 128 | Password: "foo", 129 | } 130 | 131 | as1 := RunServerOnPort(1222) 132 | as1.SetClientAuthMethod(auth) 133 | defer as1.Shutdown() 134 | as2 := RunServerOnPort(1224) 135 | as2.SetClientAuthMethod(auth) 136 | defer as2.Shutdown() 137 | 138 | pservers := strings.Join(plainServers, ",") 139 | nc, err := nats.Connect(pservers, nats.DontRandomize(), nats.Timeout(5*time.Second)) 140 | if err == nil { 141 | nc.Close() 142 | t.Fatalf("Expect Auth failure, got no error\n") 143 | } 144 | 145 | if matched, _ := regexp.Match(`authorization`, []byte(err.Error())); !matched { 146 | t.Fatalf("Wrong error, wanted Auth failure, got '%s'\n", err) 147 | } 148 | 149 | // Test that we can connect to a subsequent correct server. 150 | var authServers = []string{ 151 | "nats://localhost:1222", 152 | "nats://derek:foo@localhost:1224", 153 | } 154 | aservers := strings.Join(authServers, ",") 155 | nc, err = nats.Connect(aservers, nats.DontRandomize(), nats.Timeout(5*time.Second)) 156 | if err != nil { 157 | t.Fatalf("Expected to connect properly: %v\n", err) 158 | } 159 | defer nc.Close() 160 | if nc.ConnectedUrl() != authServers[1] { 161 | t.Fatalf("Does not report correct connection: %s\n", 162 | nc.ConnectedUrl()) 163 | } 164 | } 165 | 166 | func TestBasicClusterReconnect(t *testing.T) { 167 | s1 := RunServerOnPort(1222) 168 | defer s1.Shutdown() 169 | s2 := RunServerOnPort(1224) 170 | defer s2.Shutdown() 171 | 172 | dch := make(chan bool) 173 | rch := make(chan bool) 174 | 175 | dcbCalled := false 176 | 177 | opts := []nats.Option{nats.DontRandomize(), 178 | nats.DisconnectHandler(func(nc *nats.Conn) { 179 | // Suppress any additional callbacks 180 | if dcbCalled { 181 | return 182 | } 183 | dcbCalled = true 184 | dch <- true 185 | }), 186 | nats.ReconnectHandler(func(_ *nats.Conn) { rch <- true }), 187 | } 188 | 189 | nc, err := nats.Connect(servers, opts...) 190 | if err != nil { 191 | t.Fatalf("Expected to connect, got err: %v\n", err) 192 | } 193 | defer nc.Close() 194 | 195 | s1.Shutdown() 196 | 197 | // wait for disconnect 198 | if e := WaitTime(dch, 2*time.Second); e != nil { 199 | t.Fatal("Did not receive a disconnect callback message") 200 | } 201 | 202 | reconnectTimeStart := time.Now() 203 | 204 | // wait for reconnect 205 | if e := WaitTime(rch, 2*time.Second); e != nil { 206 | t.Fatal("Did not receive a reconnect callback message") 207 | } 208 | 209 | if nc.ConnectedUrl() != testServers[2] { 210 | t.Fatalf("Does not report correct connection: %s\n", 211 | nc.ConnectedUrl()) 212 | } 213 | 214 | // Make sure we did not wait on reconnect for default time. 215 | // Reconnect should be fast since it will be a switch to the 216 | // second server and not be dependent on server restart time. 217 | 218 | // On Windows, a failed connect takes more than a second, so 219 | // account for that. 220 | maxDuration := 100 * time.Millisecond 221 | if runtime.GOOS == "windows" { 222 | maxDuration = 1100 * time.Millisecond 223 | } 224 | reconnectTime := time.Since(reconnectTimeStart) 225 | if reconnectTime > maxDuration { 226 | t.Fatalf("Took longer than expected to reconnect: %v\n", reconnectTime) 227 | } 228 | } 229 | 230 | func TestHotSpotReconnect(t *testing.T) { 231 | s1 := RunServerOnPort(1222) 232 | defer s1.Shutdown() 233 | 234 | var srvrs string 235 | if runtime.GOOS == "windows" { 236 | srvrs = strings.Join(testServers[:5], ",") 237 | } else { 238 | srvrs = servers 239 | } 240 | 241 | numClients := 32 242 | clients := []*nats.Conn{} 243 | 244 | wg := &sync.WaitGroup{} 245 | wg.Add(numClients) 246 | 247 | opts := []nats.Option{ 248 | nats.ReconnectWait(50 * time.Millisecond), 249 | nats.ReconnectHandler(func(_ *nats.Conn) { wg.Done() }), 250 | } 251 | 252 | for i := 0; i < numClients; i++ { 253 | nc, err := nats.Connect(srvrs, opts...) 254 | if err != nil { 255 | t.Fatalf("Expected to connect, got err: %v\n", err) 256 | } 257 | defer nc.Close() 258 | if nc.ConnectedUrl() != testServers[0] { 259 | t.Fatalf("Connected to incorrect server: %v\n", nc.ConnectedUrl()) 260 | } 261 | clients = append(clients, nc) 262 | } 263 | 264 | s2 := RunServerOnPort(1224) 265 | defer s2.Shutdown() 266 | s3 := RunServerOnPort(1226) 267 | defer s3.Shutdown() 268 | 269 | s1.Shutdown() 270 | 271 | numServers := 2 272 | 273 | // Wait on all reconnects 274 | wg.Wait() 275 | 276 | // Walk the clients and calculate how many of each.. 277 | cs := make(map[string]int) 278 | for _, nc := range clients { 279 | cs[nc.ConnectedUrl()]++ 280 | nc.Close() 281 | } 282 | if len(cs) != numServers { 283 | t.Fatalf("Wrong number of reported servers: %d vs %d\n", len(cs), numServers) 284 | } 285 | expected := numClients / numServers 286 | v := uint(float32(expected) * 0.40) 287 | 288 | // Check that each item is within acceptable range 289 | for s, total := range cs { 290 | delta := uint(math.Abs(float64(expected - total))) 291 | if delta > v { 292 | t.Fatalf("Connected clients to server: %s out of range: %d\n", s, total) 293 | } 294 | } 295 | } 296 | 297 | func TestProperReconnectDelay(t *testing.T) { 298 | s1 := RunServerOnPort(1222) 299 | defer s1.Shutdown() 300 | 301 | var srvs string 302 | opts := nats.DefaultOptions 303 | if runtime.GOOS == "windows" { 304 | srvs = strings.Join(testServers[:2], ",") 305 | } else { 306 | srvs = strings.Join(testServers, ",") 307 | } 308 | opts.NoRandomize = true 309 | 310 | dcbCalled := false 311 | closedCbCalled := false 312 | dch := make(chan bool) 313 | 314 | dcb := func(nc *nats.Conn) { 315 | // Suppress any additional calls 316 | if dcbCalled { 317 | return 318 | } 319 | dcbCalled = true 320 | dch <- true 321 | } 322 | 323 | ccb := func(_ *nats.Conn) { 324 | closedCbCalled = true 325 | } 326 | 327 | nc, err := nats.Connect(srvs, nats.DontRandomize(), nats.DisconnectHandler(dcb), nats.ClosedHandler(ccb)) 328 | if err != nil { 329 | t.Fatalf("Expected to connect, got err: %v\n", err) 330 | } 331 | defer nc.Close() 332 | 333 | s1.Shutdown() 334 | 335 | // wait for disconnect 336 | if e := WaitTime(dch, 2*time.Second); e != nil { 337 | t.Fatal("Did not receive a disconnect callback message") 338 | } 339 | 340 | // Wait, want to make sure we don't spin on reconnect to non-existent servers. 341 | time.Sleep(1 * time.Second) 342 | 343 | // Make sure we are still reconnecting.. 344 | if closedCbCalled { 345 | t.Fatal("Closed CB was triggered, should not have been.") 346 | } 347 | if status := nc.Status(); status != nats.RECONNECTING { 348 | t.Fatalf("Wrong status: %d\n", status) 349 | } 350 | } 351 | 352 | func TestProperFalloutAfterMaxAttempts(t *testing.T) { 353 | s1 := RunServerOnPort(1222) 354 | defer s1.Shutdown() 355 | 356 | opts := nats.DefaultOptions 357 | // Reduce the list of servers for Windows tests 358 | if runtime.GOOS == "windows" { 359 | opts.Servers = testServers[:2] 360 | opts.MaxReconnect = 2 361 | } else { 362 | opts.Servers = testServers 363 | opts.MaxReconnect = 5 364 | } 365 | opts.NoRandomize = true 366 | opts.ReconnectWait = (25 * time.Millisecond) 367 | 368 | dch := make(chan bool) 369 | opts.DisconnectedCB = func(_ *nats.Conn) { 370 | dch <- true 371 | } 372 | 373 | closedCbCalled := false 374 | cch := make(chan bool) 375 | 376 | opts.ClosedCB = func(_ *nats.Conn) { 377 | closedCbCalled = true 378 | cch <- true 379 | } 380 | 381 | nc, err := opts.Connect() 382 | if err != nil { 383 | t.Fatalf("Expected to connect, got err: %v\n", err) 384 | } 385 | defer nc.Close() 386 | 387 | s1.Shutdown() 388 | 389 | // On Windows, creating a TCP connection to a server not running takes more than 390 | // a second. So be generous with the WaitTime. 391 | 392 | // wait for disconnect 393 | if e := WaitTime(dch, 5*time.Second); e != nil { 394 | t.Fatal("Did not receive a disconnect callback message") 395 | } 396 | 397 | // Wait for ClosedCB 398 | if e := WaitTime(cch, 5*time.Second); e != nil { 399 | t.Fatal("Did not receive a closed callback message") 400 | } 401 | 402 | // Make sure we are not still reconnecting.. 403 | if !closedCbCalled { 404 | t.Logf("%+v\n", nc) 405 | t.Fatal("Closed CB was not triggered, should have been.") 406 | } 407 | 408 | // Expect connection to be closed... 409 | if !nc.IsClosed() { 410 | t.Fatalf("Wrong status: %d\n", nc.Status()) 411 | } 412 | } 413 | 414 | func TestProperFalloutAfterMaxAttemptsWithAuthMismatch(t *testing.T) { 415 | var myServers = []string{ 416 | "nats://localhost:1222", 417 | "nats://localhost:4443", 418 | } 419 | s1 := RunServerOnPort(1222) 420 | defer s1.Shutdown() 421 | 422 | s2, _ := RunServerWithConfig("./configs/tlsverify.conf") 423 | defer s2.Shutdown() 424 | 425 | opts := nats.DefaultOptions 426 | opts.Servers = myServers 427 | opts.NoRandomize = true 428 | if runtime.GOOS == "windows" { 429 | opts.MaxReconnect = 2 430 | } else { 431 | opts.MaxReconnect = 5 432 | } 433 | opts.ReconnectWait = (25 * time.Millisecond) 434 | 435 | dch := make(chan bool) 436 | opts.DisconnectedCB = func(_ *nats.Conn) { 437 | dch <- true 438 | } 439 | 440 | closedCbCalled := false 441 | cch := make(chan bool) 442 | 443 | opts.ClosedCB = func(_ *nats.Conn) { 444 | closedCbCalled = true 445 | cch <- true 446 | } 447 | 448 | nc, err := opts.Connect() 449 | if err != nil { 450 | t.Fatalf("Expected to connect, got err: %v\n", err) 451 | } 452 | defer nc.Close() 453 | 454 | s1.Shutdown() 455 | 456 | // On Windows, creating a TCP connection to a server not running takes more than 457 | // a second. So be generous with the WaitTime. 458 | 459 | // wait for disconnect 460 | if e := WaitTime(dch, 5*time.Second); e != nil { 461 | t.Fatal("Did not receive a disconnect callback message") 462 | } 463 | 464 | // Wait for ClosedCB 465 | if e := WaitTime(cch, 5*time.Second); e != nil { 466 | t.Fatalf("Did not receive a closed callback message, #reconnects: %v", nc.Reconnects) 467 | } 468 | 469 | // Make sure we have not exceeded MaxReconnect 470 | if nc.Reconnects != uint64(opts.MaxReconnect) { 471 | t.Fatalf("Num reconnects was %v, expected %v", nc.Reconnects, opts.MaxReconnect) 472 | } 473 | 474 | // Make sure we are not still reconnecting.. 475 | if !closedCbCalled { 476 | t.Logf("%+v\n", nc) 477 | t.Fatal("Closed CB was not triggered, should have been.") 478 | } 479 | 480 | // Expect connection to be closed... 481 | if !nc.IsClosed() { 482 | t.Fatalf("Wrong status: %d\n", nc.Status()) 483 | } 484 | } 485 | 486 | func TestTimeoutOnNoServers(t *testing.T) { 487 | s1 := RunServerOnPort(1222) 488 | defer s1.Shutdown() 489 | 490 | opts := nats.DefaultOptions 491 | if runtime.GOOS == "windows" { 492 | opts.Servers = testServers[:2] 493 | opts.MaxReconnect = 2 494 | opts.ReconnectWait = (100 * time.Millisecond) 495 | } else { 496 | opts.Servers = testServers 497 | // 1 second total time wait 498 | opts.MaxReconnect = 10 499 | opts.ReconnectWait = (100 * time.Millisecond) 500 | } 501 | opts.NoRandomize = true 502 | 503 | dch := make(chan bool) 504 | opts.DisconnectedCB = func(nc *nats.Conn) { 505 | // Suppress any additional calls 506 | nc.SetDisconnectHandler(nil) 507 | dch <- true 508 | } 509 | 510 | cch := make(chan bool) 511 | opts.ClosedCB = func(_ *nats.Conn) { 512 | cch <- true 513 | } 514 | 515 | nc, err := opts.Connect() 516 | if err != nil { 517 | t.Fatalf("Expected to connect, got err: %v\n", err) 518 | } 519 | defer nc.Close() 520 | 521 | s1.Shutdown() 522 | 523 | // On Windows, creating a connection to a non-running server takes 524 | // more than a second. So be generous with WaitTime 525 | 526 | // wait for disconnect 527 | if e := WaitTime(dch, 5*time.Second); e != nil { 528 | t.Fatal("Did not receive a disconnect callback message") 529 | } 530 | 531 | startWait := time.Now() 532 | 533 | // Wait for ClosedCB 534 | if e := WaitTime(cch, 5*time.Second); e != nil { 535 | t.Fatal("Did not receive a closed callback message") 536 | } 537 | 538 | if runtime.GOOS != "windows" { 539 | timeWait := time.Since(startWait) 540 | 541 | // Use 500ms as variable time delta 542 | variable := (500 * time.Millisecond) 543 | expected := (time.Duration(opts.MaxReconnect) * opts.ReconnectWait) 544 | 545 | if timeWait > (expected + variable) { 546 | t.Fatalf("Waited too long for Closed state: %d\n", timeWait/time.Millisecond) 547 | } 548 | } 549 | } 550 | 551 | func TestPingReconnect(t *testing.T) { 552 | RECONNECTS := 4 553 | s1 := RunServerOnPort(1222) 554 | defer s1.Shutdown() 555 | 556 | opts := nats.DefaultOptions 557 | opts.Servers = testServers 558 | opts.NoRandomize = true 559 | opts.ReconnectWait = 200 * time.Millisecond 560 | opts.PingInterval = 50 * time.Millisecond 561 | opts.MaxPingsOut = -1 562 | 563 | var wg sync.WaitGroup 564 | wg.Add(1) 565 | rch := make(chan time.Time, RECONNECTS) 566 | dch := make(chan time.Time, RECONNECTS) 567 | 568 | opts.DisconnectedCB = func(_ *nats.Conn) { 569 | d := dch 570 | select { 571 | case d <- time.Now(): 572 | default: 573 | d = nil 574 | } 575 | } 576 | 577 | opts.ReconnectedCB = func(c *nats.Conn) { 578 | r := rch 579 | select { 580 | case r <- time.Now(): 581 | default: 582 | r = nil 583 | wg.Done() 584 | } 585 | } 586 | 587 | nc, err := opts.Connect() 588 | if err != nil { 589 | t.Fatalf("Expected to connect, got err: %v\n", err) 590 | } 591 | defer nc.Close() 592 | 593 | wg.Wait() 594 | s1.Shutdown() 595 | 596 | <-dch 597 | for i := 0; i < RECONNECTS-1; i++ { 598 | disconnectedAt := <-dch 599 | reconnectAt := <-rch 600 | pingCycle := disconnectedAt.Sub(reconnectAt) 601 | if pingCycle > 2*opts.PingInterval { 602 | t.Fatalf("Reconnect due to ping took %s", pingCycle.String()) 603 | } 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /test/reconnect_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | 9 | "github.com/nats-io/gnatsd/server" 10 | "github.com/nats-io/go-nats" 11 | ) 12 | 13 | func startReconnectServer(t *testing.T) *server.Server { 14 | return RunServerOnPort(22222) 15 | } 16 | 17 | func TestReconnectTotalTime(t *testing.T) { 18 | opts := nats.DefaultOptions 19 | totalReconnectTime := time.Duration(opts.MaxReconnect) * opts.ReconnectWait 20 | if totalReconnectTime < (2 * time.Minute) { 21 | t.Fatalf("Total reconnect time should be at least 2 mins: Currently %v\n", 22 | totalReconnectTime) 23 | } 24 | } 25 | 26 | func TestReconnectDisallowedFlags(t *testing.T) { 27 | ts := startReconnectServer(t) 28 | defer ts.Shutdown() 29 | 30 | ch := make(chan bool) 31 | opts := nats.DefaultOptions 32 | opts.Url = "nats://localhost:22222" 33 | opts.AllowReconnect = false 34 | opts.ClosedCB = func(_ *nats.Conn) { 35 | ch <- true 36 | } 37 | nc, err := opts.Connect() 38 | if err != nil { 39 | t.Fatalf("Should have connected ok: %v", err) 40 | } 41 | defer nc.Close() 42 | 43 | ts.Shutdown() 44 | 45 | if e := Wait(ch); e != nil { 46 | t.Fatal("Did not trigger ClosedCB correctly") 47 | } 48 | } 49 | 50 | func TestReconnectAllowedFlags(t *testing.T) { 51 | ts := startReconnectServer(t) 52 | defer ts.Shutdown() 53 | ch := make(chan bool) 54 | dch := make(chan bool) 55 | opts := nats.DefaultOptions 56 | opts.Url = "nats://localhost:22222" 57 | opts.AllowReconnect = true 58 | opts.MaxReconnect = 2 59 | opts.ReconnectWait = 1 * time.Second 60 | 61 | opts.ClosedCB = func(_ *nats.Conn) { 62 | ch <- true 63 | } 64 | opts.DisconnectedCB = func(_ *nats.Conn) { 65 | dch <- true 66 | } 67 | nc, err := opts.Connect() 68 | if err != nil { 69 | t.Fatalf("Should have connected ok: %v", err) 70 | } 71 | defer nc.Close() 72 | 73 | ts.Shutdown() 74 | 75 | // We want wait to timeout here, and the connection 76 | // should not trigger the Close CB. 77 | if e := WaitTime(ch, 500*time.Millisecond); e == nil { 78 | t.Fatal("Triggered ClosedCB incorrectly") 79 | } 80 | 81 | // We should wait to get the disconnected callback to ensure 82 | // that we are in the process of reconnecting. 83 | if e := Wait(dch); e != nil { 84 | t.Fatal("DisconnectedCB should have been triggered") 85 | } 86 | 87 | if !nc.IsReconnecting() { 88 | t.Fatal("Expected to be in a reconnecting state") 89 | } 90 | 91 | // clear the CloseCB since ch will block 92 | nc.Opts.ClosedCB = nil 93 | } 94 | 95 | var reconnectOpts = nats.Options{ 96 | Url: "nats://localhost:22222", 97 | AllowReconnect: true, 98 | MaxReconnect: 10, 99 | ReconnectWait: 100 * time.Millisecond, 100 | Timeout: nats.DefaultTimeout, 101 | } 102 | 103 | func TestConnCloseBreaksReconnectLoop(t *testing.T) { 104 | ts := startReconnectServer(t) 105 | defer ts.Shutdown() 106 | 107 | cch := make(chan bool) 108 | 109 | opts := reconnectOpts 110 | // Bump the max reconnect attempts 111 | opts.MaxReconnect = 100 112 | opts.ClosedCB = func(_ *nats.Conn) { 113 | cch <- true 114 | } 115 | nc, err := opts.Connect() 116 | if err != nil { 117 | t.Fatalf("Should have connected ok: %v", err) 118 | } 119 | defer nc.Close() 120 | nc.Flush() 121 | 122 | // Shutdown the server 123 | ts.Shutdown() 124 | 125 | // Wait a second, then close the connection 126 | time.Sleep(time.Second) 127 | 128 | // Close the connection, this should break the reconnect loop. 129 | // Do this in a go routine since the issue was that Close() 130 | // would block until the reconnect loop is done. 131 | go nc.Close() 132 | 133 | // Even on Windows (where a createConn takes more than a second) 134 | // we should be able to break the reconnect loop with the following 135 | // timeout. 136 | if err := WaitTime(cch, 3*time.Second); err != nil { 137 | t.Fatal("Did not get a closed callback") 138 | } 139 | } 140 | 141 | func TestBasicReconnectFunctionality(t *testing.T) { 142 | ts := startReconnectServer(t) 143 | defer ts.Shutdown() 144 | 145 | ch := make(chan bool) 146 | dch := make(chan bool) 147 | 148 | opts := reconnectOpts 149 | 150 | opts.DisconnectedCB = func(_ *nats.Conn) { 151 | dch <- true 152 | } 153 | 154 | nc, err := opts.Connect() 155 | if err != nil { 156 | t.Fatalf("Should have connected ok: %v\n", err) 157 | } 158 | defer nc.Close() 159 | ec, err := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER) 160 | if err != nil { 161 | t.Fatalf("Failed to create an encoded connection: %v\n", err) 162 | } 163 | 164 | testString := "bar" 165 | ec.Subscribe("foo", func(s string) { 166 | if s != testString { 167 | t.Fatal("String doesn't match") 168 | } 169 | ch <- true 170 | }) 171 | ec.Flush() 172 | 173 | ts.Shutdown() 174 | // server is stopped here... 175 | 176 | if err := Wait(dch); err != nil { 177 | t.Fatalf("Did not get the disconnected callback on time\n") 178 | } 179 | 180 | if err := ec.Publish("foo", testString); err != nil { 181 | t.Fatalf("Failed to publish message: %v\n", err) 182 | } 183 | 184 | ts = startReconnectServer(t) 185 | defer ts.Shutdown() 186 | 187 | if err := ec.FlushTimeout(5 * time.Second); err != nil { 188 | t.Fatalf("Error on Flush: %v", err) 189 | } 190 | 191 | if e := Wait(ch); e != nil { 192 | t.Fatal("Did not receive our message") 193 | } 194 | 195 | expectedReconnectCount := uint64(1) 196 | reconnectCount := ec.Conn.Stats().Reconnects 197 | 198 | if reconnectCount != expectedReconnectCount { 199 | t.Fatalf("Reconnect count incorrect: %d vs %d\n", 200 | reconnectCount, expectedReconnectCount) 201 | } 202 | } 203 | 204 | func TestExtendedReconnectFunctionality(t *testing.T) { 205 | ts := startReconnectServer(t) 206 | defer ts.Shutdown() 207 | 208 | opts := reconnectOpts 209 | dch := make(chan bool) 210 | opts.DisconnectedCB = func(_ *nats.Conn) { 211 | dch <- true 212 | } 213 | rch := make(chan bool) 214 | opts.ReconnectedCB = func(_ *nats.Conn) { 215 | rch <- true 216 | } 217 | nc, err := opts.Connect() 218 | if err != nil { 219 | t.Fatalf("Should have connected ok: %v", err) 220 | } 221 | defer nc.Close() 222 | ec, err := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER) 223 | if err != nil { 224 | t.Fatalf("Failed to create an encoded connection: %v\n", err) 225 | } 226 | testString := "bar" 227 | received := int32(0) 228 | 229 | ec.Subscribe("foo", func(s string) { 230 | atomic.AddInt32(&received, 1) 231 | }) 232 | 233 | sub, _ := ec.Subscribe("foobar", func(s string) { 234 | atomic.AddInt32(&received, 1) 235 | }) 236 | 237 | ec.Publish("foo", testString) 238 | ec.Flush() 239 | 240 | ts.Shutdown() 241 | // server is stopped here.. 242 | 243 | // wait for disconnect 244 | if e := WaitTime(dch, 2*time.Second); e != nil { 245 | t.Fatal("Did not receive a disconnect callback message") 246 | } 247 | 248 | // Sub while disconnected 249 | ec.Subscribe("bar", func(s string) { 250 | atomic.AddInt32(&received, 1) 251 | }) 252 | 253 | // Unsub foobar while disconnected 254 | sub.Unsubscribe() 255 | 256 | if err = ec.Publish("foo", testString); err != nil { 257 | t.Fatalf("Received an error after disconnect: %v\n", err) 258 | } 259 | 260 | if err = ec.Publish("bar", testString); err != nil { 261 | t.Fatalf("Received an error after disconnect: %v\n", err) 262 | } 263 | 264 | ts = startReconnectServer(t) 265 | defer ts.Shutdown() 266 | 267 | // server is restarted here.. 268 | // wait for reconnect 269 | if e := WaitTime(rch, 2*time.Second); e != nil { 270 | t.Fatal("Did not receive a reconnect callback message") 271 | } 272 | 273 | if err = ec.Publish("foobar", testString); err != nil { 274 | t.Fatalf("Received an error after server restarted: %v\n", err) 275 | } 276 | 277 | if err = ec.Publish("foo", testString); err != nil { 278 | t.Fatalf("Received an error after server restarted: %v\n", err) 279 | } 280 | 281 | ch := make(chan bool) 282 | ec.Subscribe("done", func(b bool) { 283 | ch <- true 284 | }) 285 | ec.Publish("done", true) 286 | 287 | if e := Wait(ch); e != nil { 288 | t.Fatal("Did not receive our message") 289 | } 290 | 291 | // Sleep a bit to guarantee scheduler runs and process all subs. 292 | time.Sleep(50 * time.Millisecond) 293 | 294 | if atomic.LoadInt32(&received) != 4 { 295 | t.Fatalf("Received != %d, equals %d\n", 4, received) 296 | } 297 | } 298 | 299 | func TestQueueSubsOnReconnect(t *testing.T) { 300 | ts := startReconnectServer(t) 301 | defer ts.Shutdown() 302 | 303 | opts := reconnectOpts 304 | 305 | // Allow us to block on reconnect complete. 306 | reconnectsDone := make(chan bool) 307 | opts.ReconnectedCB = func(nc *nats.Conn) { 308 | reconnectsDone <- true 309 | } 310 | 311 | // Create connection 312 | nc, err := opts.Connect() 313 | if err != nil { 314 | t.Fatalf("Should have connected ok: %v\n", err) 315 | } 316 | defer nc.Close() 317 | 318 | ec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER) 319 | if err != nil { 320 | t.Fatalf("Failed to create an encoded connection: %v\n", err) 321 | } 322 | 323 | // To hold results. 324 | results := make(map[int]int) 325 | var mu sync.Mutex 326 | 327 | // Make sure we got what we needed, 1 msg only and all seqnos accounted for.. 328 | checkResults := func(numSent int) { 329 | mu.Lock() 330 | defer mu.Unlock() 331 | 332 | for i := 0; i < numSent; i++ { 333 | if results[i] != 1 { 334 | t.Fatalf("Received incorrect number of messages, [%d] for seq: %d\n", results[i], i) 335 | } 336 | } 337 | 338 | // Auto reset results map 339 | results = make(map[int]int) 340 | } 341 | 342 | subj := "foo.bar" 343 | qgroup := "workers" 344 | 345 | cb := func(seqno int) { 346 | mu.Lock() 347 | defer mu.Unlock() 348 | results[seqno] = results[seqno] + 1 349 | } 350 | 351 | // Create Queue Subscribers 352 | ec.QueueSubscribe(subj, qgroup, cb) 353 | ec.QueueSubscribe(subj, qgroup, cb) 354 | 355 | ec.Flush() 356 | 357 | // Helper function to send messages and check results. 358 | sendAndCheckMsgs := func(numToSend int) { 359 | for i := 0; i < numToSend; i++ { 360 | ec.Publish(subj, i) 361 | } 362 | // Wait for processing. 363 | ec.Flush() 364 | time.Sleep(50 * time.Millisecond) 365 | 366 | // Check Results 367 | checkResults(numToSend) 368 | } 369 | 370 | // Base Test 371 | sendAndCheckMsgs(10) 372 | 373 | // Stop and restart server 374 | ts.Shutdown() 375 | ts = startReconnectServer(t) 376 | defer ts.Shutdown() 377 | 378 | if err := Wait(reconnectsDone); err != nil { 379 | t.Fatal("Did not get the ReconnectedCB!") 380 | } 381 | 382 | // Reconnect Base Test 383 | sendAndCheckMsgs(10) 384 | } 385 | 386 | func TestIsClosed(t *testing.T) { 387 | ts := startReconnectServer(t) 388 | defer ts.Shutdown() 389 | 390 | nc := NewConnection(t, 22222) 391 | defer nc.Close() 392 | 393 | if nc.IsClosed() { 394 | t.Fatalf("IsClosed returned true when the connection is still open.") 395 | } 396 | ts.Shutdown() 397 | if nc.IsClosed() { 398 | t.Fatalf("IsClosed returned true when the connection is still open.") 399 | } 400 | ts = startReconnectServer(t) 401 | defer ts.Shutdown() 402 | if nc.IsClosed() { 403 | t.Fatalf("IsClosed returned true when the connection is still open.") 404 | } 405 | nc.Close() 406 | if !nc.IsClosed() { 407 | t.Fatalf("IsClosed returned false after Close() was called.") 408 | } 409 | } 410 | 411 | func TestIsReconnectingAndStatus(t *testing.T) { 412 | ts := startReconnectServer(t) 413 | defer ts.Shutdown() 414 | 415 | disconnectedch := make(chan bool) 416 | reconnectch := make(chan bool) 417 | opts := nats.DefaultOptions 418 | opts.Url = "nats://localhost:22222" 419 | opts.AllowReconnect = true 420 | opts.MaxReconnect = 10000 421 | opts.ReconnectWait = 100 * time.Millisecond 422 | 423 | opts.DisconnectedCB = func(_ *nats.Conn) { 424 | disconnectedch <- true 425 | } 426 | opts.ReconnectedCB = func(_ *nats.Conn) { 427 | reconnectch <- true 428 | } 429 | 430 | // Connect, verify initial reconnecting state check, then stop the server 431 | nc, err := opts.Connect() 432 | if err != nil { 433 | t.Fatalf("Should have connected ok: %v", err) 434 | } 435 | defer nc.Close() 436 | 437 | if nc.IsReconnecting() { 438 | t.Fatalf("IsReconnecting returned true when the connection is still open.") 439 | } 440 | if status := nc.Status(); status != nats.CONNECTED { 441 | t.Fatalf("Status returned %d when connected instead of CONNECTED", status) 442 | } 443 | ts.Shutdown() 444 | 445 | // Wait until we get the disconnected callback 446 | if e := Wait(disconnectedch); e != nil { 447 | t.Fatalf("Disconnect callback wasn't triggered: %v", e) 448 | } 449 | if !nc.IsReconnecting() { 450 | t.Fatalf("IsReconnecting returned false when the client is reconnecting.") 451 | } 452 | if status := nc.Status(); status != nats.RECONNECTING { 453 | t.Fatalf("Status returned %d when reconnecting instead of CONNECTED", status) 454 | } 455 | 456 | ts = startReconnectServer(t) 457 | defer ts.Shutdown() 458 | 459 | // Wait until we get the reconnect callback 460 | if e := Wait(reconnectch); e != nil { 461 | t.Fatalf("Reconnect callback wasn't triggered: %v", e) 462 | } 463 | if nc.IsReconnecting() { 464 | t.Fatalf("IsReconnecting returned true after the connection was reconnected.") 465 | } 466 | if status := nc.Status(); status != nats.CONNECTED { 467 | t.Fatalf("Status returned %d when reconnected instead of CONNECTED", status) 468 | } 469 | 470 | // Close the connection, reconnecting should still be false 471 | nc.Close() 472 | if nc.IsReconnecting() { 473 | t.Fatalf("IsReconnecting returned true after Close() was called.") 474 | } 475 | if status := nc.Status(); status != nats.CLOSED { 476 | t.Fatalf("Status returned %d after Close() was called instead of CLOSED", status) 477 | } 478 | } 479 | 480 | func TestFullFlushChanDuringReconnect(t *testing.T) { 481 | ts := startReconnectServer(t) 482 | defer ts.Shutdown() 483 | 484 | reconnectch := make(chan bool) 485 | 486 | opts := nats.DefaultOptions 487 | opts.Url = "nats://localhost:22222" 488 | opts.AllowReconnect = true 489 | opts.MaxReconnect = 10000 490 | opts.ReconnectWait = 100 * time.Millisecond 491 | 492 | opts.ReconnectedCB = func(_ *nats.Conn) { 493 | reconnectch <- true 494 | } 495 | 496 | // Connect 497 | nc, err := opts.Connect() 498 | if err != nil { 499 | t.Fatalf("Should have connected ok: %v", err) 500 | } 501 | defer nc.Close() 502 | 503 | // Channel used to make the go routine sending messages to stop. 504 | stop := make(chan bool) 505 | 506 | // While connected, publish as fast as we can 507 | go func() { 508 | for i := 0; ; i++ { 509 | _ = nc.Publish("foo", []byte("hello")) 510 | 511 | // Make sure we are sending at least flushChanSize (1024) messages 512 | // before potentially pausing. 513 | if i%2000 == 0 { 514 | select { 515 | case <-stop: 516 | return 517 | default: 518 | time.Sleep(100 * time.Millisecond) 519 | } 520 | } 521 | } 522 | }() 523 | 524 | // Send a bit... 525 | time.Sleep(500 * time.Millisecond) 526 | 527 | // Shut down the server 528 | ts.Shutdown() 529 | 530 | // Continue sending while we are disconnected 531 | time.Sleep(time.Second) 532 | 533 | // Restart the server 534 | ts = startReconnectServer(t) 535 | defer ts.Shutdown() 536 | 537 | // Wait for the reconnect CB to be invoked (but not for too long) 538 | if e := WaitTime(reconnectch, 5*time.Second); e != nil { 539 | t.Fatalf("Reconnect callback wasn't triggered: %v", e) 540 | } 541 | } 542 | 543 | func TestReconnectVerbose(t *testing.T) { 544 | s := RunDefaultServer() 545 | defer s.Shutdown() 546 | 547 | o := nats.DefaultOptions 548 | o.Verbose = true 549 | rch := make(chan bool) 550 | o.ReconnectedCB = func(_ *nats.Conn) { 551 | rch <- true 552 | } 553 | 554 | nc, err := o.Connect() 555 | if err != nil { 556 | t.Fatalf("Should have connected ok: %v", err) 557 | } 558 | defer nc.Close() 559 | 560 | err = nc.Flush() 561 | if err != nil { 562 | t.Fatalf("Error during flush: %v", err) 563 | } 564 | 565 | s.Shutdown() 566 | s = RunDefaultServer() 567 | defer s.Shutdown() 568 | 569 | if e := Wait(rch); e != nil { 570 | t.Fatal("Should have reconnected ok") 571 | } 572 | 573 | err = nc.Flush() 574 | if err != nil { 575 | t.Fatalf("Error during flush: %v", err) 576 | } 577 | } 578 | 579 | func TestReconnectBufSize(t *testing.T) { 580 | s := RunDefaultServer() 581 | defer s.Shutdown() 582 | 583 | o := nats.DefaultOptions 584 | o.ReconnectBufSize = 32 // 32 bytes 585 | 586 | dch := make(chan bool) 587 | o.DisconnectedCB = func(_ *nats.Conn) { 588 | dch <- true 589 | } 590 | 591 | nc, err := o.Connect() 592 | if err != nil { 593 | t.Fatalf("Should have connected ok: %v", err) 594 | } 595 | defer nc.Close() 596 | 597 | err = nc.Flush() 598 | if err != nil { 599 | t.Fatalf("Error during flush: %v", err) 600 | } 601 | 602 | // Force disconnected state. 603 | s.Shutdown() 604 | 605 | if e := Wait(dch); e != nil { 606 | t.Fatal("DisconnectedCB should have been triggered") 607 | } 608 | 609 | msg := []byte("food") // 4 bytes paylaod, total proto is 16 bytes 610 | // These should work, 2X16 = 32 611 | if err := nc.Publish("foo", msg); err != nil { 612 | t.Fatalf("Failed to publish message: %v\n", err) 613 | } 614 | if err := nc.Publish("foo", msg); err != nil { 615 | t.Fatalf("Failed to publish message: %v\n", err) 616 | } 617 | 618 | // This should fail since we have exhausted the backing buffer. 619 | if err := nc.Publish("foo", msg); err == nil { 620 | // t.Fatalf("Expected to fail to publish message: got no error\n") 621 | } 622 | nc.Buffered() 623 | } 624 | -------------------------------------------------------------------------------- /test/basic_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bytes" 5 | "math" 6 | "regexp" 7 | "runtime" 8 | "sync" 9 | "sync/atomic" 10 | "testing" 11 | "time" 12 | 13 | "github.com/nats-io/go-nats" 14 | ) 15 | 16 | func TestCloseLeakingGoRoutines(t *testing.T) { 17 | s := RunDefaultServer() 18 | defer s.Shutdown() 19 | 20 | // Give time for things to settle before capturing the number of 21 | // go routines 22 | time.Sleep(500 * time.Millisecond) 23 | 24 | base := runtime.NumGoroutine() 25 | 26 | nc := NewDefaultConnection(t) 27 | 28 | nc.Flush() 29 | nc.Close() 30 | 31 | // Give time for things to settle before capturing the number of 32 | // go routines 33 | time.Sleep(500 * time.Millisecond) 34 | 35 | delta := (runtime.NumGoroutine() - base) 36 | if delta > 0 { 37 | t.Fatalf("%d Go routines still exist post Close()", delta) 38 | } 39 | // Make sure we can call Close() multiple times 40 | nc.Close() 41 | } 42 | 43 | func TestLeakingGoRoutinesOnFailedConnect(t *testing.T) { 44 | // Give time for things to settle before capturing the number of 45 | // go routines 46 | time.Sleep(500 * time.Millisecond) 47 | 48 | base := runtime.NumGoroutine() 49 | 50 | nc, err := nats.Connect(nats.DefaultURL) 51 | if err == nil { 52 | nc.Close() 53 | t.Fatalf("Expected failure to connect") 54 | } 55 | 56 | // Give time for things to settle before capturing the number of 57 | // go routines 58 | time.Sleep(500 * time.Millisecond) 59 | 60 | delta := (runtime.NumGoroutine() - base) 61 | if delta > 0 { 62 | t.Fatalf("%d Go routines still exist post Close()", delta) 63 | } 64 | } 65 | 66 | func TestConnectedServer(t *testing.T) { 67 | s := RunDefaultServer() 68 | defer s.Shutdown() 69 | 70 | nc := NewDefaultConnection(t) 71 | defer nc.Close() 72 | 73 | u := nc.ConnectedUrl() 74 | if u == "" || u != nats.DefaultURL { 75 | t.Fatalf("Unexpected connected URL of %s\n", u) 76 | } 77 | srv := nc.ConnectedServerId() 78 | if srv == "" { 79 | t.Fatal("Expeced a connected server id") 80 | } 81 | nc.Close() 82 | u = nc.ConnectedUrl() 83 | if u != "" { 84 | t.Fatalf("Expected a nil connected URL, got %s\n", u) 85 | } 86 | srv = nc.ConnectedServerId() 87 | if srv != "" { 88 | t.Fatalf("Expected a nil connect server, got %s\n", srv) 89 | } 90 | } 91 | 92 | func TestMultipleClose(t *testing.T) { 93 | s := RunDefaultServer() 94 | defer s.Shutdown() 95 | nc := NewDefaultConnection(t) 96 | 97 | var wg sync.WaitGroup 98 | for i := 0; i < 10; i++ { 99 | wg.Add(1) 100 | go func() { 101 | nc.Close() 102 | wg.Done() 103 | }() 104 | } 105 | wg.Wait() 106 | } 107 | 108 | func TestBadOptionTimeoutConnect(t *testing.T) { 109 | s := RunDefaultServer() 110 | defer s.Shutdown() 111 | 112 | opts := nats.DefaultOptions 113 | opts.Timeout = -1 114 | opts.Url = "nats://localhost:4222" 115 | 116 | _, err := opts.Connect() 117 | if err == nil { 118 | t.Fatal("Expected an error") 119 | } 120 | if err != nats.ErrNoServers { 121 | t.Fatalf("Expected a ErrNoServers error: Got %v\n", err) 122 | } 123 | } 124 | 125 | func TestSimplePublish(t *testing.T) { 126 | s := RunDefaultServer() 127 | defer s.Shutdown() 128 | nc := NewDefaultConnection(t) 129 | defer nc.Close() 130 | 131 | if err := nc.Publish("foo", []byte("Hello World")); err != nil { 132 | t.Fatal("Failed to publish string message: ", err) 133 | } 134 | } 135 | 136 | func TestSimplePublishNoData(t *testing.T) { 137 | s := RunDefaultServer() 138 | defer s.Shutdown() 139 | nc := NewDefaultConnection(t) 140 | defer nc.Close() 141 | 142 | if err := nc.Publish("foo", nil); err != nil { 143 | t.Fatal("Failed to publish empty message: ", err) 144 | } 145 | } 146 | 147 | func TestPublishDoesNotFailOnSlowConsumer(t *testing.T) { 148 | s := RunDefaultServer() 149 | defer s.Shutdown() 150 | nc := NewDefaultConnection(t) 151 | defer nc.Close() 152 | 153 | sub, err := nc.SubscribeSync("foo") 154 | if err != nil { 155 | t.Fatalf("Unable to create subscription: %v", err) 156 | } 157 | 158 | if err := sub.SetPendingLimits(1, 1000); err != nil { 159 | t.Fatalf("Unable to set pending limits: %v", err) 160 | } 161 | 162 | var pubErr error 163 | 164 | msg := []byte("Hello") 165 | for i := 0; i < 10; i++ { 166 | pubErr = nc.Publish("foo", msg) 167 | if pubErr != nil { 168 | break 169 | } 170 | nc.Flush() 171 | } 172 | 173 | if pubErr != nil { 174 | t.Fatalf("Publish() should not fail because of slow consumer. Got '%v'", pubErr) 175 | } 176 | } 177 | 178 | func TestAsyncSubscribe(t *testing.T) { 179 | s := RunDefaultServer() 180 | defer s.Shutdown() 181 | nc := NewDefaultConnection(t) 182 | defer nc.Close() 183 | 184 | omsg := []byte("Hello World") 185 | ch := make(chan bool) 186 | 187 | // Callback is mandatory 188 | if _, err := nc.Subscribe("foo", nil); err == nil { 189 | t.Fatal("Creating subscription without callback should have failed") 190 | } 191 | 192 | _, err := nc.Subscribe("foo", func(m *nats.Msg) { 193 | if !bytes.Equal(m.Data, omsg) { 194 | t.Fatal("Message received does not match") 195 | } 196 | if m.Sub == nil { 197 | t.Fatal("Callback does not have a valid Subscription") 198 | } 199 | ch <- true 200 | }) 201 | if err != nil { 202 | t.Fatal("Failed to subscribe: ", err) 203 | } 204 | nc.Publish("foo", omsg) 205 | if e := Wait(ch); e != nil { 206 | t.Fatal("Message not received for subscription") 207 | } 208 | } 209 | 210 | func TestAsyncSubscribeRoutineLeakOnUnsubscribe(t *testing.T) { 211 | s := RunDefaultServer() 212 | defer s.Shutdown() 213 | 214 | nc := NewDefaultConnection(t) 215 | defer nc.Close() 216 | 217 | ch := make(chan bool) 218 | 219 | // Give time for things to settle before capturing the number of 220 | // go routines 221 | time.Sleep(500 * time.Millisecond) 222 | 223 | // Take the base once the connection is established, but before 224 | // the subscriber is created. 225 | base := runtime.NumGoroutine() 226 | 227 | sub, err := nc.Subscribe("foo", func(m *nats.Msg) { ch <- true }) 228 | if err != nil { 229 | t.Fatal("Failed to subscribe: ", err) 230 | } 231 | 232 | // Send to ourself 233 | nc.Publish("foo", []byte("hello")) 234 | 235 | // This ensures that the async delivery routine is up and running. 236 | if err := Wait(ch); err != nil { 237 | t.Fatal("Failed to receive message") 238 | } 239 | 240 | // Make sure to give it time to go back into wait 241 | time.Sleep(200 * time.Millisecond) 242 | 243 | // Explicit unsubscribe 244 | sub.Unsubscribe() 245 | 246 | // Give time for things to settle before capturing the number of 247 | // go routines 248 | time.Sleep(500 * time.Millisecond) 249 | 250 | delta := (runtime.NumGoroutine() - base) 251 | if delta > 0 { 252 | t.Fatalf("%d Go routines still exist post Unsubscribe()", delta) 253 | } 254 | } 255 | 256 | func TestAsyncSubscribeRoutineLeakOnClose(t *testing.T) { 257 | s := RunDefaultServer() 258 | defer s.Shutdown() 259 | 260 | ch := make(chan bool) 261 | 262 | // Give time for things to settle before capturing the number of 263 | // go routines 264 | time.Sleep(500 * time.Millisecond) 265 | 266 | // Take the base before creating the connection, since we are going 267 | // to close it before taking the delta. 268 | base := runtime.NumGoroutine() 269 | 270 | nc := NewDefaultConnection(t) 271 | defer nc.Close() 272 | 273 | _, err := nc.Subscribe("foo", func(m *nats.Msg) { ch <- true }) 274 | if err != nil { 275 | t.Fatal("Failed to subscribe: ", err) 276 | } 277 | 278 | // Send to ourself 279 | nc.Publish("foo", []byte("hello")) 280 | 281 | // This ensures that the async delivery routine is up and running. 282 | if err := Wait(ch); err != nil { 283 | t.Fatal("Failed to receive message") 284 | } 285 | 286 | // Make sure to give it time to go back into wait 287 | time.Sleep(200 * time.Millisecond) 288 | 289 | // Close connection without explicit unsubscribe 290 | nc.Close() 291 | 292 | // Give time for things to settle before capturing the number of 293 | // go routines 294 | time.Sleep(500 * time.Millisecond) 295 | 296 | delta := (runtime.NumGoroutine() - base) 297 | if delta > 0 { 298 | t.Fatalf("%d Go routines still exist post Close()", delta) 299 | } 300 | } 301 | 302 | func TestSyncSubscribe(t *testing.T) { 303 | s := RunDefaultServer() 304 | defer s.Shutdown() 305 | nc := NewDefaultConnection(t) 306 | defer nc.Close() 307 | 308 | sub, err := nc.SubscribeSync("foo") 309 | if err != nil { 310 | t.Fatal("Failed to subscribe: ", err) 311 | } 312 | omsg := []byte("Hello World") 313 | nc.Publish("foo", omsg) 314 | msg, err := sub.NextMsg(1 * time.Second) 315 | if err != nil || !bytes.Equal(msg.Data, omsg) { 316 | t.Fatal("Message received does not match") 317 | } 318 | } 319 | 320 | func TestPubSubWithReply(t *testing.T) { 321 | s := RunDefaultServer() 322 | defer s.Shutdown() 323 | nc := NewDefaultConnection(t) 324 | defer nc.Close() 325 | 326 | sub, err := nc.SubscribeSync("foo") 327 | if err != nil { 328 | t.Fatal("Failed to subscribe: ", err) 329 | } 330 | omsg := []byte("Hello World") 331 | nc.PublishMsg(&nats.Msg{Subject: "foo", Reply: "bar", Data: omsg}) 332 | msg, err := sub.NextMsg(10 * time.Second) 333 | if err != nil || !bytes.Equal(msg.Data, omsg) { 334 | t.Fatal("Message received does not match") 335 | } 336 | } 337 | 338 | func TestFlush(t *testing.T) { 339 | s := RunDefaultServer() 340 | defer s.Shutdown() 341 | nc := NewDefaultConnection(t) 342 | defer nc.Close() 343 | 344 | omsg := []byte("Hello World") 345 | for i := 0; i < 10000; i++ { 346 | nc.Publish("flush", omsg) 347 | } 348 | if err := nc.FlushTimeout(0); err == nil { 349 | t.Fatal("Calling FlushTimeout() with invalid timeout should fail") 350 | } 351 | if err := nc.Flush(); err != nil { 352 | t.Fatalf("Received error from flush: %s\n", err) 353 | } 354 | if nb, _ := nc.Buffered(); nb > 0 { 355 | t.Fatalf("Outbound buffer not empty: %d bytes\n", nb) 356 | } 357 | 358 | nc.Close() 359 | if _, err := nc.Buffered(); err == nil { 360 | t.Fatal("Calling Buffered() on closed connection should fail") 361 | } 362 | } 363 | 364 | func TestQueueSubscriber(t *testing.T) { 365 | s := RunDefaultServer() 366 | defer s.Shutdown() 367 | nc := NewDefaultConnection(t) 368 | defer nc.Close() 369 | 370 | s1, _ := nc.QueueSubscribeSync("foo", "bar") 371 | s2, _ := nc.QueueSubscribeSync("foo", "bar") 372 | omsg := []byte("Hello World") 373 | nc.Publish("foo", omsg) 374 | nc.Flush() 375 | r1, _ := s1.QueuedMsgs() 376 | r2, _ := s2.QueuedMsgs() 377 | if (r1 + r2) != 1 { 378 | t.Fatal("Received too many messages for multiple queue subscribers") 379 | } 380 | // Drain messages 381 | s1.NextMsg(time.Second) 382 | s2.NextMsg(time.Second) 383 | 384 | total := 1000 385 | for i := 0; i < total; i++ { 386 | nc.Publish("foo", omsg) 387 | } 388 | nc.Flush() 389 | v := uint(float32(total) * 0.15) 390 | r1, _ = s1.QueuedMsgs() 391 | r2, _ = s2.QueuedMsgs() 392 | if r1+r2 != total { 393 | t.Fatalf("Incorrect number of messages: %d vs %d", (r1 + r2), total) 394 | } 395 | expected := total / 2 396 | d1 := uint(math.Abs(float64(expected - r1))) 397 | d2 := uint(math.Abs(float64(expected - r2))) 398 | if d1 > v || d2 > v { 399 | t.Fatalf("Too much variance in totals: %d, %d > %d", d1, d2, v) 400 | } 401 | } 402 | 403 | func TestReplyArg(t *testing.T) { 404 | s := RunDefaultServer() 405 | defer s.Shutdown() 406 | nc := NewDefaultConnection(t) 407 | defer nc.Close() 408 | 409 | ch := make(chan bool) 410 | replyExpected := "bar" 411 | 412 | nc.Subscribe("foo", func(m *nats.Msg) { 413 | if m.Reply != replyExpected { 414 | t.Fatalf("Did not receive correct reply arg in callback: "+ 415 | "('%s' vs '%s')", m.Reply, replyExpected) 416 | } 417 | ch <- true 418 | }) 419 | nc.PublishMsg(&nats.Msg{Subject: "foo", Reply: replyExpected, Data: []byte("Hello")}) 420 | if e := Wait(ch); e != nil { 421 | t.Fatal("Did not receive callback") 422 | } 423 | } 424 | 425 | func TestSyncReplyArg(t *testing.T) { 426 | s := RunDefaultServer() 427 | defer s.Shutdown() 428 | nc := NewDefaultConnection(t) 429 | defer nc.Close() 430 | 431 | replyExpected := "bar" 432 | sub, _ := nc.SubscribeSync("foo") 433 | nc.PublishMsg(&nats.Msg{Subject: "foo", Reply: replyExpected, Data: []byte("Hello")}) 434 | msg, err := sub.NextMsg(1 * time.Second) 435 | if err != nil { 436 | t.Fatal("Received an err on NextMsg()") 437 | } 438 | if msg.Reply != replyExpected { 439 | t.Fatalf("Did not receive correct reply arg in callback: "+ 440 | "('%s' vs '%s')", msg.Reply, replyExpected) 441 | } 442 | } 443 | 444 | func TestUnsubscribe(t *testing.T) { 445 | s := RunDefaultServer() 446 | defer s.Shutdown() 447 | nc := NewDefaultConnection(t) 448 | defer nc.Close() 449 | 450 | received := int32(0) 451 | max := int32(10) 452 | ch := make(chan bool) 453 | nc.Subscribe("foo", func(m *nats.Msg) { 454 | atomic.AddInt32(&received, 1) 455 | if received == max { 456 | err := m.Sub.Unsubscribe() 457 | if err != nil { 458 | t.Fatal("Unsubscribe failed with err:", err) 459 | } 460 | ch <- true 461 | } 462 | }) 463 | send := 20 464 | for i := 0; i < send; i++ { 465 | nc.Publish("foo", []byte("hello")) 466 | } 467 | nc.Flush() 468 | <-ch 469 | 470 | r := atomic.LoadInt32(&received) 471 | if r != max { 472 | t.Fatalf("Received wrong # of messages after unsubscribe: %d vs %d", 473 | r, max) 474 | } 475 | } 476 | 477 | func TestDoubleUnsubscribe(t *testing.T) { 478 | s := RunDefaultServer() 479 | defer s.Shutdown() 480 | nc := NewDefaultConnection(t) 481 | defer nc.Close() 482 | 483 | sub, err := nc.SubscribeSync("foo") 484 | if err != nil { 485 | t.Fatal("Failed to subscribe: ", err) 486 | } 487 | if err = sub.Unsubscribe(); err != nil { 488 | t.Fatal("Unsubscribe failed with err:", err) 489 | } 490 | if err = sub.Unsubscribe(); err == nil { 491 | t.Fatal("Unsubscribe should have reported an error") 492 | } 493 | } 494 | 495 | func TestRequestTimeout(t *testing.T) { 496 | s := RunDefaultServer() 497 | defer s.Shutdown() 498 | nc := NewDefaultConnection(t) 499 | defer nc.Close() 500 | 501 | if _, err := nc.Request("foo", []byte("help"), 10*time.Millisecond); err == nil { 502 | t.Fatalf("Expected to receive a timeout error") 503 | } 504 | } 505 | 506 | func TestRequest(t *testing.T) { 507 | s := RunDefaultServer() 508 | defer s.Shutdown() 509 | nc := NewDefaultConnection(t) 510 | defer nc.Close() 511 | 512 | response := []byte("I will help you") 513 | nc.Subscribe("foo", func(m *nats.Msg) { 514 | nc.Publish(m.Reply, response) 515 | }) 516 | msg, err := nc.Request("foo", []byte("help"), 500*time.Millisecond) 517 | if err != nil { 518 | t.Fatalf("Received an error on Request test: %s", err) 519 | } 520 | if !bytes.Equal(msg.Data, response) { 521 | t.Fatalf("Received invalid response") 522 | } 523 | } 524 | 525 | func TestRequestNoBody(t *testing.T) { 526 | s := RunDefaultServer() 527 | defer s.Shutdown() 528 | nc := NewDefaultConnection(t) 529 | defer nc.Close() 530 | 531 | response := []byte("I will help you") 532 | nc.Subscribe("foo", func(m *nats.Msg) { 533 | nc.Publish(m.Reply, response) 534 | }) 535 | msg, err := nc.Request("foo", nil, 500*time.Millisecond) 536 | if err != nil { 537 | t.Fatalf("Received an error on Request test: %s", err) 538 | } 539 | if !bytes.Equal(msg.Data, response) { 540 | t.Fatalf("Received invalid response") 541 | } 542 | } 543 | 544 | func TestFlushInCB(t *testing.T) { 545 | s := RunDefaultServer() 546 | defer s.Shutdown() 547 | nc := NewDefaultConnection(t) 548 | defer nc.Close() 549 | 550 | ch := make(chan bool) 551 | 552 | nc.Subscribe("foo", func(_ *nats.Msg) { 553 | nc.Flush() 554 | ch <- true 555 | }) 556 | nc.Publish("foo", []byte("Hello")) 557 | if e := Wait(ch); e != nil { 558 | t.Fatal("Flush did not return properly in callback") 559 | } 560 | } 561 | 562 | func TestReleaseFlush(t *testing.T) { 563 | s := RunDefaultServer() 564 | defer s.Shutdown() 565 | nc := NewDefaultConnection(t) 566 | 567 | for i := 0; i < 1000; i++ { 568 | nc.Publish("foo", []byte("Hello")) 569 | } 570 | go nc.Close() 571 | nc.Flush() 572 | } 573 | 574 | func TestInbox(t *testing.T) { 575 | inbox := nats.NewInbox() 576 | if matched, _ := regexp.Match(`_INBOX.\S`, []byte(inbox)); !matched { 577 | t.Fatal("Bad INBOX format") 578 | } 579 | } 580 | 581 | func TestStats(t *testing.T) { 582 | s := RunDefaultServer() 583 | defer s.Shutdown() 584 | nc := NewDefaultConnection(t) 585 | defer nc.Close() 586 | 587 | data := []byte("The quick brown fox jumped over the lazy dog") 588 | iter := 10 589 | 590 | for i := 0; i < iter; i++ { 591 | nc.Publish("foo", data) 592 | } 593 | 594 | if nc.OutMsgs != uint64(iter) { 595 | t.Fatalf("Not properly tracking OutMsgs: received %d, wanted %d\n", nc.OutMsgs, iter) 596 | } 597 | obb := uint64(iter * len(data)) 598 | if nc.OutBytes != obb { 599 | t.Fatalf("Not properly tracking OutBytes: received %d, wanted %d\n", nc.OutBytes, obb) 600 | } 601 | 602 | // Clear outbound 603 | nc.OutMsgs, nc.OutBytes = 0, 0 604 | 605 | // Test both sync and async versions of subscribe. 606 | nc.Subscribe("foo", func(_ *nats.Msg) {}) 607 | nc.SubscribeSync("foo") 608 | 609 | for i := 0; i < iter; i++ { 610 | nc.Publish("foo", data) 611 | } 612 | nc.Flush() 613 | 614 | if nc.InMsgs != uint64(2*iter) { 615 | t.Fatalf("Not properly tracking InMsgs: received %d, wanted %d\n", nc.InMsgs, 2*iter) 616 | } 617 | 618 | ibb := 2 * obb 619 | if nc.InBytes != ibb { 620 | t.Fatalf("Not properly tracking InBytes: received %d, wanted %d\n", nc.InBytes, ibb) 621 | } 622 | } 623 | 624 | func TestRaceSafeStats(t *testing.T) { 625 | s := RunDefaultServer() 626 | defer s.Shutdown() 627 | nc := NewDefaultConnection(t) 628 | defer nc.Close() 629 | 630 | go nc.Publish("foo", []byte("Hello World")) 631 | time.Sleep(200 * time.Millisecond) 632 | 633 | stats := nc.Stats() 634 | 635 | if stats.OutMsgs != uint64(1) { 636 | t.Fatalf("Not properly tracking OutMsgs: received %d, wanted %d\n", nc.OutMsgs, 1) 637 | } 638 | } 639 | 640 | func TestBadSubject(t *testing.T) { 641 | s := RunDefaultServer() 642 | defer s.Shutdown() 643 | nc := NewDefaultConnection(t) 644 | defer nc.Close() 645 | 646 | err := nc.Publish("", []byte("Hello World")) 647 | if err == nil { 648 | t.Fatalf("Expected an error on bad subject to publish") 649 | } 650 | if err != nats.ErrBadSubject { 651 | t.Fatalf("Expected a ErrBadSubject error: Got %v\n", err) 652 | } 653 | } 654 | 655 | func TestOptions(t *testing.T) { 656 | s := RunDefaultServer() 657 | defer s.Shutdown() 658 | 659 | nc, err := nats.Connect(nats.DefaultURL, nats.Name("myName"), nats.MaxReconnects(2), nats.ReconnectWait(50*time.Millisecond)) 660 | if err != nil { 661 | t.Fatalf("Failed to connect: %v", err) 662 | } 663 | defer nc.Close() 664 | 665 | rch := make(chan bool) 666 | cch := make(chan bool) 667 | 668 | nc.SetReconnectHandler(func(_ *nats.Conn) { rch <- true }) 669 | nc.SetClosedHandler(func(_ *nats.Conn) { cch <- true }) 670 | 671 | s.Shutdown() 672 | 673 | s = RunDefaultServer() 674 | defer s.Shutdown() 675 | 676 | if err := Wait(rch); err != nil { 677 | t.Fatal("Failed getting reconnected cb") 678 | } 679 | 680 | nc.Close() 681 | 682 | if err := Wait(cch); err != nil { 683 | t.Fatal("Failed getting closed cb") 684 | } 685 | 686 | nc, err = nats.Connect(nats.DefaultURL, nats.NoReconnect()) 687 | if err != nil { 688 | t.Fatalf("Failed to connect: %v", err) 689 | } 690 | defer nc.Close() 691 | 692 | nc.SetReconnectHandler(func(_ *nats.Conn) { rch <- true }) 693 | nc.SetClosedHandler(func(_ *nats.Conn) { cch <- true }) 694 | 695 | s.Shutdown() 696 | 697 | // We should not get a reconnect cb this time 698 | if err := WaitTime(rch, time.Second); err == nil { 699 | t.Fatal("Unexpected reconnect cb") 700 | } 701 | 702 | nc.Close() 703 | 704 | if err := Wait(cch); err != nil { 705 | t.Fatal("Failed getting closed cb") 706 | } 707 | } 708 | 709 | func TestNilConnection(t *testing.T) { 710 | var nc *nats.Conn 711 | data := []byte("ok") 712 | 713 | // Publish 714 | if err := nc.Publish("foo", data); err == nil || err != nats.ErrInvalidConnection { 715 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 716 | } 717 | if err := nc.PublishMsg(nil); err == nil || err != nats.ErrInvalidMsg { 718 | t.Fatalf("Expected ErrInvalidMsg error, got %v\n", err) 719 | } 720 | if err := nc.PublishMsg(&nats.Msg{}); err == nil || err != nats.ErrInvalidConnection { 721 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 722 | } 723 | if err := nc.PublishRequest("foo", "reply", data); err == nil || err != nats.ErrInvalidConnection { 724 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 725 | } 726 | 727 | // Subscribe 728 | if _, err := nc.Subscribe("foo", nil); err == nil || err != nats.ErrInvalidConnection { 729 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 730 | } 731 | if _, err := nc.SubscribeSync("foo"); err == nil || err != nats.ErrInvalidConnection { 732 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 733 | } 734 | if _, err := nc.QueueSubscribe("foo", "bar", nil); err == nil || err != nats.ErrInvalidConnection { 735 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 736 | } 737 | ch := make(chan *nats.Msg) 738 | if _, err := nc.ChanSubscribe("foo", ch); err == nil || err != nats.ErrInvalidConnection { 739 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 740 | } 741 | if _, err := nc.ChanQueueSubscribe("foo", "bar", ch); err == nil || err != nats.ErrInvalidConnection { 742 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 743 | } 744 | if _, err := nc.QueueSubscribeSyncWithChan("foo", "bar", ch); err == nil || err != nats.ErrInvalidConnection { 745 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 746 | } 747 | 748 | // Flush 749 | if err := nc.Flush(); err == nil || err != nats.ErrInvalidConnection { 750 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 751 | } 752 | if err := nc.FlushTimeout(time.Millisecond); err == nil || err != nats.ErrInvalidConnection { 753 | t.Fatalf("Expected ErrInvalidConnection error, got %v\n", err) 754 | } 755 | 756 | // Nil Subscribers 757 | var sub *nats.Subscription 758 | if sub.Type() != nats.NilSubscription { 759 | t.Fatalf("Got wrong type for nil subscription, %v\n", sub.Type()) 760 | } 761 | if sub.IsValid() { 762 | t.Fatalf("Expected IsValid() to return false") 763 | } 764 | if err := sub.Unsubscribe(); err == nil || err != nats.ErrBadSubscription { 765 | t.Fatalf("Expected Unsubscribe to return proper error, got %v\n", err) 766 | } 767 | if err := sub.AutoUnsubscribe(1); err == nil || err != nats.ErrBadSubscription { 768 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 769 | } 770 | if _, err := sub.NextMsg(time.Millisecond); err == nil || err != nats.ErrBadSubscription { 771 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 772 | } 773 | if _, err := sub.QueuedMsgs(); err == nil || err != nats.ErrBadSubscription { 774 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 775 | } 776 | if _, _, err := sub.Pending(); err == nil || err != nats.ErrBadSubscription { 777 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 778 | } 779 | if _, _, err := sub.MaxPending(); err == nil || err != nats.ErrBadSubscription { 780 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 781 | } 782 | if err := sub.ClearMaxPending(); err == nil || err != nats.ErrBadSubscription { 783 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 784 | } 785 | if _, _, err := sub.PendingLimits(); err == nil || err != nats.ErrBadSubscription { 786 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 787 | } 788 | if err := sub.SetPendingLimits(1, 1); err == nil || err != nats.ErrBadSubscription { 789 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 790 | } 791 | if _, err := sub.Delivered(); err == nil || err != nats.ErrBadSubscription { 792 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 793 | } 794 | if _, err := sub.Dropped(); err == nil || err != nats.ErrBadSubscription { 795 | t.Fatalf("Expected ErrBadSubscription error, got %v\n", err) 796 | } 797 | } 798 | --------------------------------------------------------------------------------