Open robert-min opened 1 year ago
UserGetRequest
타입의 입력 메세지를 매겨변수로 받고UserGetReply
타입의 메시지를 반환// ./service/users.proto
syntax = "proto3";
option go_package = "github.com/handson-go/chap8/user_service/service";
service Users {
rpc GetUser (UserGetRequest) returns (UserGetReply) {}
}
message UserGetRequest {
string email = 1;
string id = 2;
}
message User {
string id = 1;
string first_name = 2;
string last_name = 3;
int32 age = 4;
}
message UserGetReply {
User user = 1;
}
--go_out, go-rpc_out
옵션은 생성된 magic glue(프로토콜 버퍼 정의, 서버와 클라이언트 어플리케이션 구현체, 네트워크 상 두 장비가 통신 가능한 형태의 포로토콜 버퍼 데이터 세가지를 묶어주는 역할)이 저장될 경로--go_opt, --go-grpc_opt
옵션은 users.proto 파일 기준 상대 경로에 위치한 곳에 생성protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
users.proto
grpc
라이브러리 의존성 부여를 위해 해당 파일이 있는 경로에서 아래 명령어 실행go get google.golang.org/grpc@v1.37.0
package main
import (
"context"
"errors"
"log"
"net"
"os"
"strings"
users "github.com/handson-go/chap8/user_service/service"
"google.golang.org/grpc"
)
type userService struct {
users.UnimplementedUsersServer
}
func (s *userService) GetUser(ctx context.Context, in *users.UserGetRequest) (*users.UserGetReply, error) {
log.Printf(
"Received request for user with Email: %s Id: %s\n",
in.Email,
in.Id,
)
components := strings.Split(in.Email, "@")
if len(components) != 2 {
return nil, errors.New("invalid email address")
}
u := users.User{
Id: in.Id,
FirstName: components[0],
LastName: components[1],
Age: 36,
}
return &users.UserGetReply{User: &u}, nil
}
func registerServices(s *grpc.Server) {
users.RegisterUsersServer(s, &userService{})
}
func startServer(s *grpc.Server, l net.Listener) error {
return s.Serve(l)
}
func main() {
listenAddr := os.Getenv("LISTEN_ADDR")
if len(listenAddr) == 0 {
listenAddr = ":50051"
}
lis, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
registerServices(s)
log.Fatal(startServer(s, lis))
}
// ./go.mod
replace github.com/handson-go/chap8/user_service/service => ../service
// sh
go mod tidy
grpc.DialContext()
함수로 Connection 객체 생성 후 통신을 위해 getUserServiceClient
함수 작성
package main
import ( "context" "fmt" "log" "os"
users "github.com/handson-go/chap8/user_service/service"
"google.golang.org/grpc"
)
// Set up Grpc Connection with Server func setupGrpcConn(addr string) (*grpc.ClientConn, error) { return grpc.DialContext( context.Background(), // 공백의 Context addr, // 연결할 서버의 주소 grpc.WithInsecure(), // 전송계층보안 사용하지 않고 서버와 통신을 수립하기 위해 사용 grpc.WithBlock(), // 함수가 반환되기 전 연결이 먼저 수립외더 연결 객체가 반환되도록 설정 ) }
func getUserServiceClient(conn *grpc.ClientConn) users.UsersClient { return users.NewUsersClient(conn) }
func getUser( client users.UsersClient, u users.UserGetRequest, ) (users.UserGetReply, error) { return client.GetUser(context.Background(), u) }
func main() { if len(os.Args) != 2 { log.Fatal( "Must specify a gRPC server address", ) } conn, err := setupGrpcConn(os.Args[1]) if err != nil { log.Fatal(err) } defer conn.Close()
c := getUserServiceClient(conn)
result, err := getUser(
c,
&users.UserGetRequest{Email: "kim@naver.com"},
)
if err != nil {
log.Fatal(err)
}
fmt.Fprintf(
os.Stdout, "User: %s %s \n",
result.User.FirstName, result.User.LastName,
)
}
package main
import (
"context"
"log"
"net"
"testing"
users "github.com/handson-go/chap8/user_service/service"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
// Start test GRPC server with real network listener
func startTestGrpcServer() (*grpc.Server, *bufconn.Listener) {
l := bufconn.Listen(10)
s := grpc.NewServer()
registerServices(s)
go func() {
err := startServer(s, l)
if err != nil {
log.Fatal(err)
}
}()
return s, l
}
func TestUserService(t *testing.T) {
s, l := startTestGrpcServer()
defer s.GracefulStop()
// 다이얼러 생성
bufconnDialer := func(ctx context.Context, add string) (net.Conn, error) {
return l.Dial()
}
// 테스트서버 연결을 위한 클라이언트 생성
client, err := grpc.DialContext(
context.Background(),
"",
grpc.WithInsecure(),
grpc.WithContextDialer(bufconnDialer), // 네트워크를 인메모리 연결로 구성
)
if err != nil {
t.Fatal(err)
}
usersClient := users.NewUsersClient(client)
resp, err := usersClient.GetUser(
context.Background(),
&users.UserGetRequest{
Email: "kim@naver.com",
Id: "foo-bar",
},
)
if err != nil {
t.Fatal(err)
}
if resp.User.FirstName != "kim" {
t.Errorf(
"Expected FirstName to be : kim, Got : %s",
resp.User.FirstName,
)
}
}
package main
import (
"context"
"log"
"net"
"testing"
users "github.com/handson-go/chap8/user_service/service"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
type dummyUserService struct {
users.UnimplementedUsersServer
}
func (s *dummyUserService) GetUser(ctx context.Context, in *users.UserGetRequest) (*users.UserGetReply, error) {
u := users.User{
Id: "user-123-a",
FirstName: "kim",
LastName: "min",
Age: 36,
}
return &users.UserGetReply{User: &u}, nil
}
func startTestGrpcServer() (*grpc.Server, *bufconn.Listener) {
l := bufconn.Listen(10)
s := grpc.NewServer()
users.RegisterUsersServer(s, &dummyUserService{})
go func() {
err := s.Serve(l)
if err != nil {
log.Fatal(err)
}
}()
return s, l
}
func TestGetUser(t *testing.T) {
s, l := startTestGrpcServer()
defer s.GracefulStop()
// 다이얼러 생성
bufconnDialer := func(ctx context.Context, add string) (net.Conn, error) {
return l.Dial()
}
// 테스트서버 연결을 위한 클라이언트 생성
conn, err := grpc.DialContext(
context.Background(),
"",
grpc.WithInsecure(),
grpc.WithContextDialer(bufconnDialer), // 네트워크를 인메모리 연결로 구성
)
if err != nil {
t.Fatal(err)
}
c := getUserServiceClient(conn)
result, err := getUser(
c,
&users.UserGetRequest{Email: "kim@min.com"},
)
if err != nil {
t.Fatal(err)
}
if result.User.FirstName != "kim" || result.User.LastName != "min" {
t.Fatalf(
"Expected: kim doe, min: %s %s",
result.User.FirstName,
result.User.LastName,
)
}
}
Marshal
Unmarshal
함수 사용// 위 코드 동일
func createUserRequest(jsonQuery string) (*users.UserGetRequest, error) {
u := users.UserGetRequest{}
input := []byte(jsonQuery)
return &u, protojson.Unmarshal(input, &u)
}
func getUserResponseJson(result *users.UserGetReply) ([]byte, error) {
return protojson.Marshal(result)
}
func main() {
if len(os.Args) != 3 {
log.Fatal(
"Must specify a gRPC server address and search query",
)
}
serverAddr := os.Args[1]
u, err := createUserRequest(os.Args[2])
if err != nil {
log.Fatalf("Bad user input : %v", err)
}
conn, err := setupGrpcConn(serverAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
c := getUserServiceClient(conn)
result, err := getUser(
c,
u,
)
if err != nil {
log.Fatal(err)
}
data, err := getUserResponseJson(result)
if err != nil {
log.Fatal(err)
}
fmt.Fprint(
os.Stdout, string(data),
)
}
RPC(Remote Procedure Call)
gRPC
protocol buff(protobuf)
를 사용