이번에는 go 에서 gRPC-Gateway 를 사용하는 방법에 대해 알아보려 한다.
최종 코드는 다음에서 확인 가능 : https://github.com/jeremyko/grpc-gateway-sample
앞서 살펴본 go 에서 proto buffer 사용하기 와 거의 비슷한 절차이나 gRPC-Gateway 사용을 위해 추가되는 절차가 있다.
다음 내용을 기초로 작성되었다 (그대로 따라 했더니 에러가 발생되어, 최신 go 버전에 맞게 내용이 추가된 부분이 있다. go 1.16 버전 기준).
https://grpc-ecosystem.github.io/grpc-gateway/docs/tutorials/introduction/
필요한 패키지 다운로드
go get google.golang.org/grpc
go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
go get google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
테스트 grpc 모듈 생성
임의의 위치에 my_grpc_module 디렉토리 생성. 테스트 편의를 위해 main package 를 포함한 모듈을 생성한다.
모듈명은 github.com/jeremyko/my_grpc_module 으로 한다.
root: ~/mydev# mkdir my_grpc_module
root: ~/mydev# cd my_grpc_module
go mod init 수행한다
root: ~/mydev/my_grpc_module# go mod init github.com/jeremyko/my_grpc_module
생성된 go.mod 파일 내용 확인
root: ~/mydev/my_grpc_module# cat go.mod
module github.com/jeremyko/my_grpc_module
go 1.16
간단한 테스트용 hello world 서비스를 작성한다.
protocol buffer를 사용하여 gRPC service 를 정의해야 한다. 현위치에서 proto/helloworld/hello_world.proto 파일을 생성한다.
mkdir -p proto/helloworld/
cd proto/helloworld/
touch hello_world.proto
hello_world.proto 파일을 다음처럼 작성한다
syntax = "proto3";
package helloworld;
// The greeting service definition
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
protoc 혹은 buf 를 사용해서 컴파일 한다.
cd ~/mydev/my_grpc_module
protoc -I ./proto --go_out ./proto --go_opt paths=source_relative --go-grpc_out ./proto --go-grpc_opt paths=source_relative ./proto/helloworld/hello_world.proto
튜토리얼 그대로 하면 에러가 발생한다.–> go_package 를 정의해야 함
protoc-gen-go: unable to determine Go import path for "helloworld/hello_world.proto"
Please specify either:
• a "go_package" option in the .proto source file, or
• a "M" argument on the command line.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.
--go_out: protoc-gen-go: Plugin failed with status code 1.
다시 hello_world.proto 파일을 수정한다(package 경로를 모듈명에 덧붙인 형식으로)
syntax = "proto3";
package helloworld;
option go_package = "github.com/jeremyko/my_grpc_module/proto/helloworld" ;
// The greeting service definition
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
다시 컴파일 하면 정상적으로 파일들이 생성된다.
protoc -I ./proto --go_out ./proto --go_opt paths=source_relative --go-grpc_out ./proto --go-grpc_opt paths=source_relative ./proto/helloworld/hello_world.proto
root: ~/mydev/my_grpc_module/proto/helloworld# ll
total 16
-rw-r--r-- 1 root root 3445 Apr 23 16:51 hello_world_grpc.pb.go
-rw-r--r-- 1 root root 7260 Apr 23 16:51 hello_world.pb.go
-rw-r--r-- 1 root root 432 Apr 23 16:51 hello_world.proto
gRPC server 코드를 작성한다.
/root/mydev/my_grpc_module 에 main.go 를 생성한다
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
helloworldpb "github.com/jeremyko/my_grpc_module/proto/helloworld" ;
)
type server struct{}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *helloworldpb.HelloRequest)
(*helloworldpb.HelloReply, error) {
return &helloworldpb.HelloReply{Message: in.Name + " world"}, nil
}
func main() {
// Create a listener on TCP port
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
// Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
helloworldpb.RegisterGreeterServer(s, &server{})
// Serve gRPC Server
log.Println("Serving gRPC on 0.0.0.0:8080")
log.Fatal(s.Serve(lis))
}
gRPC-Gateway 기능 추가
자..여기까지가 Go gRPC server 를 작업한것이고 ;-), 이제 추가로 gRPC-Gateway 를 위한 처리가 필요하다.
다시 proto 파일에 다음처럼 변경을 해줘야 한다.
import “google/api/annotations.proto”; 를 추가한다
HTTP->gRPC mapping 을 추가한다
syntax = "proto3";
package helloworld;
import "google/api/annotations.proto";
option go_package = "github.com/jeremyko/my_grpc_module/proto/helloworld" ;
// The greeting service definition
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
}
// The request message containing the user's name
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
이제 stub 코드를 생성한다.
protoc 혹은 buf 를 사용한다. protoc 를 사용하는 경우에는 googleapis 의존 파일을 다음 폴더 구조를 만들어서 복사해야 한다.
proto
├── google
│ └── api
│ ├── annotations.proto
│ └── http.proto
└── helloworld
└── hello_world.proto
my_grpc_module/proto/ 에 google/api 폴더 생성 mkdir -p google/api
사용할 googleapis 파일을 가져와야 한다. 임의의 위치에서 git clone 을 수행한다.
git clone https://github.com/googleapis/googleapis.git
그리고 파일을 복사한다.
cd googleapis/google/api
cp annotations.proto /root/mydev/my_grpc_module/proto/google/api/.
cp http.proto /root/mydev/my_grpc_module/proto/google/api/.
gRPC-Gateway generator 추가를 위해 protoc 를 실행한다.
cd ~/mydev/my_grpc_module/
root: ~/mydev/my_grpc_module# protoc -I ./proto --go_out ./proto --go_opt paths=source_relative --go-grpc_out ./proto --go-grpc_opt paths=source_relative --grpc-gateway_out ./proto --grpc-gateway_opt paths=source_relative ./proto/helloworld/hello_world.proto
실행하면 hello_world.pb.gw.go 파일이 생성된다.
root: ~/mydev/my_grpc_module/proto/helloworld# ll
total 24
-rw-r--r-- 1 root root 3445 Apr 23 17:20 hello_world_grpc.pb.go
-rw-r--r-- 1 root root 7664 Apr 23 17:20 hello_world.pb.go
-rw-r--r-- 1 root root 6377 Apr 23 17:20 hello_world.pb.gw.go
-rw-r--r-- 1 root root 586 Apr 23 17:10 hello_world.proto
main.go 를 수정한다
package main
import (
"context"
"log"
"net"
"net/http" //추가
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" //추가
"google.golang.org/grpc"
helloworldpb "github.com/jeremyko/my_grpc_module/proto/helloworld" ;
)
type server struct{
helloworldpb.UnimplementedGreeterServer //추가
}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *helloworldpb.HelloRequest)
(*helloworldpb.HelloReply, error) {
return &helloworldpb.HelloReply{Message: in.Name + " world"}, nil
}
func main() {
// Create a listener on TCP port
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
// Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
helloworldpb.RegisterGreeterServer(s, &server{})
// Serve gRPC Server
log.Println("Serving gRPC on 0.0.0.0:8080")
//log.Fatal(s.Serve(lis)) //막고 다음을 추가
//------------------------------------------ START
go func() {
log.Fatalln(s.Serve(lis))
}()
// Create a client connection to the gRPC server we just started
// This is where the gRPC-Gateway proxies the requests
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:8080",
grpc.WithBlock(),
grpc.WithInsecure(),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
}
gwmux := runtime.NewServeMux()
// Register Greeter
err = helloworldpb.RegisterGreeterHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
}
gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
log.Fatalln(gwServer.ListenAndServe())
//------------------------------------------ END
}
go mod tidy 실행
jeremyko: ~/mydev/go_dev/my_grpc_module# go mod tidy
go: finding module for package google.golang.org/grpc/codes
go: finding module for package google.golang.org/grpc/grpclog
go: finding module for package google.golang.org/genproto/googleapis/api/annotations
go: finding module for package github.com/grpc-ecosystem/grpc-gateway/v2/utilities
go: finding module for package github.com/grpc-ecosystem/grpc-gateway/v2/runtime
go: finding module for package google.golang.org/grpc/metadata
go: finding module for package google.golang.org/grpc
... 중략 ...
go: downloading github.com/golang/protobuf v1.5.2
go: downloading golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
go: downloading golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4
서버 실행
자체에서 main package 로 테스트 하는 경우이므로 이제 바로 서버를 실행할수 있다.
jeremyko: ~/mydev/go_dev/my_grpc_module# go run main.go
2021/04/24 11:28:47 Serving gRPC on 0.0.0.0:8080
2021/04/24 11:28:47 Serving gRPC-Gateway on http://0.0.0.0:8090
서버로 http post 요청을 보내본다
curl -X POST -k http://localhost:8090/v1/example/echo -d '{"name": " hello"}'
jeremyko: ~# curl -X POST -k http://localhost:8090/v1/example/echo -d '{"name": " hello"}'
{"message":" hello world"}jeremyko: ~#
서버 응답이 오는것을 확인할수 있다.
느낀점
해줘야 할게 많고 복잡 ㅠ