[번역] gRPC with Golang and Python
이 포스팅 python과 Golang으로
gRPC 통신을 하는 예제 튜토리얼입니다.
번역 상의 오류가 있을 수 있으니 참고하시기 바랍니다.
이번 포스팅에서는 gRPC를 사용하여 Golang과 Python 간의 통신을 생성하는 작은 애플리케이션은 만들기에 앞서 우선 gRPC에 대한 이해를 해야한다.
gRPC는 Google의 Remote Procedure Call의 약자이며 원격으로 실행되는 코드를 호출하는 데 사용되는 오픈소스 RPC 프레임워크이다. gRPC는 많은 클라이언트가 중앙 서버에서 서비스를 요청하고 받는 클라이언트-서버 아키텍처를 사용한다.
이러한 방식으로 클라이언트와 서버는 환경과 독립적으로 서로 통신할 수 있다. gRPC는 구조화된 데이터를 직렬 화하기 위해 전송 프로토콜 버퍼(매우 작고 인코딩 및 디코딩이 빠른 바이너리 직렬화 형식)에 HTTP/2를 사용한다
프로토콜 버퍼를 사용하기 때문에 가장 먼저 아래 링크에서 Protocol Buffer를 설치해야 한다.
참고로 이번에 만들 애플리케이션은, 국가 이름을 request로 받아 국가에 대한 정보(이름, 수도, 인구, 화폐)를 반환하는 간단한 프로젝트이다.
Protocol Buffer Download
github.com/protocolbuffers/protobuf/releases
Go Get Plugin
프로토콜 버퍼를 다운로드한 후에는, Go 코드로 작업할 수 있게 아래 2개의 플러그인을 추가해야 한다.
$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
$ go get -u google.golang.org/grpc
Let's Start Coding
새로운 폴더를 생성
$ GOPATH/src/grpc : 이곳이 이제 이번 프로젝트의 root folder가 된다.
그리고 2개의 서브 폴더가 있는데, 하나는 server , 또 다른 하나는 client이다.
그리고 server 폴더에는 Golang의 코드가, client 폴더에는 Python 코드가 존재할 것이다.
$ mkdir $GOPATH/src/grpc
$ mkdir $GOPATH/src/grpc/{client, server}
. proto 파일 생성
gRPC를 생성하기 위해서는 우선 .proto 파일을 생성해야 한다. 이 파일에는 프로토콜 버터 데이터 서명이 포함되어야 한다.
루트 디렉토리에서 countries.proto로 파일을 하나 생성한다.
$ touch $GOPATH/src/grpc/countries.proto
.proto 파일 작성
syntax = "proto3";
package countries;
service Country {
rpc Search (CountryRequest) returns (CountryResponse) {}
}
message CountryRequest {
string name = 1;
}
message Currencies {
string code = 1;
string name = 2;
string symbol = 3;
}
message CountryResponse {
string name = 1;
string alpha2Code = 2;
string capital = 3;
string subregion = 4;
int32 population = 5;
string nativeName = 6;
repeated Currencies currencies = 7;
}
> .proto 시작 부분에는 syntax를 통해 proto의 버전을 지정한다.
> 서비스 Country의 rpc 블록에는 구현되어야 하는 하나의 Search 매소드가 있으며, CountryRequest를 받아서 CountryResponse를 반환한다.
> Go와 Python은 .proto 코드를 이해하지 못한다. 이러한 언어로 작업하려면 proto 파일을 컴파일해야 한다.
> 컴파일하기 위해 protoc-gen-go를 설치한 것이다.
Creating Go code from .proto
# grpc 폴더로 이동
$ cd $GOPATH/src/grpc
# server 폴더 하위에 countries 폴더 생성
$ mkdir $GOPATH/src/grpc/server/countries
# .proto 파일 컴파일
$ protoc --go_out=plugins=grpc:server/countries countries.proto
> 위 명령어를 실행하면 /server/countries 아래에 countries.pb.go 파일이 생성된다.
> 생성된 코드는 변경해서는 안된다. 만약 변경이 필요하다면 .proto 파일에서 변경 한 다음 다시 컴파일 해아 한다.
> 어떤 언어가 .proto가 될 것인지는 중요하지 않으며 각 언어 플러그인에는 자체 출력이 있다.
[ Trouble Shooting 1 ]
protoc-gen-go 프로그램을 찾을 수 없음 혹은 실행이 안된다고 하는 에러 문구
>> GOPATH가 제대로 안 걸려있을 경우 위와 같은 에러 발생
>> 아래와 같이 GOPATH를 다시 설정
$ export GOROOT=/usr/local/go
$ export GOPATH=$HOME/go
$ export GOBIN=$GOPATH/bin
$ export PATH=$PATH:$GOROOT:$GOPATH:$GOBIN
[ Trouble Shooting 2 ]
go_package 옵션을 추가하라는 경고와 경로 입력 시 절대 경로로 지정하라는 경고
Building the server
$ touch $GOPATH/src/grpc/server/main.go
$ touch $GOPATH/src/grpc/server/main_test.go
main.go 파일 생성 (Server)
package main
import (
"encoding/json"
"grpc/server/countries"
"io/ioutil"
"log"
"net"
"net/http"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
func main() {
grpcServer := grpc.NewServer()
var server Server
countries.RegisterCountryServer(grpcServer, server)
listen, err := net.Listen("tcp", "0.0.0.0:3000")
if err != nil {
log.Fatalf("could not listen to 0.0.0.0:3000 %v", err)
}
log.Println("Server starting...")
log.Fatal(grpcServer.Serve(listen))
}
// Server is implementation proto interface
type Server struct{}
// Search function responsible to get the Country information
func (Server) Search(ctx context.Context, request *countries.CountryRequest) (*countries.CountryResponse, error) {
resp, err := http.Get("https://restcountries.eu/rest/v2/name/ " + request.Name)
if err != nil {
return nil, err
}
defer resp.Body.Close()
jsonData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var data []countries.CountryResponse
if err := json.Unmarshal(jsonData, &data); err != nil {
return nil, err
}
return &data[0], nil
}
main_test.go 파일 생성(Server)
package main
import (
"grpc/server/countries"
"log"
"testing"
"golang.org/x/net/context"
)
func TestCountry(t *testing.T) {
ctx := context.Background()
request := countries.CountryRequest{Name: "Brazil"}
server := Server{}
response, err := server.Search(ctx, &request)
if err != nil {
t.Error(err)
}
if response.Alpha2Code != "BR" {
t.Error("Different Country returned")
}
log.Println(response)
}
테스트 진행
$ cd $GOPATH/src/grpc/server
$ go test
> go test 입력 시 아래와 같은 화면을 볼 수 있다.
Creating Python code from .proto
이제 작동 중인 gRPC 서버를 가지고 있고, 테스트도 완료했다. 따라서 이제 Python언어를 사용하여 client를 생성할 것이다.
countries.proto 파일만 있고, gRPC 서버와 어떠한 접촉도 없다고 상상하면 사실 서버가 어떤 언어로 구축되었는지는 알 수 없다.
Library Download
$ pip install grpcio grpcio-tools
$ pip install protobuf
# Flask 웹 프레임워크
$ pip install Flask
Python 코드 생성
$ python -m grpc_tools.protoc -I. --python_out=client --grpc_python_out=client countries.proto
위 명령어를 실행하면“countries_pb2_grpc.py” 와 “countries_pb2.py” 2개의 파이썬 파일이 생성된다.
Python client 파일 생성
touch $GOPATH/src/grpc/client/app.py
> 이 클라이언트를 Python 스크립트 혹은 웹 애플리케이션으로 실행하도록 할 수 있다.
> gRPC 서버에 연결하는 한 실행 방법은 중요하지 않다.
> 이번에는 웹 애플리케이션으로 만들기 위해 웹 프레임워크인 Flask를 사용할 것이다.
app.py 작성
from flask import Flask
from flask import jsonify
app = Flask(__name__)
import grpc
import countries_pb2
import countries_pb2_grpc
def obj_to_dict(obj):return obj.__dict__
@app.route('/<countrie>')
def countrie(countrie):
print("Start service")
try:
channel = grpc.insecure_channel('localhost:3000')
stub = countries_pb2_grpc.CountryStub(channel)
countryRequest = countries_pb2.CountryRequest(name=countrie)
countryResponse = stub.Search(countryRequest)
return jsonify({
"name": countryResponse.name,
"alpha2Code": countryResponse.alpha2Code,
"capital": countryResponse.capital,
"subregion": countryResponse.subregion,
"population": countryResponse.population,
"nativeName": countryResponse.nativeName
})
except Exception as e:
print(e)
return e
if __name__ == '__main__':
app.run()
> client를 실행하기 전에 우선적으로 gRPC 서버를 구동해야 한다.
Final Test (Golang Server + Python Client)
# go server 실행
$ cd $GOPATH/src/grpc/server
$ go run main.go
# python web application 실행
$ cd $GOPATH/src/grpc/client
$ python app.py
# 요청테스트
$ curl http://localhost:5000/brazi
> 응답 결과
> 참고용 현재 디렉터리 구조
< 출처 링크 >
medium.com/@andersonborges_70700/grpc-with-golang-and-python-f5b7aa602d74