xk6-nats
This is a k6 extension using the xk6 system, that allows to use NATS protocol.
| ❗ This extension isn't supported by the k6 team, and may break in the future. USE AT YOUR OWN RISK! |
Build
To build a k6 binary with this extension, first ensure you have the prerequisites:
- Install
xk6 framework for extending k6:
go install go.k6.io/xk6/cmd/xk6@latest
- Build the binary:
xk6 build --with github.com/ydarias/xk6-nats@latest
- Run a test
k6 run -e NATS_HOSTNAME=localhost test/test.js
To run JetStream test, make sure NATS JetStream is started, e.g. nats-server -js
k6 run -e NATS_HOSTNAME=localhost test/test_jetstream.js
To run publish with headers test, make sure NATS JetStream is started, e.g. nats-server -js
./k6 run -e NATS_HOSTNAME=localhost test/test_headers.js
API
Nats
A Nats instance represents the connection with the NATS server, and it is created with new Nats(configuration), where configuration attributes are:
| Attribute |
Description |
servers |
(mandatory) is the list of servers where NATS is available. Supports multiple protocols: nats:// for standard NATS connections, ws:// for WebSocket connections, and wss:// for WebSocket Secure connections (e.g. ['nats://localhost:4222'], ['wss://nats.example.com:443']) |
unsafe |
(optional) allows running with self-signed certificates when doing tests against a testing environment, it is a boolean value (default value is false) |
token |
(optional) is the value of the token used to connect to the NATS server |
Example (standard NATS connection):
import {Nats} from 'k6/x/nats'
const natsConfig = {
servers: ['nats://localhost:4222'],
unsafe: true,
}
const nats = new Nats(natsConfig)
Example (WebSocket Secure connection):
import {Nats} from 'k6/x/nats'
const natsConfig = {
servers: ['wss://nats.example.com:443'],
unsafe: false,
caFile: '/path/to/ca.pem',
}
const nats = new Nats(natsConfig)
Bootstrap API Client
The Bootstrap API Client provides a generic way to authenticate with any Bootstrap API endpoint using mTLS (mutual TLS) to obtain JWT tokens and NATS connection information. This functionality is implemented in Go and is completely configurable with no hardcoded endpoints or domain names.
BootstrapConfig Structure:
| Attribute |
Description |
Endpoint |
(mandatory) Full Bootstrap API endpoint URL (e.g., https://bootstrap.example.com) |
APIPath |
(mandatory) API path pattern with placeholders (e.g., /discovery/api/v1/jwt/{service}/{resource}) |
ServiceName |
(mandatory) Service identifier that replaces {service} in the API path |
CarID |
(mandatory) Resource/Vehicle identifier that replaces {resource} in the API path |
CertFile |
(mandatory) Path to client certificate file for mTLS authentication |
KeyFile |
(mandatory) Path to client private key file for mTLS authentication |
CAFile |
(mandatory) Path to root CA certificate file |
Authentication Flow:
- Generates an ephemeral Ed25519 key pair using NATS Nkeys
- Signs the ephemeral public key with the vehicle's private key
- Makes an HTTPS POST request to the Bootstrap API with mTLS
- Returns JWT token, NATS server addresses, and optional stream mappings
BootstrapResponse Structure:
| Attribute |
Description |
JWT |
Authentication token for NATS connection |
NATSServers |
Array of NATS server addresses |
StreamMappings |
Map of stream names to subjects (optional) |
SubjectPrefix |
Prefix for NATS subjects (optional) |
JavaScript API:
The Bootstrap API is exposed to JavaScript/k6 through the BootstrapAuth function, which handles authentication and automatically generates a NATS credentials file.
import nats from 'k6/x/nats';
const config = {
endpoint: 'https://bootstrap.example.com',
apiPath: '/discovery/api/v1/jwt/{service}/{resource}',
serviceName: 'messaging-service',
carId: 'vehicle-123',
certFile: '/path/to/cert.pem',
keyFile: '/path/to/key.pem',
caFile: '/path/to/ca.pem',
credentialsFile: 'nats.creds' // Optional, defaults to 'nats.creds'
};
const result = nats.BootstrapAuth(config);
BootstrapAuth Configuration:
| Attribute |
Required |
Description |
endpoint |
Yes |
Full Bootstrap API endpoint URL |
apiPath |
Yes |
API path pattern with placeholders (e.g., /discovery/api/v1/jwt/{service}/{resource}) |
serviceName |
Yes |
Service identifier that replaces {service} in the API path |
carId |
Yes |
Vehicle/Resource identifier that replaces {resource} in the API path |
certFile |
Yes |
Path to client certificate file for mTLS authentication |
keyFile |
Yes |
Path to client private key file for mTLS authentication |
caFile |
Yes |
Path to root CA certificate file |
credentialsFile |
No |
Path where the NATS credentials file will be generated (defaults to nats.creds) |
BootstrapAuth Response:
| Attribute |
Description |
jwt |
JWT authentication token |
natsServerAddress |
Primary NATS server address |
natsServers |
Array of all NATS server addresses |
scopeToStream |
Map of scopes to stream names |
scopeToSubjectPrefix |
Map of scopes to subject prefixes |
credentialsFile |
Path to the generated credentials file |
streamMappings |
Original stream mappings from the Bootstrap API |
subjectPrefix |
Subject prefix from the Bootstrap API |
Complete Example:
import nats from 'k6/x/nats';
export default function () {
const bootstrapConfig = {
endpoint: 'https://bootstrap.example.com',
apiPath: '/discovery/api/v1/jwt/{service}/{resource}',
serviceName: 'messaging-service',
carId: 'vehicle-123',
certFile: '/path/to/cert.pem',
keyFile: '/path/to/key.pem',
caFile: '/path/to/ca.pem',
credentialsFile: 'nats-bootstrap.creds'
};
const result = nats.BootstrapAuth(bootstrapConfig);
const natsClient = new nats.Nats({
servers: result.natsServers,
userCredentials: result.credentialsFile
});
natsClient.publish(result.subjectPrefix + '.example', 'Hello NATS!');
natsClient.close();
}
Go-Level Usage:
The Bootstrap API Client is also available at the Go level for programmatic use:
config := BootstrapConfig{
Endpoint: "https://bootstrap.example.com",
APIPath: "/discovery/api/v1/jwt/{service}/{resource}",
ServiceName: "messaging-service",
CarID: "vehicle-123",
CertFile: "/path/to/cert.pem",
KeyFile: "/path/to/key.pem",
CAFile: "/path/to/ca.pem",
}
response, err := Authenticate(config)
if err != nil {
// Handle error
}
err = GenerateCredentialsFile(response.JWT, response.NkeySeed, "nats.creds")
if err != nil {
// Handle error
}
Publishing
You can publish messages to a topic using the following functions:
| Function |
Description |
publish(topic, payload) |
publish a new message using the topic (string) and the given payload that is a string representation that later is serialized as a byte array |
publisWithHeaders(topic, payload, headers) |
publish a new message using the topic (string), the given payload that is a string representation that later is serialized as a byte array and the headers |
publishMsg(message) |
publish a new message using the message (object) that has the following attributes: topic (string), data (string), raw(byte array) and headers (object) |
request(topic, payload, headers) |
sends a request to the topic (string) and the given payload as string representation and the headers, and returns a message |
Example:
const publisher = new Nats(natsConfig)
publisher.publish('topic', 'data')
publisher.publishWithHeaders('topic', 'data', { 'header1': 'value1' })
publisher.publishMsg({ topic: 'topic', data: 'string data', headers: { 'header1': 'value1' } })
publisher.publishMsg({ topic: 'topic', raw: [ 0, 1, 2, 3 ], headers: { 'header1': 'value1' } })
const message = publisher.request('topic', 'data', { 'header1': 'value1' })
Subscribing
You can subscribe to a topic using the following functions:
| Function |
Description |
subscribe(topic, callback) |
subscribe to a topic (string) and execute the callback function when a message is received, it returns a subscription |
Example:
const subscriber = new Nats(natsConfig)
const subscription = subscriber.subscribe('topic', (msg) => {
console.log(msg.data)
})
// ...
subscription.close()
Note: the subscription model has been changed. Now when you use subscribe method, a subscription object is returned and the subscription should be closed using the close() method.
JetStream
You can use JetStream Pub/Sub in the same way as NATS Pub/Sub. The only difference is that you need to setup the stream before publishing or subscribing to it.
The configuration is the same as the one used in the nats-io's StreamConfig:
| Attribute |
Description |
name |
(mandatory) is the name of the stream |
description |
(optional) is the description of the stream |
subjects |
(mandatory) is the list of subjects that the stream will be listening to |
retention |
(optional) is the retention policy of the stream, it can be limits, interest, workqueue or stream |
max_consumers |
(optional) is the maximum number of consumers that the stream will allow |
max_msgs |
(optional) is the maximum number of messages that the stream will store |
max_bytes |
(optional) is the maximum number of bytes that the stream will store |
max_age |
(optional) is the maximum age of the messages that the stream will store |
max_msg_size |
(optional) is the maximum size of the messages that the stream will store |
discard |
(optional) is the discard policy of the stream, it can be old, new or none |
storage |
(optional) is the type of storage that the stream will use, it can be file or memory |
replicas |
(optional) is the number of replicas that the stream will have |
no_ack |
(optional) is a boolean value that indicates if the stream will use acks or not |
Example:
const streamConfig = {
name: "mock",
subjects: ["foo"],
max_msgs_per_subject: 100,
discard: 0,
storage: 1
}
const publisher = new Nats(natsConfig)
publisher.jetStreamSetup(streamConfig)
JetStream operations
Once the stream is setup, you can publish and subscribe to it using the following functions:
| Function |
Description |
jetStreamSetup(config) |
setup a stream with the given configuration |
jetStreamPublish(topic, payload) |
publish a new message using the topic (string) and the given payload that is a string representation that later is serialized as a byte array |
jetStreamPublishWithHeaders(topic, payload, headers) |
publish a new message using the topic (string), the given payload that is a string representation that later is serialized as a byte array and the headers |
jetStreamPublishMsg(message) |
publish a new message using the message (object) that has the following attributes: topic (string), data (string), raw(byte array) and headers (object) |
jetStreamSubscribe(topic, callback) |
subscribe to a topic (string) and execute the callback function when a message is received, it returns a subscription |
Example:
const subscriber = new Nats(natsConfig)
publisher.jetStreamSetup(streamConfig)
const subscription = subscriber.jetStreamSubscribe('mock', (msg) => {
console.log(msg.data)
})
const publisher = new Nats(natsConfig)
publisher.jetStreamPublish('foo', 'data')
publisher.jetStreamPublishWithHeaders('foo', 'data', { 'header1': 'value1' })
publisher.jetStreamPublishMsg({ topic: 'topic', data: 'string data', headers: { 'header1': 'value1' } })
publisher.jetStreamPublishMsg({ topic: 'topic', raw: [ 0, 1, 2, 3 ], headers: { 'header1': 'value1' } })
// ...
subscription.close()
Return values
A subscription return value has the following methods:
| Method |
Description |
close() |
closes the subscription |
A message return value has the following attributes:
| Attribute |
Description |
raw |
the payload in byte array format |
data |
the payload in string format |
topic |
the topic where the message was published |
headers |
the headers of the message |
Examples
You can find some examples in the examples folder. To run them, you need to have a NATS server running and then run the following command:
k6 run -e NATS_HOSTNAME=your_nats_server_host examples/binary.js
k6 run -e NATS_HOSTNAME=your_nats_server_host examples/complex.js
k6 run -e NATS_HOSTNAME=your_nats_server_host examples/simple.js
k6 run -e NATS_HOSTNAME=your_nats_server_host examples/withHeaders.js
k6 run -e NATS_HOSTNAME=your_nats_server_host examples/websocket.js
k6 run examples/bootstrapAuth.js
Available examples:
binary.js - Publishing binary data to NATS
complex.js - Complex NATS operations
simple.js - Simple NATS publish/subscribe
withHeaders.js - Publishing messages with headers
websocket.js - WebSocket connections
bootstrapAuth.js - Bootstrap API authentication and credentials generation
bootstrap.js - Bootstrap API documentation and usage guide
For WebSocket connections, make sure your NATS server is configured to support WebSocket connections on the appropriate port (typically 443 for WSS or 80 for WS).
Or you can check the test folder to see how to use the extension.
License
The source code of this project is released under the MIT License.