gRPC-Gateway 시작하기
민경환 사원
gRPC-Gateway 시작하기
gRPC는 Protocol Buffer를 이용하여 RESTful HTTP API에 비하여 적은 데이터로 빠른 통신을 할 수 있습니다.
하지만 브라우저-서버 간의 gRPC 통신이 지원되지 않는 단점이 존재하는데요. 이는 gRPC-Gateway를 이용하면 해결할 수 있습니다.
이번 포스팅에서는 gRPC-Gateway를 간략하게 알아보고 golang을 이용하여 실습을 진행해보도록 하겠습니다.
gRPC-Gateway 란?
gRPC-Gateway는 프로토콜 버퍼 컴파일러(protoc)의 플러그인입니다.
gRPC 서비스 정의(proto file) 를 읽고 리버스 프록시 서버를 생성하는데, 이 서버는 RESTful HTTP API를 gRPC로 변환해주는 역할을 하게됩니다.
위 그림은 서비스 정의를 이용하여 gRPC 클라이언트(Stub)와 리버스 프록시 서버를 생성한 뒤 RESTful HTTP API 요청을 gRPC로 변환하는 것을 잘 나타내고 있습니다.
사전작업
golang 설치
gRPC-Gateway 는 golang 만 지원되므로 다른 언어로 리버스 프록시 서버를 생성할 수 없습니다.
$ brew install golang
# 2020. 12. 31 기준으로 go1.15.6
$ go version
protobuf 설치
gRPC-Gateway 를 사용하기 위해서는
protoc
v3.0.0 이상을 설치하여야 합니다.
$ brew install protobuf
# 2020. 12. 31 기준으로 3.14.0
$ protoc --version
환경변수 설정
golang 관련 환경변수를 설정합니다.
$ vi ~/.zshrc
export GOROOT="$(brew --prefix golang)/libexec"
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin
export PATH=$PATH:$GOPATH/bin
$ source ~/.zshrc
GOROOT
- golang SDK의 위치를 의미합니다. Homebrew를 이용하여 설치하였기에 이에 해당하는 실제 설치 경로를 입력하였습니다.
GOPATH
- 3rd Party library가 설치되는 위치입니다.
프로젝트 생성
해당 글의 모든 소스코드는
macOS Big Sur
+Visual Studio Code
환경에서 작성 및 실행하였습니다.
프로젝트 생성에 완료하면 아래와 같은 프로젝트 구조를 가지게 됩니다.
.
├── go.mod
├── go.sum
├── main.go
└── proto
├── google
│ └── api
│ ├── annotations.proto
│ └── http.proto
└── hello
├── hello.pb.go
├── hello.pb.gw.go
├── hello.proto
└── hello_grpc.pb.go
extension 설치
golang 개발에 도움을 주는 extension 인 golang.go 을 설치합니다.
프로젝트 초기화
터미널을 열고 hello
디렉토리를 생성합니다.
$ mkdir hello
$ cd hello
go mod init
명령을 이용하여 모듈의 경로를 지정합니다. orgs
에 조직의 이름을 작성합니다. e.g.) musma
$ go mod init github.com/[orgs]/hello
go.mod
파일이 생성 된 것을 확인할 수 있습니다.
module github.com/[orgs]/hello
go 1.15
패키지 설치
프로토콜 버퍼 컴파일러가 gRPC 서비스 정의를 읽고 컴파일하기 위해서 필요한 종속 패키지들을 설치합니다.
$ go get \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc
Proto 파일 생성
hello.proto 생성
proto
디렉토리와 하위에 hello
디렉토리를 생성 후 hello.proto
파일을 생성합니다.
$ mkdir -p proto/hello
$ cd proto/hello
$ touch hello.proto
간단하게 HelloRequest
받아 HelloResponse
를 반환하는 SayHello
RPC 메서드를 작성합니다.
orgs
에 조직의 이름을 작성합니다. e.g.) musma
syntax = "proto3";
package hello;
option go_package = "github.com/[orgs]/hello/proto/hello";
import "google/api/annotations.proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse) {
option (google.api.http) = {
post: "/echo"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
annotations.proto 생성
gRPC 서비스가 JSON 요청 및 응답에 매핑되는 방식을 정의한 annotations.proto
파일을 생성하겠습니다.
우선 proto
디렉토리에 하위에 google/api
디렉토리를 생성합니다.
$ cd ../..
$ mkdir -p proto/google/api
$ cd proto/google/api
$ touch annotations.proto
이 후, 여기 의 소스코드를 복사하여 파일을 생성합니다.
http.proto 생성
서비스 정의(proto file)를 이용하여 리버스 프록시 서버를 생성하기 위해서는 gRPC 메서드를 HTTP 리소스에 매핑하여야 합니다. 따라서 http.proto
를 생성합니다.
여기 의 소스코드를 복사하여 google/api
디렉토리에 http.proto
파일을 생성합니다.
$ touch http.proto
gRPC 클라이언트(Stub) 생성
프로토콜 버퍼 컴파일러를 이용하여 golang 에 해당하는 스텁을 생성합니다.
$ cd ../../..
$ protoc -I ./proto \
--go_out ./proto --go_opt paths=source_relative \
--go-grpc_out ./proto --go-grpc_opt paths=source_relative \
./proto/hello/hello.proto
hello_grpc.pb.go
파일과 hello.ph.go
파일이 생성된 것을 볼 수 있습니다.
이후 스텁에 필요한 패키지를 다운받기 위하여 go mod tidy
명령어를 실행합니다.
$ go mod tidy
리버스 프록시 서버 생성
RESTful HTTP API 클라이언트 호출 지원용 리버스 프록시 서버를 생성하기 위하여 아래와 같이 입력합니다.
$ protoc -I ./proto \
--grpc-gateway_out ./proto \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt paths=source_relative \
--grpc-gateway_opt generate_unbound_methods=true \
./proto/hello/hello.proto
동일한 위치에 hello.pb.gw.go
파일이 생성된 것을 볼 수 있습니다.
이후 리버스 프록시 서버에 필요한 패키지를 다운받기 위하여 go mod tidy
명령어를 실행합니다.
$ go mod tidy
main.go 생성
리버스 프록시 서버를 실행시키기 위한 main.go
를 루트 디렉토리에 작성합니다.
$ tocuh main.go
package main
import (
"context"
"log"
"net"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
hellopb "guthub.com/[orgs]/hello/proto/hello"
)
type server struct {
hellopb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *hellopb.HelloRequest) (*hellopb.HelloResponse, error) {
return &hellopb.HelloResponse{Message: in.Name + " world"}, nil
}
func main() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
s := grpc.NewServer()
hellopb.RegisterGreeterServer(s, &server{})
log.Println("Serving gRPC on 0.0.0.0:8080")
go func() {
log.Fatalln(s.Serve(lis))
}()
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()
err = hellopb.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())
}
테스트
필요한 파일을 모두 생성하였으므로 테스트를 진행해보겠습니다. 먼저 gRPC 서버와 HTTP 서버를 실행합니다.
$ go run main.go
이어서 curl 을 이용하여 HTTP 요청을 진행합니다.
$ curl -X POST http://localhost:8090/echo -d '{"name": " Hello"}'
{"message":" Hello world"}
마치며
여기 에서도 언급하였지만 gRPC-Gateway 는 현재 golang 에서만 지원되고있습니다. 어서 빨리 Node.js 를 포함한 다른 언어들을 지원하여 조금 더 익숙한 언어로 사용할 수 있으면 좋을 것 같습니다