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) {}
}
One-click generation, you know.
$ goctl rpc protoc --go_out=. --go-grpc_out=. --zrpc_out=. sum.proto
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
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
}
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...
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
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: "*"
};
}
}
Generate proto descriptor file
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)
type Config struct {
zrpc.RpcServerConf
Gateway gateway.GatewayConf
Gateway.GatewayConf }
The modified etc/sum.yaml
is as follows
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.
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
$ 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
$ 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!
{"@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
Project address
https://github.com/zeromicro/go-zero
Feel free to use go-zero
and star to support us!
Top comments (3)
What a good explanation
Is it possible to make requests using the Multipart form?
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.
Very interesting. It would maybe be interesting if gozero could support code generation from openapi and asyncapi specs.