mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
12
Dockerfile
12
Dockerfile
@@ -1,12 +0,0 @@
|
||||
FROM golang:1.12.9
|
||||
|
||||
MAINTAINER Ivan Kozlovic <ivan@synadia.com>
|
||||
|
||||
COPY . /go/src/github.com/nats-io/nats-server
|
||||
WORKDIR /go/src/github.com/nats-io/nats-server
|
||||
|
||||
RUN CGO_ENABLED=0 GO111MODULE=off go install -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`"
|
||||
|
||||
EXPOSE 4222 8222
|
||||
ENTRYPOINT ["nats-server"]
|
||||
CMD ["--help"]
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM golang:1.12.9
|
||||
|
||||
MAINTAINER Ivan Kozlovic <ivan@synadia.com>
|
||||
|
||||
COPY . /go/src/github.com/nats-io/nats-server
|
||||
WORKDIR /go/src/github.com/nats-io/nats-server
|
||||
|
||||
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=amd64 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-amd64/nats-server
|
||||
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=arm GOARM=6 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-arm6/nats-server
|
||||
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=arm GOARM=7 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-arm7/nats-server
|
||||
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=linux GOARCH=arm64 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/linux-arm64/nats-server
|
||||
RUN CGO_ENABLED=0 GO111MODULE=off GOOS=windows GOARCH=amd64 go build -v -a -tags netgo -installsuffix netgo -ldflags "-s -w -X github.com/nats-io/nats-server/server.gitCommit=`git rev-parse --short HEAD`" -o pkg/win-amd64/nats-server.exe
|
||||
|
||||
ENTRYPOINT ["go"]
|
||||
CMD ["version"]
|
||||
@@ -29,8 +29,8 @@ If you are interested in contributing to NATS, read about our...
|
||||
[Fossa-Image]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fgnatsd.svg?type=shield
|
||||
[Build-Status-Url]: https://travis-ci.org/nats-io/nats-server
|
||||
[Build-Status-Image]: https://travis-ci.org/nats-io/nats-server.svg?branch=master
|
||||
[Release-Url]: https://github.com/nats-io/nats-server/releases/tag/v2.1.0
|
||||
[Release-image]: https://img.shields.io/badge/release-v2.1.0-1eb0fc.svg
|
||||
[Release-Url]: https://github.com/nats-io/nats-server/releases/tag/v2.1.2
|
||||
[Release-image]: https://img.shields.io/badge/release-v2.1.2-1eb0fc.svg
|
||||
[Coverage-Url]: https://coveralls.io/r/nats-io/nats-server?branch=master
|
||||
[Coverage-image]: https://coveralls.io/repos/github/nats-io/nats-server/badge.svg?branch=master
|
||||
[ReportCard-Url]: https://goreportcard.com/report/nats-io/nats-server
|
||||
|
||||
6
go.mod
6
go.mod
@@ -2,9 +2,9 @@ module github.com/nats-io/nats-server/v2
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/nats-io/jwt v0.3.0
|
||||
github.com/nats-io/nats.go v1.8.1
|
||||
github.com/nats-io/nkeys v0.1.0
|
||||
github.com/nats-io/jwt v0.3.2
|
||||
github.com/nats-io/nats.go v1.9.1
|
||||
github.com/nats-io/nkeys v0.1.3
|
||||
github.com/nats-io/nuid v1.0.1
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e
|
||||
|
||||
12
go.sum
12
go.sum
@@ -1,15 +1,15 @@
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ=
|
||||
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
|
||||
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
|
||||
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
|
||||
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
||||
@@ -40,7 +40,7 @@ var (
|
||||
|
||||
const (
|
||||
// VERSION is the current version for the server.
|
||||
VERSION = "2.1.1-RC6"
|
||||
VERSION = "2.1.2"
|
||||
|
||||
// PROTO is the currently supported protocol.
|
||||
// 0 was the original
|
||||
|
||||
5
vendor/github.com/nats-io/jwt/.travis.yml
generated
vendored
5
vendor/github.com/nats-io/jwt/.travis.yml
generated
vendored
@@ -1,8 +1,8 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- 1.12.x
|
||||
- 1.11.x
|
||||
|
||||
install:
|
||||
- go get -t ./...
|
||||
@@ -18,4 +18,5 @@ before_script:
|
||||
- staticcheck ./...
|
||||
|
||||
script:
|
||||
- if [[ "$TRAVIS_GO_VERSION" == 1.10.* ]] ; then ./scripts/cov.sh TRAVIS; else go test -v -race ./...; fi
|
||||
- go test -v -race ./...
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; fi
|
||||
|
||||
4
vendor/github.com/nats-io/jwt/account_claims.go
generated
vendored
4
vendor/github.com/nats-io/jwt/account_claims.go
generated
vendored
@@ -17,6 +17,7 @@ package jwt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nkeys"
|
||||
@@ -127,7 +128,8 @@ func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) {
|
||||
if !nkeys.IsValidPublicAccountKey(a.Subject) {
|
||||
return "", errors.New("expected subject to be account public key")
|
||||
}
|
||||
|
||||
sort.Sort(a.Exports)
|
||||
sort.Sort(a.Imports)
|
||||
a.ClaimsData.Type = AccountClaim
|
||||
return a.ClaimsData.Encode(pair, a)
|
||||
}
|
||||
|
||||
3
vendor/github.com/nats-io/jwt/creds_utils.go
generated
vendored
3
vendor/github.com/nats-io/jwt/creds_utils.go
generated
vendored
@@ -23,6 +23,7 @@ func formatJwt(kind string, jwtString string) ([]byte, error) {
|
||||
templ := `-----BEGIN NATS %s JWT-----
|
||||
%s
|
||||
------END NATS %s JWT------
|
||||
|
||||
`
|
||||
w := bytes.NewBuffer(nil)
|
||||
kind = strings.ToUpper(kind)
|
||||
@@ -60,6 +61,7 @@ func DecorateSeed(seed []byte) ([]byte, error) {
|
||||
header := `************************* IMPORTANT *************************
|
||||
NKEY Seed printed below can be used to sign and prove identity.
|
||||
NKEYs are sensitive and should be treated as secrets.
|
||||
|
||||
-----BEGIN %s NKEY SEED-----
|
||||
`
|
||||
_, err := fmt.Fprintf(w, header, kind)
|
||||
@@ -70,6 +72,7 @@ NKEYs are sensitive and should be treated as secrets.
|
||||
|
||||
footer := `
|
||||
------END %s NKEY SEED------
|
||||
|
||||
*************************************************************
|
||||
`
|
||||
_, err = fmt.Fprintf(w, footer, kind)
|
||||
|
||||
14
vendor/github.com/nats-io/jwt/exports.go
generated
vendored
14
vendor/github.com/nats-io/jwt/exports.go
generated
vendored
@@ -158,7 +158,7 @@ func (e *Export) IsRevoked(pubKey string) bool {
|
||||
return e.Revocations.IsRevoked(pubKey, time.Now())
|
||||
}
|
||||
|
||||
// Exports is an array of exports
|
||||
// Exports is a slice of exports
|
||||
type Exports []*Export
|
||||
|
||||
// Add appends exports to the list
|
||||
@@ -222,3 +222,15 @@ func (e *Exports) HasExportContainingSubject(subject Subject) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Exports) Len() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
func (e Exports) Swap(i, j int) {
|
||||
e[i], e[j] = e[j], e[i]
|
||||
}
|
||||
|
||||
func (e Exports) Less(i, j int) bool {
|
||||
return e[i].Subject < e[j].Subject
|
||||
}
|
||||
|
||||
2
vendor/github.com/nats-io/jwt/go.mod
generated
vendored
2
vendor/github.com/nats-io/jwt/go.mod
generated
vendored
@@ -1,3 +1,3 @@
|
||||
module github.com/nats-io/jwt
|
||||
|
||||
require github.com/nats-io/nkeys v0.1.0
|
||||
require github.com/nats-io/nkeys v0.1.3
|
||||
|
||||
4
vendor/github.com/nats-io/jwt/go.sum
generated
vendored
4
vendor/github.com/nats-io/jwt/go.sum
generated
vendored
@@ -1,5 +1,5 @@
|
||||
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
||||
2
vendor/github.com/nats-io/jwt/header.go
generated
vendored
2
vendor/github.com/nats-io/jwt/header.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version is semantic version.
|
||||
Version = "0.3.0"
|
||||
Version = "0.3.2"
|
||||
|
||||
// TokenTypeJwt is the JWT token type supported JWT tokens
|
||||
// encoded and decoded by this library
|
||||
|
||||
21
vendor/github.com/nats-io/jwt/imports.go
generated
vendored
21
vendor/github.com/nats-io/jwt/imports.go
generated
vendored
@@ -63,10 +63,11 @@ func (i *Import) Validate(actPubKey string, vr *ValidationResults) {
|
||||
|
||||
i.Subject.Validate(vr)
|
||||
|
||||
if i.IsService() {
|
||||
if i.Subject.HasWildCards() {
|
||||
vr.AddWarning("services cannot have wildcard subject: %q", i.Subject)
|
||||
}
|
||||
if i.IsService() && i.Subject.HasWildCards() {
|
||||
vr.AddError("services cannot have wildcard subject: %q", i.Subject)
|
||||
}
|
||||
if i.IsStream() && i.To.HasWildCards() {
|
||||
vr.AddError("streams cannot have wildcard to subject: %q", i.Subject)
|
||||
}
|
||||
|
||||
var act *ActivationClaims
|
||||
@@ -136,3 +137,15 @@ func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) {
|
||||
func (i *Imports) Add(a ...*Import) {
|
||||
*i = append(*i, a...)
|
||||
}
|
||||
|
||||
func (i Imports) Len() int {
|
||||
return len(i)
|
||||
}
|
||||
|
||||
func (i Imports) Swap(j, k int) {
|
||||
i[j], i[k] = i[k], i[j]
|
||||
}
|
||||
|
||||
func (i Imports) Less(j, k int) bool {
|
||||
return i[j].Subject < i[k].Subject
|
||||
}
|
||||
|
||||
4
vendor/github.com/nats-io/nats.go/.travis.yml
generated
vendored
4
vendor/github.com/nats-io/nats.go/.travis.yml
generated
vendored
@@ -16,8 +16,8 @@ install:
|
||||
before_script:
|
||||
- $(exit $(go fmt ./... | wc -l))
|
||||
- go vet ./...
|
||||
- misspell -error -locale US .
|
||||
- find . -type f -name "*.go" | xargs misspell -error -locale US
|
||||
- staticcheck ./...
|
||||
script:
|
||||
- go test -i -race ./...
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; else go test -race ./...; fi
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; else go test -race -v -p=1 ./... --failfast; fi
|
||||
|
||||
30
vendor/github.com/nats-io/nats.go/README.md
generated
vendored
30
vendor/github.com/nats-io/nats.go/README.md
generated
vendored
@@ -15,6 +15,20 @@ go get github.com/nats-io/nats.go/
|
||||
go get github.com/nats-io/nats-server
|
||||
```
|
||||
|
||||
When using or transitioning to Go modules support:
|
||||
|
||||
```bash
|
||||
# Go client latest or explicit version
|
||||
go get github.com/nats-io/nats.go/@latest
|
||||
go get github.com/nats-io/nats.go/@v1.9.1
|
||||
|
||||
# For latest NATS Server, add /v2 at the end
|
||||
go get github.com/nats-io/nats-server/v2
|
||||
|
||||
# NATS Server v1 is installed otherwise
|
||||
# go get github.com/nats-io/nats-server
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
@@ -33,7 +47,7 @@ nc.Subscribe("foo", func(m *nats.Msg) {
|
||||
|
||||
// Responding to a request message
|
||||
nc.Subscribe("request", func(m *nats.Msg) {
|
||||
m.Respond([]byte("answer is 42")
|
||||
m.Respond([]byte("answer is 42"))
|
||||
})
|
||||
|
||||
// Simple Sync Subscriber
|
||||
@@ -55,7 +69,7 @@ sub.Drain()
|
||||
msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond)
|
||||
|
||||
// Replies
|
||||
nc.Subscribe("help", func(m *Msg) {
|
||||
nc.Subscribe("help", func(m *nats.Msg) {
|
||||
nc.Publish(m.Reply, []byte("I can help!"))
|
||||
})
|
||||
|
||||
@@ -102,12 +116,12 @@ c.Publish("hello", me)
|
||||
|
||||
// Unsubscribe
|
||||
sub, err := c.Subscribe("foo", nil)
|
||||
...
|
||||
// ...
|
||||
sub.Unsubscribe()
|
||||
|
||||
// Requests
|
||||
var response string
|
||||
err := c.Request("help", "help me", &response, 10*time.Millisecond)
|
||||
err = c.Request("help", "help me", &response, 10*time.Millisecond)
|
||||
if err != nil {
|
||||
fmt.Printf("Request failed: %v\n", err)
|
||||
}
|
||||
@@ -127,7 +141,7 @@ This requires server with version >= 2.0.0
|
||||
NATS servers have a new security and authentication mechanism to authenticate with user credentials and Nkeys.
|
||||
The simplest form is to use the helper method UserCredentials(credsFilepath).
|
||||
```go
|
||||
nc, err := nats.Connect(url, UserCredentials("user.creds"))
|
||||
nc, err := nats.Connect(url, nats.UserCredentials("user.creds"))
|
||||
```
|
||||
|
||||
The helper methods creates two callback handlers to present the user JWT and sign the nonce challenge from the server.
|
||||
@@ -136,12 +150,12 @@ The helper will load and wipe and erase memory it uses for each connect or recon
|
||||
|
||||
The helper also can take two entries, one for the JWT and one for the NKey seed file.
|
||||
```go
|
||||
nc, err := nats.Connect(url, UserCredentials("user.jwt", "user.nk"))
|
||||
nc, err := nats.Connect(url, nats.UserCredentials("user.jwt", "user.nk"))
|
||||
```
|
||||
|
||||
You can also set the callback handlers directly and manage challenge signing directly.
|
||||
```go
|
||||
nc, err := nats.Connect(url, UserJWT(jwtCB, sigCB))
|
||||
nc, err := nats.Connect(url, nats.UserJWT(jwtCB, sigCB))
|
||||
```
|
||||
|
||||
Bare Nkeys are also supported. The nkey seed should be in a read only file, e.g. seed.txt
|
||||
@@ -160,7 +174,7 @@ opt, err := nats.NkeyOptionFromSeed("seed.txt")
|
||||
nc, err := nats.Connect(serverUrl, opt)
|
||||
|
||||
// Direct
|
||||
nc, err := nats.Connect(serverUrl, Nkey(pubNkey, sigCB))
|
||||
nc, err := nats.Connect(serverUrl, nats.Nkey(pubNkey, sigCB))
|
||||
```
|
||||
|
||||
## TLS
|
||||
|
||||
28
vendor/github.com/nats-io/nats.go/context.go
generated
vendored
28
vendor/github.com/nats-io/nats.go/context.go
generated
vendored
@@ -43,29 +43,7 @@ func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte
|
||||
return nc.oldRequestWithContext(ctx, subj, data)
|
||||
}
|
||||
|
||||
// Do setup for the new style.
|
||||
if nc.respMap == nil {
|
||||
nc.initNewResp()
|
||||
}
|
||||
// Create literal Inbox and map to a chan msg.
|
||||
mch := make(chan *Msg, RequestChanLen)
|
||||
respInbox := nc.newRespInbox()
|
||||
token := respToken(respInbox)
|
||||
nc.respMap[token] = mch
|
||||
createSub := nc.respMux == nil
|
||||
ginbox := nc.respSub
|
||||
nc.mu.Unlock()
|
||||
|
||||
if createSub {
|
||||
// Make sure scoped subscription is setup only once.
|
||||
var err error
|
||||
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err := nc.PublishRequest(subj, respInbox, data)
|
||||
mch, token, err := nc.createNewRequestAndSend(subj, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -140,7 +118,7 @@ func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) {
|
||||
select {
|
||||
case msg, ok = <-mch:
|
||||
if !ok {
|
||||
return nil, ErrConnectionClosed
|
||||
return nil, s.getNextMsgErr()
|
||||
}
|
||||
if err := s.processNextMsgDelivered(msg); err != nil {
|
||||
return nil, err
|
||||
@@ -153,7 +131,7 @@ func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) {
|
||||
select {
|
||||
case msg, ok = <-mch:
|
||||
if !ok {
|
||||
return nil, ErrConnectionClosed
|
||||
return nil, s.getNextMsgErr()
|
||||
}
|
||||
if err := s.processNextMsgDelivered(msg); err != nil {
|
||||
return nil, err
|
||||
|
||||
4
vendor/github.com/nats-io/nats.go/enc.go
generated
vendored
4
vendor/github.com/nats-io/nats.go/enc.go
generated
vendored
@@ -33,7 +33,7 @@ type Encoder interface {
|
||||
var encMap map[string]Encoder
|
||||
var encLock sync.Mutex
|
||||
|
||||
// Indexe names into the Registered Encoders.
|
||||
// Indexed names into the Registered Encoders.
|
||||
const (
|
||||
JSON_ENCODER = "json"
|
||||
GOB_ENCODER = "gob"
|
||||
@@ -109,7 +109,7 @@ func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error
|
||||
|
||||
// Request will create an Inbox and perform a Request() call
|
||||
// with the Inbox reply for the data v. A response will be
|
||||
// decoded into the vPtrResponse.
|
||||
// decoded into the vPtr Response.
|
||||
func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
|
||||
b, err := c.Enc.Encode(subject, v)
|
||||
if err != nil {
|
||||
|
||||
3
vendor/github.com/nats-io/nats.go/go.mod
generated
vendored
3
vendor/github.com/nats-io/nats.go/go.mod
generated
vendored
@@ -1,6 +1,7 @@
|
||||
module github.com/nats-io/nats.go
|
||||
|
||||
require (
|
||||
github.com/nats-io/nkeys v0.0.2
|
||||
github.com/nats-io/jwt v0.3.0
|
||||
github.com/nats-io/nkeys v0.1.0
|
||||
github.com/nats-io/nuid v1.0.1
|
||||
)
|
||||
|
||||
15
vendor/github.com/nats-io/nats.go/go.sum
generated
vendored
15
vendor/github.com/nats-io/nats.go/go.sum
generated
vendored
@@ -1,6 +1,13 @@
|
||||
github.com/nats-io/nkeys v0.0.2 h1:+qM7QpgXnvDDixitZtQUBDY9w/s9mu1ghS+JIbsrx6M=
|
||||
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
|
||||
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
416
vendor/github.com/nats-io/nats.go/nats.go
generated
vendored
416
vendor/github.com/nats-io/nats.go/nats.go
generated
vendored
@@ -28,7 +28,8 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/jwt"
|
||||
"github.com/nats-io/nats.go/util"
|
||||
"github.com/nats-io/nkeys"
|
||||
"github.com/nats-io/nuid"
|
||||
@@ -43,7 +45,7 @@ import (
|
||||
|
||||
// Default Constants
|
||||
const (
|
||||
Version = "1.8.1"
|
||||
Version = "1.9.1"
|
||||
DefaultURL = "nats://127.0.0.1:4222"
|
||||
DefaultPort = 4222
|
||||
DefaultMaxReconnect = 60
|
||||
@@ -67,6 +69,9 @@ const (
|
||||
|
||||
// AUTHORIZATION_ERR is for when nats server user authorization has failed.
|
||||
AUTHORIZATION_ERR = "authorization violation"
|
||||
|
||||
// AUTHENTICATION_EXPIRED_ERR is for when nats server user authorization has expired.
|
||||
AUTHENTICATION_EXPIRED_ERR = "user authentication expired"
|
||||
)
|
||||
|
||||
// Errors
|
||||
@@ -80,10 +85,12 @@ var (
|
||||
ErrBadSubscription = errors.New("nats: invalid subscription")
|
||||
ErrTypeSubscription = errors.New("nats: invalid subscription type")
|
||||
ErrBadSubject = errors.New("nats: invalid subject")
|
||||
ErrBadQueueName = errors.New("nats: invalid queue name")
|
||||
ErrSlowConsumer = errors.New("nats: slow consumer, messages dropped")
|
||||
ErrTimeout = errors.New("nats: timeout")
|
||||
ErrBadTimeout = errors.New("nats: timeout invalid")
|
||||
ErrAuthorization = errors.New("nats: authorization violation")
|
||||
ErrAuthExpired = errors.New("nats: authentication expired")
|
||||
ErrNoServers = errors.New("nats: no servers available for connection")
|
||||
ErrJsonParse = errors.New("nats: connect message, json parse error")
|
||||
ErrChanArg = errors.New("nats: argument needs to be a channel type")
|
||||
@@ -166,7 +173,8 @@ type UserJWTHandler func() (string, error)
|
||||
|
||||
// SignatureHandler is used to sign a nonce from the server while
|
||||
// authenticating with nkeys. The user should sign the nonce and
|
||||
// return the base64 encoded signature.
|
||||
// return the raw signature. The client will base64 encode this to
|
||||
// send to the server.
|
||||
type SignatureHandler func([]byte) ([]byte, error)
|
||||
|
||||
// AuthTokenHandler is used to generate a new token.
|
||||
@@ -339,6 +347,11 @@ type Options struct {
|
||||
// UseOldRequestStyle forces the old method of Requests that utilize
|
||||
// a new Inbox and a new Subscription for each request.
|
||||
UseOldRequestStyle bool
|
||||
|
||||
// NoCallbacksAfterClientClose allows preventing the invocation of
|
||||
// callbacks after Close() is called. Client won't receive notifications
|
||||
// when Close is invoked by user code. Default is to invoke the callbacks.
|
||||
NoCallbacksAfterClientClose bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -363,6 +376,7 @@ const (
|
||||
|
||||
// A Conn represents a bare connection to a nats-server.
|
||||
// It can send and receive []byte payloads.
|
||||
// The connection is safe to use in multiple Go routines concurrently.
|
||||
type Conn struct {
|
||||
// Keep all members for which we use atomic at the beginning of the
|
||||
// struct and make sure they are all 64bits (or use padding if necessary).
|
||||
@@ -394,13 +408,15 @@ type Conn struct {
|
||||
ps *parseState
|
||||
ptmr *time.Timer
|
||||
pout int
|
||||
ar bool // abort reconnect
|
||||
|
||||
// New style response handler
|
||||
respSub string // The wildcard subject
|
||||
respScanf string // The scanf template to extract mux token
|
||||
respMux *Subscription // A single response subscription
|
||||
respMap map[string]chan *Msg // Request map for the response msg channels
|
||||
respSetup sync.Once // Ensures response subscription occurs once
|
||||
respRand *rand.Rand // Used for generating suffix.
|
||||
respRand *rand.Rand // Used for generating suffix
|
||||
}
|
||||
|
||||
// A Subscription represents interest in a given subject.
|
||||
@@ -475,6 +491,7 @@ type srv struct {
|
||||
didConnect bool
|
||||
reconnects int
|
||||
lastAttempt time.Time
|
||||
lastErr error
|
||||
isImplicit bool
|
||||
tlsName string
|
||||
}
|
||||
@@ -872,6 +889,16 @@ func UseOldRequestStyle() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// NoCallbacksAfterClientClose is an Option to disable callbacks when user code
|
||||
// calls Close(). If close is initiated by any other condition, callbacks
|
||||
// if any will be invoked.
|
||||
func NoCallbacksAfterClientClose() Option {
|
||||
return func(o *Options) error {
|
||||
o.NoCallbacksAfterClientClose = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Handler processing
|
||||
|
||||
// SetDisconnectHandler will set the disconnect event handler.
|
||||
@@ -1445,6 +1472,7 @@ func (nc *Conn) connect() error {
|
||||
if err == nil {
|
||||
nc.srvPool[i].didConnect = true
|
||||
nc.srvPool[i].reconnects = 0
|
||||
nc.current.lastErr = nil
|
||||
returnedErr = nil
|
||||
break
|
||||
} else {
|
||||
@@ -1614,7 +1642,6 @@ func normalizeErr(line string) string {
|
||||
// applicable. Will wait for a flush to return from the server for error
|
||||
// processing.
|
||||
func (nc *Conn) sendConnect() error {
|
||||
|
||||
// Construct the CONNECT protocol string
|
||||
cProto, err := nc.connectProto()
|
||||
if err != nil {
|
||||
@@ -1668,6 +1695,17 @@ func (nc *Conn) sendConnect() error {
|
||||
if strings.HasPrefix(proto, _ERR_OP_) {
|
||||
// Remove -ERR, trim spaces and quotes, and convert to lower case.
|
||||
proto = normalizeErr(proto)
|
||||
|
||||
// Check if this is an auth error
|
||||
if authErr := checkAuthError(strings.ToLower(proto)); authErr != nil {
|
||||
// This will schedule an async error if we are in reconnect,
|
||||
// and keep track of the auth error for the current server.
|
||||
// If we have got the same error twice, this sets nc.ar to true to
|
||||
// indicate that the reconnect should be aborted (will be checked
|
||||
// in doReconnect()).
|
||||
nc.processAuthError(authErr)
|
||||
}
|
||||
|
||||
return errors.New("nats: " + proto)
|
||||
}
|
||||
|
||||
@@ -1841,6 +1879,11 @@ func (nc *Conn) doReconnect(err error) {
|
||||
|
||||
// Process connect logic
|
||||
if nc.err = nc.processConnectInit(); nc.err != nil {
|
||||
// Check if we should abort reconnect. If so, break out
|
||||
// of the loop and connection will be closed.
|
||||
if nc.ar {
|
||||
break
|
||||
}
|
||||
nc.status = RECONNECTING
|
||||
// Reset the buffered writer to the pending buffer
|
||||
// (was set to a buffered writer on nc.conn in createConn)
|
||||
@@ -1848,6 +1891,10 @@ func (nc *Conn) doReconnect(err error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Clear possible lastErr under the connection lock after
|
||||
// a successful processConnectInit().
|
||||
nc.current.lastErr = nil
|
||||
|
||||
// Clear out server stats for the server we connected to..
|
||||
cur.didConnect = true
|
||||
cur.reconnects = 0
|
||||
@@ -1883,6 +1930,7 @@ func (nc *Conn) doReconnect(err error) {
|
||||
if nc.Opts.ReconnectedCB != nil {
|
||||
nc.ach.push(func() { nc.Opts.ReconnectedCB(nc) })
|
||||
}
|
||||
|
||||
// Release lock here, we will return below.
|
||||
nc.mu.Unlock()
|
||||
|
||||
@@ -1897,7 +1945,7 @@ func (nc *Conn) doReconnect(err error) {
|
||||
nc.err = ErrNoServers
|
||||
}
|
||||
nc.mu.Unlock()
|
||||
nc.Close()
|
||||
nc.close(CLOSED, true, nil)
|
||||
}
|
||||
|
||||
// processOpErr handles errors from reading or parsing the protocol.
|
||||
@@ -1932,7 +1980,7 @@ func (nc *Conn) processOpErr(err error) {
|
||||
nc.status = DISCONNECTED
|
||||
nc.err = err
|
||||
nc.mu.Unlock()
|
||||
nc.Close()
|
||||
nc.close(CLOSED, true, nil)
|
||||
}
|
||||
|
||||
// dispatch is responsible for calling any async callbacks
|
||||
@@ -2132,8 +2180,8 @@ func (nc *Conn) processMsg(data []byte) {
|
||||
nc.subsMu.RLock()
|
||||
|
||||
// Stats
|
||||
nc.InMsgs++
|
||||
nc.InBytes += uint64(len(data))
|
||||
atomic.AddUint64(&nc.InMsgs, 1)
|
||||
atomic.AddUint64(&nc.InBytes, uint64(len(data)))
|
||||
|
||||
sub := nc.subs[nc.ps.ma.sid]
|
||||
if sub == nil {
|
||||
@@ -2239,15 +2287,24 @@ func (nc *Conn) processPermissionsViolation(err string) {
|
||||
nc.mu.Unlock()
|
||||
}
|
||||
|
||||
// processAuthorizationViolation is called when the server signals a user
|
||||
// authorization violation.
|
||||
func (nc *Conn) processAuthorizationViolation(err string) {
|
||||
nc.mu.Lock()
|
||||
nc.err = ErrAuthorization
|
||||
if nc.Opts.AsyncErrorCB != nil {
|
||||
nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, ErrAuthorization) })
|
||||
// processAuthError generally processing for auth errors. We want to do retries
|
||||
// unless we get the same error again. This allows us for instance to swap credentials
|
||||
// and have the app reconnect, but if nothing is changing we should bail.
|
||||
// This function will return true if the connection should be closed, false otherwise.
|
||||
// Connection lock is held on entry
|
||||
func (nc *Conn) processAuthError(err error) bool {
|
||||
nc.err = err
|
||||
if !nc.initc && nc.Opts.AsyncErrorCB != nil {
|
||||
nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, err) })
|
||||
}
|
||||
nc.mu.Unlock()
|
||||
// We should give up if we tried twice on this server and got the
|
||||
// same error.
|
||||
if nc.current.lastErr == err {
|
||||
nc.ar = true
|
||||
} else {
|
||||
nc.current.lastErr = err
|
||||
}
|
||||
return nc.ar
|
||||
}
|
||||
|
||||
// flusher is a separate Go routine that will process flush requests for the write
|
||||
@@ -2417,6 +2474,18 @@ func (nc *Conn) LastError() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the given error string is an auth error, and if so returns
|
||||
// the corresponding ErrXXX error, nil otherwise
|
||||
func checkAuthError(e string) error {
|
||||
if strings.HasPrefix(e, AUTHORIZATION_ERR) {
|
||||
return ErrAuthorization
|
||||
}
|
||||
if strings.HasPrefix(e, AUTHENTICATION_EXPIRED_ERR) {
|
||||
return ErrAuthExpired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// processErr processes any error messages from the server and
|
||||
// sets the connection's lastError.
|
||||
func (nc *Conn) processErr(ie string) {
|
||||
@@ -2425,18 +2494,25 @@ func (nc *Conn) processErr(ie string) {
|
||||
// convert to lower case.
|
||||
e := strings.ToLower(ne)
|
||||
|
||||
close := false
|
||||
|
||||
// FIXME(dlc) - process Slow Consumer signals special.
|
||||
if e == STALE_CONNECTION {
|
||||
nc.processOpErr(ErrStaleConnection)
|
||||
} else if strings.HasPrefix(e, PERMISSIONS_ERR) {
|
||||
nc.processPermissionsViolation(ne)
|
||||
} else if strings.HasPrefix(e, AUTHORIZATION_ERR) {
|
||||
nc.processAuthorizationViolation(ne)
|
||||
} else if authErr := checkAuthError(e); authErr != nil {
|
||||
nc.mu.Lock()
|
||||
close = nc.processAuthError(authErr)
|
||||
nc.mu.Unlock()
|
||||
} else {
|
||||
close = true
|
||||
nc.mu.Lock()
|
||||
nc.err = errors.New("nats: " + ne)
|
||||
nc.mu.Unlock()
|
||||
nc.Close()
|
||||
}
|
||||
if close {
|
||||
nc.close(CLOSED, true, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2572,21 +2648,32 @@ func (nc *Conn) publish(subj, reply string, data []byte) error {
|
||||
// the appropriate channel based on the last token and place
|
||||
// the message on the channel if possible.
|
||||
func (nc *Conn) respHandler(m *Msg) {
|
||||
rt := respToken(m.Subject)
|
||||
|
||||
nc.mu.Lock()
|
||||
|
||||
// Just return if closed.
|
||||
if nc.isClosed() {
|
||||
nc.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var mch chan *Msg
|
||||
|
||||
// Grab mch
|
||||
mch := nc.respMap[rt]
|
||||
// Delete the key regardless, one response only.
|
||||
// FIXME(dlc) - should we track responses past 1
|
||||
// just statistics wise?
|
||||
delete(nc.respMap, rt)
|
||||
rt := nc.respToken(m.Subject)
|
||||
if rt != _EMPTY_ {
|
||||
mch = nc.respMap[rt]
|
||||
// Delete the key regardless, one response only.
|
||||
delete(nc.respMap, rt)
|
||||
} else if len(nc.respMap) == 1 {
|
||||
// If the server has rewritten the subject, the response token (rt)
|
||||
// will not match (could be the case with JetStream). If that is the
|
||||
// case and there is a single entry, use that.
|
||||
for k, v := range nc.respMap {
|
||||
mch = v
|
||||
delete(nc.respMap, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
nc.mu.Unlock()
|
||||
|
||||
// Don't block, let Request timeout instead, mch is
|
||||
@@ -2610,11 +2697,43 @@ func (nc *Conn) createRespMux(respSub string) error {
|
||||
return err
|
||||
}
|
||||
nc.mu.Lock()
|
||||
nc.respScanf = strings.Replace(respSub, "*", "%s", -1)
|
||||
nc.respMux = s
|
||||
nc.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to setup and send new request style requests. Return the chan to receive the response.
|
||||
func (nc *Conn) createNewRequestAndSend(subj string, data []byte) (chan *Msg, string, error) {
|
||||
// Do setup for the new style if needed.
|
||||
if nc.respMap == nil {
|
||||
nc.initNewResp()
|
||||
}
|
||||
// Create new literal Inbox and map to a chan msg.
|
||||
mch := make(chan *Msg, RequestChanLen)
|
||||
respInbox := nc.newRespInbox()
|
||||
token := respInbox[respInboxPrefixLen:]
|
||||
nc.respMap[token] = mch
|
||||
createSub := nc.respMux == nil
|
||||
ginbox := nc.respSub
|
||||
nc.mu.Unlock()
|
||||
|
||||
if createSub {
|
||||
// Make sure scoped subscription is setup only once.
|
||||
var err error
|
||||
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
|
||||
if err != nil {
|
||||
return nil, token, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := nc.PublishRequest(subj, respInbox, data); err != nil {
|
||||
return nil, token, err
|
||||
}
|
||||
|
||||
return mch, token, nil
|
||||
}
|
||||
|
||||
// Request will send a request payload and deliver the response message,
|
||||
// or an error, including a timeout if no message was received properly.
|
||||
func (nc *Conn) Request(subj string, data []byte, timeout time.Duration) (*Msg, error) {
|
||||
@@ -2629,29 +2748,8 @@ func (nc *Conn) Request(subj string, data []byte, timeout time.Duration) (*Msg,
|
||||
return nc.oldRequest(subj, data, timeout)
|
||||
}
|
||||
|
||||
// Do setup for the new style.
|
||||
if nc.respMap == nil {
|
||||
nc.initNewResp()
|
||||
}
|
||||
// Create literal Inbox and map to a chan msg.
|
||||
mch := make(chan *Msg, RequestChanLen)
|
||||
respInbox := nc.newRespInbox()
|
||||
token := respToken(respInbox)
|
||||
nc.respMap[token] = mch
|
||||
createSub := nc.respMux == nil
|
||||
ginbox := nc.respSub
|
||||
nc.mu.Unlock()
|
||||
|
||||
if createSub {
|
||||
// Make sure scoped subscription is setup only once.
|
||||
var err error
|
||||
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := nc.PublishRequest(subj, respInbox, data); err != nil {
|
||||
mch, token, err := nc.createNewRequestAndSend(subj, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2754,9 +2852,16 @@ func (nc *Conn) NewRespInbox() string {
|
||||
}
|
||||
|
||||
// respToken will return the last token of a literal response inbox
|
||||
// which we use for the message channel lookup.
|
||||
func respToken(respInbox string) string {
|
||||
return respInbox[respInboxPrefixLen:]
|
||||
// which we use for the message channel lookup. This needs to do a
|
||||
// scan to protect itself against the server changing the subject.
|
||||
// Lock should be held.
|
||||
func (nc *Conn) respToken(respInbox string) string {
|
||||
var token string
|
||||
n, err := fmt.Sscanf(respInbox, nc.respScanf, &token)
|
||||
if err != nil || n != 1 {
|
||||
return ""
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// Subscribe will express interest in the given subject. The subject
|
||||
@@ -2822,11 +2927,37 @@ func (nc *Conn) QueueSubscribeSyncWithChan(subj, queue string, ch chan *Msg) (*S
|
||||
return nc.subscribe(subj, queue, nil, ch, false)
|
||||
}
|
||||
|
||||
// badSubject will do quick test on whether a subject is acceptable.
|
||||
// Spaces are not allowed and all tokens should be > 0 in len.
|
||||
func badSubject(subj string) bool {
|
||||
if strings.ContainsAny(subj, " \t\r\n") {
|
||||
return true
|
||||
}
|
||||
tokens := strings.Split(subj, ".")
|
||||
for _, t := range tokens {
|
||||
if len(t) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// badQueue will check a queue name for whitespace.
|
||||
func badQueue(qname string) bool {
|
||||
return strings.ContainsAny(qname, " \t\r\n")
|
||||
}
|
||||
|
||||
// subscribe is the internal subscribe function that indicates interest in a subject.
|
||||
func (nc *Conn) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync bool) (*Subscription, error) {
|
||||
if nc == nil {
|
||||
return nil, ErrInvalidConnection
|
||||
}
|
||||
if badSubject(subj) {
|
||||
return nil, ErrBadSubject
|
||||
}
|
||||
if queue != "" && badQueue(queue) {
|
||||
return nil, ErrBadQueueName
|
||||
}
|
||||
nc.mu.Lock()
|
||||
// ok here, but defer is generally expensive
|
||||
defer nc.mu.Unlock()
|
||||
@@ -2902,7 +3033,6 @@ func (nc *Conn) removeSub(s *Subscription) {
|
||||
s.mch = nil
|
||||
|
||||
// Mark as invalid
|
||||
s.conn = nil
|
||||
s.closed = true
|
||||
if s.pCond != nil {
|
||||
s.pCond.Broadcast()
|
||||
@@ -2939,7 +3069,7 @@ func (s *Subscription) IsValid() bool {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.conn != nil
|
||||
return s.conn != nil && !s.closed
|
||||
}
|
||||
|
||||
// Drain will remove interest but continue callbacks until all messages
|
||||
@@ -2964,8 +3094,12 @@ func (s *Subscription) Unsubscribe() error {
|
||||
}
|
||||
s.mu.Lock()
|
||||
conn := s.conn
|
||||
closed := s.closed
|
||||
s.mu.Unlock()
|
||||
if conn == nil {
|
||||
if conn == nil || conn.IsClosed() {
|
||||
return ErrConnectionClosed
|
||||
}
|
||||
if closed {
|
||||
return ErrBadSubscription
|
||||
}
|
||||
if conn.IsDraining() {
|
||||
@@ -3021,8 +3155,9 @@ func (s *Subscription) AutoUnsubscribe(max int) error {
|
||||
}
|
||||
s.mu.Lock()
|
||||
conn := s.conn
|
||||
closed := s.closed
|
||||
s.mu.Unlock()
|
||||
if conn == nil {
|
||||
if conn == nil || closed {
|
||||
return ErrBadSubscription
|
||||
}
|
||||
return conn.unsubscribe(s, max, false)
|
||||
@@ -3069,8 +3204,8 @@ func (nc *Conn) unsubscribe(sub *Subscription, max int, drainMode bool) error {
|
||||
}
|
||||
|
||||
// NextMsg will return the next message available to a synchronous subscriber
|
||||
// or block until one is available. A timeout can be used to return when no
|
||||
// message has been delivered.
|
||||
// or block until one is available. An error is returned if the subscription is invalid (ErrBadSubscription),
|
||||
// the connection is closed (ErrConnectionClosed), or the timeout is reached (ErrTimeout).
|
||||
func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) {
|
||||
if s == nil {
|
||||
return nil, ErrBadSubscription
|
||||
@@ -3094,7 +3229,7 @@ func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) {
|
||||
select {
|
||||
case msg, ok = <-mch:
|
||||
if !ok {
|
||||
return nil, ErrConnectionClosed
|
||||
return nil, s.getNextMsgErr()
|
||||
}
|
||||
if err := s.processNextMsgDelivered(msg); err != nil {
|
||||
return nil, err
|
||||
@@ -3113,7 +3248,7 @@ func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) {
|
||||
select {
|
||||
case msg, ok = <-mch:
|
||||
if !ok {
|
||||
return nil, ErrConnectionClosed
|
||||
return nil, s.getNextMsgErr()
|
||||
}
|
||||
if err := s.processNextMsgDelivered(msg); err != nil {
|
||||
return nil, err
|
||||
@@ -3150,6 +3285,18 @@ func (s *Subscription) validateNextMsgState() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is called when the sync channel has been closed.
|
||||
// The error returned will be either connection or subscription
|
||||
// closed depending on what caused NextMsg() to fail.
|
||||
func (s *Subscription) getNextMsgErr() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.connClosed {
|
||||
return ErrConnectionClosed
|
||||
}
|
||||
return ErrBadSubscription
|
||||
}
|
||||
|
||||
// processNextMsgDelivered takes a message and applies the needed
|
||||
// accounting to the stats from the subscription, returning an
|
||||
// error in case we have the maximum number of messages have been
|
||||
@@ -3197,7 +3344,7 @@ func (s *Subscription) Pending() (int, int, error) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return -1, -1, ErrBadSubscription
|
||||
}
|
||||
if s.typ == ChanSubscription {
|
||||
@@ -3213,7 +3360,7 @@ func (s *Subscription) MaxPending() (int, int, error) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return -1, -1, ErrBadSubscription
|
||||
}
|
||||
if s.typ == ChanSubscription {
|
||||
@@ -3229,7 +3376,7 @@ func (s *Subscription) ClearMaxPending() error {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return ErrBadSubscription
|
||||
}
|
||||
if s.typ == ChanSubscription {
|
||||
@@ -3254,7 +3401,7 @@ func (s *Subscription) PendingLimits() (int, int, error) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return -1, -1, ErrBadSubscription
|
||||
}
|
||||
if s.typ == ChanSubscription {
|
||||
@@ -3271,7 +3418,7 @@ func (s *Subscription) SetPendingLimits(msgLimit, bytesLimit int) error {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return ErrBadSubscription
|
||||
}
|
||||
if s.typ == ChanSubscription {
|
||||
@@ -3291,7 +3438,7 @@ func (s *Subscription) Delivered() (int64, error) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return -1, ErrBadSubscription
|
||||
}
|
||||
return int64(s.delivered), nil
|
||||
@@ -3307,7 +3454,7 @@ func (s *Subscription) Dropped() (int, error) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.conn == nil {
|
||||
if s.conn == nil || s.closed {
|
||||
return -1, ErrBadSubscription
|
||||
}
|
||||
return s.dropped, nil
|
||||
@@ -3529,8 +3676,14 @@ func (nc *Conn) close(status Status, doCBs bool, err error) {
|
||||
nc.stopPingTimer()
|
||||
nc.ptmr = nil
|
||||
|
||||
// Go ahead and make sure we have flushed the outbound
|
||||
if nc.conn != nil {
|
||||
// Need to close and set tcp conn to nil if reconnect loop has stopped,
|
||||
// otherwise we would incorrectly invoke Disconnect handler (if set)
|
||||
// down below.
|
||||
if nc.ar && nc.conn != nil {
|
||||
nc.conn.Close()
|
||||
nc.conn = nil
|
||||
} else if nc.conn != nil {
|
||||
// Go ahead and make sure we have flushed the outbound
|
||||
nc.bw.Flush()
|
||||
defer nc.conn.Close()
|
||||
}
|
||||
@@ -3583,7 +3736,7 @@ func (nc *Conn) close(status Status, doCBs bool, err error) {
|
||||
// all blocking calls, such as Flush() and NextMsg()
|
||||
func (nc *Conn) Close() {
|
||||
if nc != nil {
|
||||
nc.close(CLOSED, true, nil)
|
||||
nc.close(CLOSED, !nc.Opts.NoCallbacksAfterClientClose, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3662,12 +3815,12 @@ func (nc *Conn) drainConnection() {
|
||||
err := nc.Flush()
|
||||
if err != nil {
|
||||
pushErr(err)
|
||||
nc.Close()
|
||||
nc.close(CLOSED, true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Move to closed state.
|
||||
nc.Close()
|
||||
nc.close(CLOSED, true, nil)
|
||||
}
|
||||
|
||||
// Drain will put a connection into a drain state. All subscriptions will
|
||||
@@ -3773,18 +3926,16 @@ func (nc *Conn) isDrainingPubs() bool {
|
||||
|
||||
// Stats will return a race safe copy of the Statistics section for the connection.
|
||||
func (nc *Conn) Stats() Statistics {
|
||||
// Stats are updated either under connection's mu or subsMu mutexes.
|
||||
// Lock both to safely get them.
|
||||
// Stats are updated either under connection's mu or with atomic operations
|
||||
// for inbound stats in processMsg().
|
||||
nc.mu.Lock()
|
||||
nc.subsMu.RLock()
|
||||
stats := Statistics{
|
||||
InMsgs: nc.InMsgs,
|
||||
InBytes: nc.InBytes,
|
||||
InMsgs: atomic.LoadUint64(&nc.InMsgs),
|
||||
InBytes: atomic.LoadUint64(&nc.InBytes),
|
||||
OutMsgs: nc.OutMsgs,
|
||||
OutBytes: nc.OutBytes,
|
||||
Reconnects: nc.Reconnects,
|
||||
}
|
||||
nc.subsMu.RUnlock()
|
||||
nc.mu.Unlock()
|
||||
return stats
|
||||
}
|
||||
@@ -3902,70 +4053,74 @@ func NkeyOptionFromSeed(seedFile string) (Option, error) {
|
||||
return Nkey(string(pub), sigCB), nil
|
||||
}
|
||||
|
||||
// This is a regex to match decorated jwts in keys/seeds.
|
||||
// .e.g.
|
||||
// -----BEGIN NATS USER JWT-----
|
||||
// eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5...
|
||||
// ------END NATS USER JWT------
|
||||
//
|
||||
// ************************* IMPORTANT *************************
|
||||
// NKEY Seed printed below can be used sign and prove identity.
|
||||
// NKEYs are sensitive and should be treated as secrets.
|
||||
//
|
||||
// -----BEGIN USER NKEY SEED-----
|
||||
// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM
|
||||
// ------END USER NKEY SEED------
|
||||
|
||||
var nscDecoratedRe = regexp.MustCompile(`\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))`)
|
||||
// Just wipe slice with 'x', for clearing contents of creds or nkey seed file.
|
||||
func wipeSlice(buf []byte) {
|
||||
for i := range buf {
|
||||
buf[i] = 'x'
|
||||
}
|
||||
}
|
||||
|
||||
func userFromFile(userFile string) (string, error) {
|
||||
contents, err := ioutil.ReadFile(userFile)
|
||||
path, err := expandPath(userFile)
|
||||
if err != nil {
|
||||
return _EMPTY_, fmt.Errorf("nats: %v", err)
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return _EMPTY_, fmt.Errorf("nats: %v", err)
|
||||
}
|
||||
defer wipeSlice(contents)
|
||||
return jwt.ParseDecoratedJWT(contents)
|
||||
}
|
||||
|
||||
items := nscDecoratedRe.FindAllSubmatch(contents, -1)
|
||||
if len(items) == 0 {
|
||||
return string(contents), nil
|
||||
func homeDir() (string, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH")
|
||||
userProfile := os.Getenv("USERPROFILE")
|
||||
|
||||
var home string
|
||||
if homeDrive == "" || homePath == "" {
|
||||
if userProfile == "" {
|
||||
return _EMPTY_, errors.New("nats: failed to get home dir, require %HOMEDRIVE% and %HOMEPATH% or %USERPROFILE%")
|
||||
}
|
||||
home = userProfile
|
||||
} else {
|
||||
home = filepath.Join(homeDrive, homePath)
|
||||
}
|
||||
|
||||
return home, nil
|
||||
}
|
||||
// First result should be the user JWT.
|
||||
// We copy here so that if the file contained a seed file too we wipe appropriately.
|
||||
raw := items[0][1]
|
||||
tmp := make([]byte, len(raw))
|
||||
copy(tmp, raw)
|
||||
return string(tmp), nil
|
||||
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
return _EMPTY_, errors.New("nats: failed to get home dir, require $HOME")
|
||||
}
|
||||
return home, nil
|
||||
}
|
||||
|
||||
func expandPath(p string) (string, error) {
|
||||
p = os.ExpandEnv(p)
|
||||
|
||||
if !strings.HasPrefix(p, "~") {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
home, err := homeDir()
|
||||
if err != nil {
|
||||
return _EMPTY_, err
|
||||
}
|
||||
|
||||
return filepath.Join(home, p[1:]), nil
|
||||
}
|
||||
|
||||
func nkeyPairFromSeedFile(seedFile string) (nkeys.KeyPair, error) {
|
||||
var seed []byte
|
||||
contents, err := ioutil.ReadFile(seedFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("nats: %v", err)
|
||||
}
|
||||
defer wipeSlice(contents)
|
||||
|
||||
items := nscDecoratedRe.FindAllSubmatch(contents, -1)
|
||||
if len(items) > 1 {
|
||||
seed = items[1][1]
|
||||
} else {
|
||||
lines := bytes.Split(contents, []byte("\n"))
|
||||
for _, line := range lines {
|
||||
if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) {
|
||||
seed = line
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if seed == nil {
|
||||
return nil, fmt.Errorf("nats: No nkey user seed found in %q", seedFile)
|
||||
}
|
||||
kp, err := nkeys.FromSeed(seed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kp, nil
|
||||
return jwt.ParseDecoratedNKey(contents)
|
||||
}
|
||||
|
||||
// Sign authentication challenges from the server.
|
||||
@@ -3982,13 +4137,6 @@ func sigHandler(nonce []byte, seedFile string) ([]byte, error) {
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// Just wipe slice with 'x', for clearing contents of nkey seed file.
|
||||
func wipeSlice(buf []byte) {
|
||||
for i := range buf {
|
||||
buf[i] = 'x'
|
||||
}
|
||||
}
|
||||
|
||||
type timeoutWriter struct {
|
||||
timeout time.Duration
|
||||
conn net.Conn
|
||||
|
||||
4
vendor/github.com/nats-io/nkeys/README.md
generated
vendored
4
vendor/github.com/nats-io/nkeys/README.md
generated
vendored
@@ -17,7 +17,7 @@ Ed25519 is fast and resistant to side channel attacks. Generation of a seed key
|
||||
|
||||
The NATS system will utilize Ed25519 keys, meaning that NATS systems will never store or even have access to any private keys. Authentication will utilize a random challenge response mechanism.
|
||||
|
||||
Dealing with 32 byte and 64 byte raw keys can be challenging. NKEYS is designed to formulate keys in a much friendlier fashion and references work done in cryptocurrencies, specifically [Stellar](https://www.stellar.org/). Bitcoin and others used a form of Base58 (or Base58Check) to endode raw keys. Stellar utilized a more traditonal Base32 with a CRC16 and a version or prefix byte. NKEYS utilizes a similar format where the prefix will be 1 byte for public and private keys and will be 2 bytes for seeds. The base32 encoding of these prefixes will yield friendly human readbable prefixes, e.g. '**N**' = server, '**C**' = cluster, '**O**' = operator, '**A**' = account, and '**U**' = user. '**P**' is used for private keys. For seeds, the first encoded prefix is '**S**', and the second character will be the type for the public key, e.g. "**SU**" is a seed for a user key pair, "**SA**" is a seed for an account key pair.
|
||||
Dealing with 32 byte and 64 byte raw keys can be challenging. NKEYS is designed to formulate keys in a much friendlier fashion and references work done in cryptocurrencies, specifically [Stellar](https://www.stellar.org/). Bitcoin and others used a form of Base58 (or Base58Check) to encode raw keys. Stellar utilized a more traditional Base32 with a CRC16 and a version or prefix byte. NKEYS utilizes a similar format where the prefix will be 1 byte for public and private keys and will be 2 bytes for seeds. The base32 encoding of these prefixes will yield friendly human readable prefixes, e.g. '**N**' = server, '**C**' = cluster, '**O**' = operator, '**A**' = account, and '**U**' = user. '**P**' is used for private keys. For seeds, the first encoded prefix is '**S**', and the second character will be the type for the public key, e.g. "**SU**" is a seed for a user key pair, "**SA**" is a seed for an account key pair.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -69,4 +69,4 @@ Unless otherwise noted, the NATS source files are distributed
|
||||
under the Apache Version 2.0 license found in the LICENSE file.
|
||||
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fnkeys?ref=badge_large)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fnkeys?ref=badge_large)
|
||||
|
||||
7
vendor/github.com/nats-io/nkeys/main.go
generated
vendored
7
vendor/github.com/nats-io/nkeys/main.go
generated
vendored
@@ -19,8 +19,8 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Version
|
||||
const Version = "0.1.0"
|
||||
// Version is our current version
|
||||
const Version = "0.1.3"
|
||||
|
||||
// Errors
|
||||
var (
|
||||
@@ -33,6 +33,7 @@ var (
|
||||
ErrInvalidSignature = errors.New("nkeys: signature verification failed")
|
||||
ErrCannotSign = errors.New("nkeys: can not sign, no private key available")
|
||||
ErrPublicKeyOnly = errors.New("nkeys: no seed or private key available")
|
||||
ErrIncompatibleKey = errors.New("nkeys: incompatible key")
|
||||
)
|
||||
|
||||
// KeyPair provides the central interface to nkeys.
|
||||
@@ -93,7 +94,7 @@ func FromSeed(seed []byte) (KeyPair, error) {
|
||||
return &kp{copy}, nil
|
||||
}
|
||||
|
||||
// Create a KeyPair from the raw 32 byte seed for a given type.
|
||||
// FromRawSeed will create a KeyPair from the raw 32 byte seed for a given type.
|
||||
func FromRawSeed(prefix PrefixByte, rawSeed []byte) (KeyPair, error) {
|
||||
seed, err := EncodeSeed(prefix, rawSeed)
|
||||
if err != nil {
|
||||
|
||||
24
vendor/github.com/nats-io/nkeys/strkey.go
generated
vendored
24
vendor/github.com/nats-io/nkeys/strkey.go
generated
vendored
@@ -17,7 +17,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
@@ -47,7 +46,7 @@ const (
|
||||
PrefixByteUser PrefixByte = 20 << 3 // Base32-encodes to 'U...'
|
||||
|
||||
// PrefixByteUnknown is for unknown prefixes.
|
||||
PrefixByteUknown PrefixByte = 23 << 3 // Base32-encodes to 'X...'
|
||||
PrefixByteUnknown PrefixByte = 23 << 3 // Base32-encodes to 'X...'
|
||||
)
|
||||
|
||||
// Set our encoding to not include padding '=='
|
||||
@@ -188,10 +187,11 @@ func DecodeSeed(src []byte) (PrefixByte, []byte, error) {
|
||||
return PrefixByte(b2), raw[2:], nil
|
||||
}
|
||||
|
||||
// Prefix returns PrefixBytes of its input
|
||||
func Prefix(src string) PrefixByte {
|
||||
b, err := decode([]byte(src))
|
||||
if err != nil {
|
||||
return PrefixByteUknown
|
||||
return PrefixByteUnknown
|
||||
}
|
||||
prefix := PrefixByte(b[0])
|
||||
err = checkValidPrefixByte(prefix)
|
||||
@@ -203,7 +203,7 @@ func Prefix(src string) PrefixByte {
|
||||
if PrefixByte(b1) == PrefixByteSeed {
|
||||
return PrefixByteSeed
|
||||
}
|
||||
return PrefixByteUknown
|
||||
return PrefixByteUnknown
|
||||
}
|
||||
|
||||
// IsValidPublicKey will decode and verify that the string is a valid encoded public key.
|
||||
@@ -288,3 +288,19 @@ func (p PrefixByte) String() string {
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// CompatibleKeyPair returns an error if the KeyPair doesn't match expected PrefixByte(s)
|
||||
func CompatibleKeyPair(kp KeyPair, expected ...PrefixByte) error {
|
||||
pk, err := kp.PublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkType := Prefix(pk)
|
||||
for _, k := range expected {
|
||||
if pkType == k {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrIncompatibleKey
|
||||
}
|
||||
|
||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -1,10 +1,10 @@
|
||||
# github.com/nats-io/jwt v0.3.0
|
||||
# github.com/nats-io/jwt v0.3.2
|
||||
github.com/nats-io/jwt
|
||||
# github.com/nats-io/nats.go v1.8.1
|
||||
# github.com/nats-io/nats.go v1.9.1
|
||||
github.com/nats-io/nats.go
|
||||
github.com/nats-io/nats.go/encoders/builtin
|
||||
github.com/nats-io/nats.go/util
|
||||
# github.com/nats-io/nkeys v0.1.0
|
||||
# github.com/nats-io/nkeys v0.1.3
|
||||
github.com/nats-io/nkeys
|
||||
# github.com/nats-io/nuid v1.0.1
|
||||
github.com/nats-io/nuid
|
||||
|
||||
Reference in New Issue
Block a user