NATS Logo by Example

Request-Reply in Messaging

The request-reply pattern allows a client to send a message and expect a reply of some kind. In practice, the request message will either be a command, which is an intention for service to carry out some work that results in a state change, or a query, which is a request for information.

Unlike request-reply constrained protocols like HTTP, NATS is not limited to a strict point-to-point interfaction between a client and server. The request-reply pattern is built on top of the core publish-subscribe model.

By default, this means that any one of subscribers could be a responder and reply to the client. However, because NATS is not limited to point-to-point interactions, the client could indicate to NATS that multiple replies should be allowed.

This example shows the basics of the request-reply pattern including the standard “no responders” error if there are no subscribers available to handle and reply to the requesting message.

CLI Go Python Deno Node Rust C# Java Ruby Elixir Crystal C
Jump to the output or the recording
$ nbe run messaging/request-reply/go
View the source code or learn how to run this example yourself

Code

package main


import (
	"fmt"
	"os"
	"time"


	"github.com/nats-io/nats.go"
)


func main() {

Use the env variable if running in the container, otherwise use the default.

	url := os.Getenv("NATS_URL")
	if url == "" {
		url = nats.DefaultURL
	}

Create an unauthenticated connection to NATS.

	nc, _ := nats.Connect(url)
	defer nc.Drain()

In addition to vanilla publish-request, NATS supports request-reply interactions as well. Under the covers, this is just an optimized pair of publish-subscribe operations. The request handler is just a subscription that responds to a message sent to it. This kind of subscription is called a service. For this example, we can use the built-in asynchronous subscription in the Go SDK.

	sub, _ := nc.Subscribe("greet.*", func(msg *nats.Msg) {

Parse out the second token in the subject (everything after greet.) and use it as part of the response message.

		name := msg.Subject[6:]
		msg.Respond([]byte("hello, " + name))
	})

Now we can use the built-in Request method to do the service request. We simply pass a nil body since that is being used right now. In addition, we need to specify a timeout since with a request we are waiting for the reply and we likely don’t want to wait forever.

	rep, _ := nc.Request("greet.joe", nil, time.Second)
	fmt.Println(string(rep.Data))


	rep, _ = nc.Request("greet.sue", nil, time.Second)
	fmt.Println(string(rep.Data))


	rep, _ = nc.Request("greet.bob", nil, time.Second)
	fmt.Println(string(rep.Data))

What happens if the service is unavailable? We can simulate this by unsubscribing our handler from above. Now if we make a request, we will expect an error.

	sub.Unsubscribe()


	_, err := nc.Request("greet.joe", nil, time.Second)
	fmt.Println(err)
}

Output

Network f540e0ef_default  Creating
Network f540e0ef_default  Created
Container f540e0ef-nats-1  Creating
Container f540e0ef-nats-1  Created
Container f540e0ef-nats-1  Starting
Container f540e0ef-nats-1  Started
hello, joe
hello, sue
hello, bob
nats: no responders available for request

Recording

Note, playback is half speed to make it a bit easier to follow.