Basic NATS

There aren’t many docs out there on NATS even though it is so awesome (and fast) so I thought I would share some notes on what it is needed to implement a basic client for NATS, (and maybe one day write a client for it…)

Connecting and receiving INFO

Let’s try a simple connecting test:

export msgcounter=0
nc 127.0.0.1 4222 | while read msg; do
  msgcounter=`expr $msgcounter + 1`
  echo "$msgcounter :: " $msg
done

We can see that as soon as we establish the connection, the server will respond with an INFO message, and then after 2 minutes, it will send its first PING message to check the connection with the client.

The server will try to send 3 more PING messages to the client, and on the 4th message, it will close the connection.

[2014-11-16T14:17:12 -0500] run-nats-server -- 2014/11/16 14:17:12 [INFO] Starting gnatsd version 0.5.6
[2014-11-16T14:17:12 -0500] run-nats-server -- 2014/11/16 14:17:12 [INFO] Listening for client connections on 0.0.0.0:4222
[2014-11-16T14:17:12 -0500] run-nats-server -- 2014/11/16 14:17:12 [INFO] gnatsd is ready
[2014-11-16T14:17:12 -0500] client          -- 1 ::  INFO {"server_id":"da9955fe007233fa724ec91a8978c44d","version":"0.5.6","host":"0.0.0.0","port":4222,"auth_required":false,"ssl_required":false,"max_payload":1048576} 
[2014-11-16T14:19:12 -0500] client          -- 2 ::  PING
[2014-11-16T14:21:12 -0500] client          -- 3 ::  PING
[2014-11-16T14:23:12 -0500] client          -- 4 ::  -ERR 'Stale Connection'

PING <-> PONG

After we have received the first PING, it is needed to reply with another PONG:

To keep on using netcat for this examples, we will be relying on named pipes and a multiprocess run. Then, we will be using the named pipe to feed the PONG when we receive a PING from NATS, otherwise the connection would be closed.

[[ -e nats-in ]]  || mkfifo nats-in
[[ -e nats-out ]] || mkfifo nats-out

echo "Start client..."
nc 127.0.0.1 4222 <nats-in >nats-out &

# open nats-in for writing  on fd 3
# open nats-out for reading on fd 4
exec 3> nats-in 4<nats-out

export msgcounter=0
while read msg <&4; do
  echo "GOT ::" $msg

  export msgcounter=`expr $msgcounter + 1`
  echo "$msgcounter messages received"

  # process ping
  echo $msg | grep -q PING && echo "PONG" >&3 ;

done
Sample output
gnatsd		-- [INFO] Starting gnatsd version 0.5.7
gnatsd		-- [INFO] Listening for client connections on 0.0.0.0:4222
gnatsd		-- [INFO] gnatsd is ready
nats-client	-- Start client..
gnatsd		-- [DEBUG] Client connection created%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client	-- 1 messages received
nats-client	-- GOT :: INFO {"server_id":"93f5d02d3b613ec2f6135e4ffcee199a","version":"0.5.7","host":"0.0.0.0","port":4222,"auth_required":false,"ssl_required":false,"max_payload":1048576} 
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 2 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 3 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 4 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 5 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 6 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 7 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 8 messages received
nats-client    -- GOT :: PING
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client    -- 9 messages received
nats-client    -- GOT :: PING

PUB <-> SUB

As we have seen, we need to constantly be sending PING otherwise the server will close the connection.

Now let’s try creating a subscription!

Another step that we need to handle is CONNECT, so we do that before starting a subscription. After that, we subscribe to the hello.world channel:

echo 'CONNECT {"verbose":false,"pedantic":false}' > nats-in
echo "SUB hello.world  2" > nats-in

Now if we try to send a message to this channel by nats-pub

nats-pub hello.world -s nats://127.0.0.1:4222 "hoge hoge"

…we get the following displayed on our netcat console:

[2014-11-16T23:14:20 -0500] nats-client    -- GOT :: MSG hello.world 2 9
[2014-11-16T23:14:20 -0500] nats-client    -- GOT :: hoge hoge

The 9 here represents the number of letters that were in the MSG. Now let’s make them cooperate and send a PUB that says fuga fuga soon after it gets the first message:

[[ -e nats-in ]]  || mkfifo nats-in
[[ -e nats-out ]] || mkfifo nats-out

# cat > nats-in
echo "Start client.."
nc 127.0.0.1 4222 <nats-in >nats-out &

# open nats-in for writing  on fd 3
# open nats-out for reading on fd 4
exec 3> nats-in 4<nats-out

export pingcounter=0
while read msg <&4; do

  export pingcounter=`expr $pingcounter + 1`
  echo "$pingcounter messages received"
  echo "GOT :: $msg"

  # As soon as we get INFO, send CONNECT and subscriptions
  echo $msg | grep -q INFO && {
    echo 'CONNECT {"verbose":false,"pedantic":false}' > nats-in
    echo "SUB hello.world  2" > nats-in
  }

  # respond to PING
  echo $msg | grep -q PING && echo "PONG" > nats-in

  # respond to MSG hello.world
  echo $msg | grep -q "MSG hello.world" && {
    echo "PONG" > nats-in
    echo -ne 'PUB hello.world  9\r\nfuga fuga\r\n' > nats-in
    sleep 1
  }

done
Using the special channel ’>

Here we are subscribing to all channels by using >, which is kind of a special channel to match all:

echo 'CONNECT {"verbose":false,"pedantic":false}' > nats-in
echo "SUB >  2" > nats-in

Replying to an INBOX

Once having covered subscriptions and publishing, we can try creating an _INBOX to support requests.

Agents subscribed to the channel will get this message and respond to to it, but from the client side it is possible to stop receiving once we got enough.

$ nats-request hello.world -s nats://127.0.0.1:4222 -n 1

SUB _INBOX.46bdca94dd22f452e836b5e1f6  2\r\n"
PUB hello.world _INBOX.46bdca94dd22f452e836b5e1f6 11\r\nHello World\r\n"

[#1] Replied with : 'trying to help'

And in our client we handle it as

MSG hello.world 2 _INBOX.e16d52026b72575471357cc17d 11

The client for this would look like this:

[[ -e nats-in ]]  || mkfifo nats-in
[[ -e nats-out ]] || mkfifo nats-out

echo "Start client.."
nc 127.0.0.1 4222 <nats-in >nats-out &

# open nats-in for writing  on fd 3
# open nats-out for reading on fd 4
exec 3> nats-in 4<nats-out

export pingcounter=0
cat nats-out | while read msg; do

  export pingcounter=`expr $pingcounter + 1`
  echo "$pingcounter messages received"
  echo "GOT :: $msg"

  # As soon as we get INFO, send CONNECT and subscriptions
  echo $msg | grep -q INFO && {
    echo 'CONNECT {"verbose":false,"pedantic":false}' > nats-in
    echo "SUB hello.world  2" > nats-in
  }

  # respond to PING
  echo $msg | grep -q PING && echo "PONG" > nats-in

  # respond to MSG hello.world
  echo $msg | grep -q "MSG hello.world" && {
    echo "PONG" > nats-in
    echo -ne "PUB hello.world  9\r\nfuga fuga\r\n" > nats-in
    sleep 1
  }

  # process MSG hello.world requests
  echo $msg | grep -q "^MSG hello.world..._INBOX" && {
    echo "PONG" > nats-in
    inbox=`echo $msg | awk '{print $4}'`
    echo -ne "PUB $inbox 14\r\ntrying to help\r\n" > nats-in
  }

done

…which makes up for an extremely basic version of a NATS client :)

gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58802 - cid:7)
nats-client	-- 134 messages received
nats-client	-- GOT :: fuga fuga
nats-client	-- 135 messages received
nats-client	-- GOT :: MSG hello.world 2 9
gnatsd		-- [DEBUG] Client Ping Timer%!(EXTRA *server.client=127.0.0.1:58790 - cid:1)
nats-client	-- 136 messages received
nats-client	-- GOT :: fuga fuga
nats-client	-- 137 messages received