5 minutes to add RESTful APIs for your gRPC services
Tags: go, grpc, webdev, gateway
Published: 2022-08-22 14:35:00

## gRPC service with HTTP interface?

While go-zero brought an excellent RESTful and gRPC service development experience to the developers, more expectations arised.

- I want to write code only once
- I want both the gRPC and HTTP interfaces

It makes sense! You see what users say.

> User A: a set of logic, HTTP and gRPC together.
>
> User B: if go-zero can simplify this step I feel it will become the the best microservices framework, ever.

So I fell into a deep thought: the user is never wrong, but do we want to provide it?

Here comes the article.

## Let's write gRPC service first

We are too familiar with this service. Create a new directory, let's call it `grpc-restufl`, and put a `sum.proto` file in it

```protobuf
syntax = "proto3";

package sum;
option go_package="./pb";

message SumRequest {
    int64 a = 1;
    int64 b = 2;
}

message SumResponse {
    int64 result = 1;
}

service Sum {
    rpc Add(SumRequest) returns (SumResponse) {}
}
```

One-click generation, you know.

```shell
$ goctl rpc protoc --go_out=. --go-grpc_out=. --zrpc_out=. sum.proto
```

See what you get

```shell
.
├── etc
│ └── sum.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ └── addlogic.go
│ ├── server
│ │ └── sumserver.go
│ └── svc
│ └── servicecontext.go
├─ pb
│ ├── sum.pb.go
│ └── sum_grpc.pb.go
├─ sum
│ └── sum.go
├── sum.go
└── sum.proto
```

To implement the business logic, modify the `Add` method in `internal/logic/addlogic.go` as follows.

```go
func (l *AddLogic) Add(in *pb.SumRequest) (*pb.SumResponse, error) {
    return &pb.SumResponse{
        Result: in.A+in.B,
    }, nil
}
```

You can run it, and the business logic is there (although it is very simple, for demo purpose.)

```shell
$ go mod tidy && go run sum.go
Starting rpc server at 127.0.0.1:8080...
```

For those who are familiar with go-zero, there is no highlight (new knowledge) here, let's go ahead ~

## Provide HTTP interface

### Update go-zero

First, let's update go-zero to `v1.4.0` version,

```shell
$ go get -u github.com/zeromicro/go-zero@latest
```

### Modify the proto file

Modify `sum.proto`, I created a new `sum-api.proto`, as follows

```protobuf
syntax = "proto3";

package sum;
option go_package="./pb";

import "google/api/annotations.proto";

message SumRequest {
    int64 a = 1;
    int64 b = 2;
}

message SumResponse {
    int64 result = 1;
}

service Sum {
    rpc Add(SumRequest) returns (SumResponse) {
        option (google.api.http) = {
            post: "/v1/sum"
            body: "*"
        };
    }
}
```

### Generate proto descriptor file

```shell
protoc --include_imports --proto_path=. --descriptor_set_out=sum.pb sum-api.proto
```

### Modify the configuration file

The modified `internal/config/config.go` looks like this (partially)

```go
type Config struct {
    zrpc.RpcServerConf
    Gateway gateway.GatewayConf
Gateway.GatewayConf }
```

The modified `etc/sum.yaml` is as follows

```yaml
Gateway:
  Name: gateway
  Port: 8081
  Upstreams:
    - Grpc:
        Endpoints:
          - localhost:8080
      ProtoSets:
        - sum.pb
```

### Modify the main function

Create `gateway` and use `ServiceGroup` to manage `gRPC server` and `gateway server`, part of the code is as follows.

```go
    gw := gateway.MustNewServer(c.Gateway)
    group := service.NewServiceGroup()
    group.Add(s)
    group.Add(gw)
    defer group.Stop()

    fmt.Printf("Starting rpc server at %s... \n", c.ListenOn)
    fmt.Printf("Starting gateway at %s:%d... \n", c.Gateway.Host, c.Gateway.Port)
    group.Start()
```

## Great job!

Let's start the service

```shell
$ go run sum.go
Starting rpc server at 127.0.0.1:8080...
Starting gateway at 0.0.0.0:8081...
```

Test it with `curl`

```shell
$ curl -i -H "Content-Type: application/json" -d '{"a":2, "b":3}' localhost:8081/v1/sum
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-ad5b7df7a834a1c05ee64999e3310811-195ba1f4f9956cc4-00
Date: Mon, 18 Jul 2022 14:33:11 GMT
Content-Length: 20

{
  "result": "5"
}
```

And look at the link information in our `gateway` and `gRPC` logs that corresponds to what the client received, awesome!

```json
{"@timestamp": "2022-07-18T22:33:11.437+08:00", "caller": "serverinterceptors/statinterceptor.go:76", "content": "127.0.0.1:61635 - /sum. Sum/Add - {\"a\":2,\"b\":3}", "duration": "0.0ms", "level": "info", "span": "b3c85cd32a76f8c9", "trace":" ad5b7df7a834a1c05ee64999e3310811"}
{"@timestamp": "2022-07-18T22:33:11.438+08:00", "caller": "handler/loghandler.go:197", "content":"[HTTP] 200 - POST /v1/sum - 127.0.0.1: 61662 - curl/7.79.1", "duration": "0.7ms", "level": "info", "span": "195ba1f4f9956cc4", "trace": "ad5b7df7a834a1c05ee64999e3310811"}
```

## Conclusion

You see, adding the `HTTP` interface to our `gRPC` service is very easy? Isn't it?

Also, don't underestimate this simple `gateway`, the configuration will automatically load balance if it is docked to the `gRPC` service found behind it, and you can also customize the middleware to control it whatever you want.

By the way, the full code for this example is at.

[https://github.com/kevwan/grpc-restful](https://github.com/kevwan/grpc-restful)

## Project address

[https://github.com/zeromicro/go-zero](https://github.com/zeromicro/go-zero)

Feel free to use `go-zero` and **star** to support us!