RPC with Iris
This is a repost from https://github.com/IrisMQ/book
In our very basic example, we have only one API or procedure call named “jwt”. Doing RPC with Iris is a two-step process:
(1) Register your service (API, procedure or function)
(2) Implement the service handler
Your service may access a database, filesystem, process a complex algorithm etc. Just remember that an RPC has a timeout so you are likely to put tasks that are not time-intensive. Also, you can run multiple identical services so you can take advantage of Iris load balancing.
RPC server
//irisrpc-server
package main
import (
"fmt"
"github.com/ibmendoza/jwt"
"gopkg.in/project-iris/iris-go.v1"
"log"
)
type JwtHandler struct{}
func (b *JwtHandler) Init(conn *iris.Connection) error {
return nil
}
func (b *JwtHandler) HandleBroadcast(msg []byte) {
}
func (b *JwtHandler) HandleRequest(req []byte) ([]byte, error) {
claims := make(map[string]interface{})
claims["sub"] = "1234567890"
claims["name"] = "John Doe"
claims["admin"] = true
claims["exp"] = jwt.ExpiresInMinutes(15)
naclKey, err := jwt.GenerateKey()
fmt.Println(naclKey)
token, err := jwt.Sign("HS384", claims, "secret", naclKey)
if err != nil {
return []byte(""), err
}
return []byte(token), nil
}
func (b *JwtHandler) HandleDrop(reason error) {
}
func (b *JwtHandler) HandleTunnel(tun *iris.Tunnel) {
}
func main() {
service, err := iris.Register(55555, "jwt", new(JwtHandler), nil)
if err != nil {
log.Fatalf("failed to register to the Iris relay: %v.", err)
}
defer service.Unregister()
fmt.Scanln()
}
RPC Client
The sample RPC client below is adapted from Alex Edwards’ example.
//irisrpc-client
package main
import (
"fmt"
"github.com/alexedwards/stack"
"gopkg.in/project-iris/iris-go.v1"
"log"
"net/http"
"time"
)
var conn *iris.Connection
func main() {
var err error
//iris
conn, err = iris.Connect(55555)
if err != nil {
log.Fatalf("failed to connect to the Iris relay: %v.", err)
} else {
log.Println("Connected to port 55555")
}
defer conn.Close()
//iris
stk := stack.New(token, stack.Adapt(language))
http.Handle("/", stk.Then(final))
http.ListenAndServe(":3000", nil)
}
//RPC
func fromIris(conn *iris.Connection) string {
request := []byte("")
reply, err := conn.Request("jwt", request, time.Second*3)
if err != nil {
return ""
}
return string(reply)
}
func token(ctx *stack.Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokn := fromIris(conn)
ctx.Put("token", tokn)
next.ServeHTTP(w, r)
})
}
func language(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Language", "en-gb")
next.ServeHTTP(w, r)
})
}
func final(ctx *stack.Context, w http.ResponseWriter, r *http.Request) {
token, ok := ctx.Get("token").(string)
if !ok {
http.Error(w, http.StatusText(500), 500)
return
}
fmt.Fprintf(w, "Token is: %s", token)
}
The advantage of using stack over the native golang context is that the former can hold multiple key-value pairs while the latter can only hold one key-value pair at a time. This saves you the trouble of spawning a new context everytime you need to access a separate backend service.
To illustrate, consider the following diagram courtesy of Harlow Ward:
With stack, you can use ctx.Put (if necessary) for every func (ctx *stack.Context, next http.Handler) http.Handler and aggregate the results to the final handler.

Do you want to be a developer, and sustain a fun and meaningful IT Marketplace? Just click HERE.