5 minutes to add RESTful APIs for your gRPC services
Kevin Wan

Kevin Wan @kevwan

About: go-zero.dev

Location:
Shanghai, China
Joined:
May 24, 2021

5 minutes to add RESTful APIs for your gRPC services

Publish Date: Aug 22 '22
25 3

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

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) {}
}
Enter fullscreen mode Exit fullscreen mode

One-click generation, you know.

$ goctl rpc protoc --go_out=. --go-grpc_out=. --zrpc_out=. sum.proto
Enter fullscreen mode Exit fullscreen mode

See what you get

.
├── 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
Enter fullscreen mode Exit fullscreen mode

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

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

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

$ go mod tidy && go run sum.go
Starting rpc server at 127.0.0.1:8080...
Enter fullscreen mode Exit fullscreen mode

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,

$ go get -u github.com/zeromicro/go-zero@latest
Enter fullscreen mode Exit fullscreen mode

Modify the proto file

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

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: "*"
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Generate proto descriptor file

protoc --include_imports --proto_path=. --descriptor_set_out=sum.pb sum-api.proto
Enter fullscreen mode Exit fullscreen mode

Modify the configuration file

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

type Config struct {
    zrpc.RpcServerConf
    Gateway gateway.GatewayConf
Gateway.GatewayConf }
Enter fullscreen mode Exit fullscreen mode

The modified etc/sum.yaml is as follows

Gateway:
  Name: gateway
  Port: 8081
  Upstreams:
    - Grpc:
        Endpoints:
          - localhost:8080
      ProtoSets:
        - sum.pb
Enter fullscreen mode Exit fullscreen mode

Modify the main function

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

    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()
Enter fullscreen mode Exit fullscreen mode

Great job!

Let's start the service

$ go run sum.go
Starting rpc server at 127.0.0.1:8080...
Starting gateway at 0.0.0.0:8081...
Enter fullscreen mode Exit fullscreen mode

Test it with curl

$ 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"
}
Enter fullscreen mode Exit fullscreen mode

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

{"@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"}
Enter fullscreen mode Exit fullscreen mode

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

Project address

https://github.com/zeromicro/go-zero

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

Comments 3 total

  • faysou
    faysouAug 24, 2022

    Very interesting. It would maybe be interesting if gozero could support code generation from openapi and asyncapi specs.

  • Jake
    JakeAug 25, 2022

    This really conflates REST and HTTP. gRPC doesn’t require transferring a state, but REST does. These are semantic differences, not syntactic ones, so a lot more domain knowledge is required.

  • Andres Felipe Sierra Rojas
    Andres Felipe Sierra Rojas Nov 21, 2023

    What a good explanation
    Is it possible to make requests using the Multipart form?

Add comment