Beginner's Guide to RPC in Golang: Understanding the Basics
Tags: go, rpc, beginners, webdev
Published: 2023-06-26 08:41:27

In the Information Exchange Model, the client sends a request, and the server responds to the client. The type of data/information exchange is based on a specific format that lets both sides communicate with each other. Presently most applications do not use this model to share but instead call services, as they would call any function. 

**Remote Procedure Call(RPC)** was intended to be the function model for networked systems. Client execute RPC like they call a native function, except client package the function parameter and send them through the network to the server. The server can then unpack this parameter and process the request, executing results to the client.


## What is RPC

A Remote Procedure Call is an interprocess communication that exchanges data/information between the sender and receiver in a distributed system. RPC is used in client/server architecture for accessible communication. The primary objective of RPC is to develop the physical and logical layers.

**The following list is a basic overview of how the RPC process works:**

- The RPC client puts together the function and argument required to be sent to the server
- The RPC server receives them by dialling the connection
- The RPC server executes the remote process
- The RPC server responded to the client with the result
- The RPC client gets the response from the request duly and uses it then, it moves to the next task.

## Prerequisites 

Before getting started, ensure you have the following prerequisites:

- Basic knowledge of Go
- Go installed on your machine


## Step 1: Setting Up Your Project

To begin building your project, follow these steps to set up your project:

Create a new directory for your project, open your terminal, and run the following command:


    mkdir -p go-RPC

This will create a new folder name `go-RPC` in your current directory.

Navigate to the project director by running the following command:


    cd go-RPC

Now you are inside the `go-RPC` directory. 

Initialize your new project by running the following command:


    go mod init go-RPC

This command creates a new `go.mod` file, your project's configuration, and the dependency management file.

Following these steps, you will have a new project directory with a `go.mod` file ready.

This project will have two sub-directory; follow the steps in the preceding to create the sub-directory. You will have an RPC server and RPC client folder that looks like this.


    client-server-RPC
    ├── client
    │    └── main.go
    └── server
         └── main.go


## Step 2: Create The RPC Server

You will create an RPC server to send UTC server time to the RPC client. To build the RPC server, go has two libraries, `net/rpc` and `net/http`.


    package main
    import (
        "log"
        "net"
        "net/http"
        "net/rpc"
        "time"
    )
    type Args struct{}
    type TimeServer int64
    func (t *TimeServer) GiveServerTime(args *Args, reply *int64) error {
        // Fill reply pointer to send the data back
        *reply = time.Now().Unix()
        return nil
    }
    func main() {
        // Create a new RPC server
        timeserver := new(TimeServer)
        // Register RPC server
        rpc.Register(timeserver)
        rpc.HandleHTTP()
        // Listen for requests on port 1234
        l, e := net.Listen("tcp", ":2233")
        if e != nil {
            log.Fatal("listen error:", e)
        }
        http.Serve(l, nil)
    }

**Explanation:**
The `Args` struct holds information about arguments passed from the client (RPC) to the server. TimeServer number to register with the `rpc.Register`. Here, the server wishes to export an object of type TimeServer(int64). HandleHTTP registers an HTTP handler for RPC messages to DefaultServer. Then you started a TCP server that listens on port 2233—the `http.Serve` function is used to serve it as a running program. `GiveServerTime` is the client's function, and the current server time is returned.

**Here are a few points to note:**

- `Args struct` has no field because the server code is not expecting any argument from the client
- GiveServerTime takes the Args objects and replies pointer object
- it sets the reply pointer object
- It serves on port 2233

## Step 3: Create The RPC Client
You'll create the client to call your server and handle the response here. It also uses the same `net/rpc` library but with a different method to dial the server.


    package main
    import (
        "log"
        "net/rpc"
    )
    type Args struct {
    }
    func main() {
        var reply int64
        args := Args{}
        client, err := rpc.DialHTTP("tcp", "localhost"+":2233")
        if err != nil {
            log.Fatal("dialing:", err)
        }
        err = client.Call("TimeServer.GiveServerTime", args, &reply)
        if err != nil {
            log.Fatal("arith error:", err)
        }
        log.Printf("%d", reply)
    }

**Explanation:**
The client `DialHTTP` to connect to the RPC server, which is running on port 2233. Call the Remote function with the Name: function format with `args` and `reply` with the pointer object, the client get the data collected into the reply object and call function is sequential.

**Note:** The client is running as an independent program. Both programs can be on different machines, and the computing can still be shared. This is the core concept of distributed systems. The tasks are divided and given to various RPC servers. Finally, the client collects the results and uses them for further actions. Custom RPC code is only valid when the client and server are written in Go. So to have the RPC server consumed by multiple services, we need to define the JSON RPC over HTTP. Then, any other programming language can send a JSON string and get JSON.

Now you will start the `server.go` to the program:


    go run server.go

Open another terminal tab to start the client.go program:


    go run client.go

The client console will give an output of the current UTC.


![The output of the terminal](https://paper-attachments.dropboxusercontent.com/s_15795FB1A72CB2DB9E885F6603AA9D963B0DFE28F70674F2F14837095E74C87F_1687301992612_Screenshot+2023-06-20+152541.png)

## JSON RPC Using Gorilla RPC

Gorilla also provides an RPC library. It makes it easier for the client and server to be in the same file. You can send a request as JSON and receive a response as JSON. 

This section will be a real-world use case. You have a JSON file on the server with Twitter profile details (Name, username, followers, and following). The client requests Twitter information by making an HTTP request. When the RPC server receives the request, it reads the file from the filesystem and parses it. If the Name matches any profile, the server returns the information to the client in JSON format.

You will install Gorilla RPC and Mux library with the following command: 


    go get github.com/gorilla/rpc


    go get github.com/gorilla/mux

**Your folder structure should look like this.**


    JSON-RPC
    ├── go.mod
    ├── go.sum
    ├── twitterProfile.json
    └── server
         └── main.go

**The JSON data file**
Here is a sample data file with a `twitterprofile` details structure in it.


    [
        {
            "name": "Name cannot be black",
            "username": "@hackSultan",
            "followers": "187k",
            "following": "974"
        },
        {
            "name": "Scott Hanselman",
            "username": "@shanselman",
            "followers": "317k",
            "following": "10.5k"
        },
        {
            "name": "Odogwu Machalla",
            "username": "@unicodeveloper",
            "followers": "97k",
            "following": "15.5k"
        },
        {
            "name": "Gbedilodo",
            "username": "@olodocoder",
            "followers": "252",
            "following": "143"
        },
        {
            "name": "Angie Jones",
            "username": "@techgirl908",
            "followers": "114k",
            "following": "626"
        }
    ]


## Implementing The RPC JSON Server

In this example, you must make a few changes to the preceding server example. Including Gorilla 
libraries in this example.


    package main
    import (
        jsonparse "encoding/json"
        "io/ioutil"
        "log"
        "net/http"
        "os"
        "github.com/gorilla/mux"
        "github.com/gorilla/rpc"
        "github.com/gorilla/rpc/json"
    )
    // Args holds arguments passed to JSON RPC service
    type Args struct {
        Name string
    }
    // TwitterProfie struct holds TwitterProfile JSON structure
    type TwitterProfile struct {
        Name     string `json:"name,omitempty"`
        Username   string `json:"username,omitempty"`
        Followers string `json:"followers,omitempty"`
        Following string `json:"following,omitempty"`
    }
    type JSONServer struct{}
    // TwitterProfileDetail
    func (t *JSONServer) TwitterProfileDetail(r *http.Request, args *Args, reply *TwitterProfile) error {
        var twitterprofiles []TwitterProfile
        // Read JSON file and load data
        raw, readerr := ioutil.ReadFile("./twitterprofile.json")
        if readerr != nil {
            log.Println("error:", readerr)
            os.Exit(1)
        }
        // Unmarshal JSON raw data into twitterprofiles array
        marshalerr := jsonparse.Unmarshal(raw, &twitterprofiles)
        if marshalerr != nil {
            log.Println("error:", marshalerr)
            os.Exit(1)
        }
        // Iterate over each twitterprofile to find the given twitterprofile
        for _, twitterprofile := range twitterprofiles {
            if twitterprofile.Id == args.Id {
                // If twitterprofile found, fill reply with it
                *reply = twitterprofile
                break
            }
        }
        return nil
    }
    func main() {
        // Create a new RPC server
        s := rpc.NewServer() // Register the type of data requested as JSON
        s.RegisterCodec(json.NewCodec(), "application/json")
        // Register the service by creating a new JSON server
        s.RegisterService(new(JSONServer), "")
        r := mux.NewRouter()
        r.Handle("/rpc", s)
        http.ListenAndServe(":9000", r)
    }
    

Open a terminal to start the server program:


    go run main.go

**Explanation:**
The `args` and `TwitterProfile` structs contain information about the JSON arguments passed and the profile structure. You define a remote function called TwitterProfileDetail on a resource called `JSONServer`. This struct is a service created to register with the RegisterService function of the RPC server. If you notice, you also write the codec as JSON. Whenever you get a request from the client, You load the JSON file called `twitterprofile.json` into memory and then into the TwitterProfile struct using JSON's Unmarshal method. `jsonparse` is the alias given for the Go package `encoding/json` because the JSON package from the Gorilla import has the same Name to remove conflict, use an alias instead.

In the remote function, you set the value of the reply with the matched `twiterprofile`. The data is filled if the client's name matches any of the `twitterprofiles` in JSON. If there is no match, the RPC server will return empty data. This way, you can create a JSON RPC to make clients universal. Here, no Go client was written. Any client can access data from the service.

Now that the server is running, you must interact with the client. The client can be a CRUL command since the RPC server is serving a request over HTTP. The `twitterprofile` name is required to get the details.

Open another terminal with the following command to execute the CRUL request:


    curl -X POST \
     http: //localhost:9000/rpc \
     -H 'cache-control: no-cache' \
     -H 'content-type: application/json' \
     -d '{
        "method": "JSONServer.TwitterProfileDetail",
        "params": [
            {
                "name":"Name cannot be blank"
            }
        ],
        "id": "1"
    }'


## Conclusion

You've learned what an RPC is and how an RPC server and client can be built. The JSON RPC is used as a use case, and the use case JSON RPC uses the Gorilla toolkit. Now you can easily create RPC in your Golang project.

For a better understanding, kindly check more articles on RPC. I hope you enjoy this article. Leave a comment on the part you didn’t understand and smash the like button.

**Note:**
Prefer JSON RPC when multiple client technologies need to connect to your service.
You can view the source code [here](https://github.com/atanda0x/gorilla-jsonRPC).

I'd love to connect with you on. [Twitter](https://twitter.com/atanda0x) | [Linkedin](https://www.linkedin.com/in/nafiu-atanda-7685681a7?originalSubdomain=ng) | [Github](https://github.com/atanda0x)