Skip to content

Connect to a cluster somebody else runs

Most services should do this, and at v0.1.0 it is the only way to reach a broker on the network — this module will not build one.

cli, err := client.Register(ctx, "nats-client", controller, gonats.ClientSettings{
    URL:  "tls://nats.internal:4222",
    Name: "my-service",
}, credential)

No server import, no client.InProcess, and everything that publishes or subscribes is unchanged from the in-process case.

The credential

The argument after the settings is a gonats.CredentialSource — a function returning the token, resolved when the connection is made rather than carried around as a string.

func credential(p *props.Props) gonats.CredentialSource {
    return func(ctx context.Context) (string, error) {
        return vcs.SomeEstateLadder(ctx)   // env var name, keychain, literal
    }
}

This module deliberately does not compose that ladder. Precedence is the application's decision, and a library that decides it is a second statement of something the estate already states once.

A credential over nats:// is refused

A NATS token is bearer material. Over an unencrypted scheme it crosses the network in plaintext, so anyone on the path has it — and it is the first thing anybody writes.

Use tls://, or connect without a credential to a server that does not require one. Passing nil is only ever right for an in-process server.

A URL must not carry a secret

// Refused with ErrCredentialInURL.
gonats.ClientSettings{URL: "nats://s3cret@broker:4222"}

NATS accepts credentials in a connection URL, which puts a secret in a struct that gets logged, dumped and committed. The credential belongs in a CredentialSource, which is resolved at connect time and never stored.

One target, or none

client.Connect(ctx, gonats.ClientSettings{}, nil)                                   // refused
client.Connect(ctx, gonats.ClientSettings{URL: "..."}, cred, client.InProcess(srv)) // refused

Supplying both says two different things, and guessing which was meant is how a service ends up talking to a broker nobody thought it was talking to.

Reconnection

The default is to retry forever, two seconds apart: a long-running service that gives up reconnecting has to be restarted by a person to recover from an outage it would otherwise have survived.

To fail fast instead, say so explicitly:

gonats.ClientSettings{URL: "tls://...", MaxReconnects: gonats.Reconnects(0)}

It is a pointer because NATS reads 0 as "do not reconnect", so zero cannot also be how a caller says "I did not set this".

The credential is resolved once

It is read at connect time and installed for the life of the connection, so every reconnect reuses it. After a token rotation, reconnects keep presenting the stale one and NATS eventually stops trying — at which point Liveness reports the connection closed, which is the signal to restart the process. Rotation currently requires a new client.