init 251117
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# 인터페이스 정의 언어(IDL) 파일 정의하기
|
||||
|
||||
인터페이스 정의 언어(IDL) 파일을 정의하는 방법을 배워보겠습니다.
|
||||
|
||||
우리가 개발할 gRPC 서비스는 다음 기능을 지원할 것입니다:
|
||||
|
||||
- 서버는 클라이언트에게 현재 날짜와 시간을 반환해야 합니다.
|
||||
- 서버는 클라이언트에게 주어진 길이의 무작위로 생성된 비밀번호를 반환해야 합니다.
|
||||
- 서버는 클라이언트에게 무작위 정수를 반환해야 합니다.
|
||||
|
||||
gRPC 클라이언트와 서버 개발을 시작하기 전에, IDL 파일을 먼저 정의해야 합니다. IDL 파일과 관련된 파일들을 관리하기 위해 별도의 GitHub 저장소가 필요하며, 여기서는 https://github.com/Educative-Content/protoapi 를 사용합니다.
|
||||
|
||||
## IDL 파일의 구조
|
||||
|
||||
다음은 `protoapi.proto`라는 IDL 파일의 내용입니다:
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
```
|
||||
|
||||
이 파일은 프로토콜 버퍼 언어의 **proto3** 버전을 사용합니다. 이전 버전인 **proto2**도 있으며, 약간의 문법적 차이가 있습니다. `proto3`를 명시하지 않으면, 프로토콜 버퍼 컴파일러는 **proto2**를 사용하는 것으로 간주합니다. 버전 정의는 `.proto` 파일의 첫 번째 비어있지 않은, 주석이 아닌 라인에 위치해야 합니다.
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./;protoapi";
|
||||
```
|
||||
|
||||
gRPC 도구들은 이 `.proto` 파일로부터 Go 코드를 생성할 것입니다. 위 라인은 생성될 Go 패키지의 이름이 `protoapi`임을 명시합니다. `./`를 사용했기 때문에 출력 파일은 `protoapi.proto`와 동일한 현재 디렉토리에 생성됩니다.
|
||||
|
||||
```proto
|
||||
service Random {
|
||||
rpc GetDate (RequestDateTime) returns (DateTime);
|
||||
rpc GetRandom (RandomParams) returns (RandomInt);
|
||||
rpc GetRandomPass (RequestPass) returns (RandomPass);
|
||||
}
|
||||
```
|
||||
|
||||
이 블록은 gRPC 서비스의 이름(`Random`)과 지원하는 메서드들을 명시합니다. 또한, 각 상호작용에 필요한 메시지들을 지정합니다. 예를 들어, `GetDate`의 경우 클라이언트는 `RequestDateTime` 메시지를 보내고 `DateTime` 메시지를 받기를 기대합니다.
|
||||
|
||||
이 메시지들은 동일한 `.proto` 파일에 정의되어 있습니다.
|
||||
|
||||
```proto
|
||||
// For random number
|
||||
```
|
||||
|
||||
모든 `.proto` 파일은 C와 C++ 스타일의 주석을 지원합니다. 즉, `// text`와 `/* text */` 형식의 주석을 사용할 수 있습니다.
|
||||
|
||||
```proto
|
||||
message RandomParams {
|
||||
int64 Seed = 1;
|
||||
int64 Place = 2;
|
||||
}
|
||||
```
|
||||
|
||||
난수 생성기는 시드(seed) 값으로 시작하며, 이 값은 클라이언트가 지정하여 `RandomParams` 메시지를 통해 서버로 전송됩니다. `Place` 필드는 무작위로 생성된 정수 시퀀스에서 반환될 난수의 위치를 지정합니다.
|
||||
|
||||
```proto
|
||||
message RandomInt {
|
||||
int64 Value = 1;
|
||||
}
|
||||
```
|
||||
|
||||
앞선 두 메시지는 `GetRandom` 메서드와 관련이 있습니다. `RandomParams`는 요청의 매개변수를 설정하는 데 사용되고, `RandomInt`는 서버가 생성한 난수를 저장하는 데 사용됩니다. 모든 메시지 필드는 `int64` 데이터 타입을 가집니다.
|
||||
|
||||
```proto
|
||||
message DateTime {
|
||||
string Value = 1;
|
||||
}
|
||||
|
||||
message RequestDateTime {
|
||||
string Value = 2;
|
||||
}
|
||||
```
|
||||
|
||||
위 두 메시지는 `GetDate` 메서드의 동작을 지원하기 위한 것입니다. `RequestDateTime` 메시지는 실질적인 데이터를 담고 있지 않은 더미 메시지입니다. 단지 클라이언트가 서버로 보내는 메시지가 필요할 뿐이며, `Value` 필드에는 어떤 종류의 정보든 저장할 수 있습니다. 서버가 반환하는 정보는 `DateTime` 메시지에 `string` 값으로 저장됩니다.
|
||||
|
||||
```proto
|
||||
// For random password
|
||||
message RequestPass {
|
||||
int64 Seed = 1;
|
||||
int64 Length = 8;
|
||||
}
|
||||
|
||||
message RandomPass {
|
||||
string Password = 1;
|
||||
}
|
||||
```
|
||||
|
||||
마지막으로, 위 두 메시지는 `GetRandomPass`의 동작을 위한 것입니다.
|
||||
|
||||
요약하자면, IDL 파일은 다음을 수행합니다:
|
||||
- `proto3`를 사용함을 명시합니다.
|
||||
- 서비스의 이름이 `Random`임을 정의합니다.
|
||||
- 생성될 Go 패키지의 이름이 `protoapi`임을 명시합니다.
|
||||
- gRPC 서비스가 `GetDate`, `GetRandom`, `GetRandomPass` 세 가지 메서드를 지원함을 정의하고, 이 메서드 호출에서 교환될 메시지들의 이름을 정의합니다.
|
||||
- 데이터 교환에 사용될 여섯 가지 메시지의 형식을 정의합니다.
|
||||
|
||||
## Go에서 IDL 파일 사용하기
|
||||
|
||||
다음 중요한 단계는 이 파일을 Go에서 사용할 수 있는 형식으로 변환하는 것입니다. `protoapi.proto`나 다른 `.proto` 파일을 처리하여 관련된 Go `.pb.go` 파일을 생성하기 위해 몇 가지 추가 도구를 다운로드해야 합니다. 프로토콜 버퍼 컴파일러 바이너리의 이름은 `protoc`입니다. macOS에서는 `brew install protobuf` 명령을 사용하여 `protoc`를 설치해야 합니다. 마찬가지로, Homebrew를 사용하여 `protoc-gen-go-grpc`와 `protoc-gen-go` 패키지도 설치해야 합니다. 이 두 패키지는 Go와 관련이 있습니다.
|
||||
|
||||
Linux에서는 선호하는 패키지 관리자를 사용하여 `protobuf`를 설치하고, `go install github.com/golang/protobuf/protoc-gen-go@latest` 명령을 사용하여 `protoc-gen-go`를 설치해야 합니다. 마찬가지로, `go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest`를 실행하여 `protoc-gen-go-grpc` 실행 파일을 설치해야 합니다.
|
||||
|
||||
> 참고: Go 1.16부터는 모듈 모드에서 패키지를 빌드하고 설치하는 데 `go install`을 사용하는 것이 권장됩니다. `go get`의 사용은 더 이상 사용되지 않습니다. `go install`을 사용할 때는 최신 버전을 설치하기 위해 패키지 이름 뒤에 `@latest`를 추가하는 것을 잊지 마세요.
|
||||
|
||||
- protoc
|
||||
- protoc-gen-go
|
||||
- protoc-gen-go-grpc
|
||||
|
||||
변환 과정은 다음 단계를 필요로 합니다:
|
||||
|
||||
```bash
|
||||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. \
|
||||
--go-grpc_opt=paths=source_relative protoapi.proto
|
||||
```
|
||||
|
||||
이 명령을 실행하면, GitHub 저장소의 루트 디렉토리에 `protoapi_grpc.pb.go`와 `protoapi.pb.go`라는 두 개의 파일이 생성됩니다. `protoapi.pb.go` 소스 코드 파일에는 메시지가 포함되어 있고, `protoapi_grpc.pb.go`에는 서비스가 포함되어 있습니다.
|
||||
|
||||
`protoapi_grpc.pb.go`의 첫 열 줄은 다음과 같습니다:
|
||||
|
||||
```go
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package protoapi
|
||||
```
|
||||
|
||||
앞서 논의했듯이, 패키지 이름은 `protoapi`입니다.
|
||||
|
||||
```go
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
```
|
||||
|
||||
이것은 `import` 블록입니다. `context "context"`가 있는 이유는 `context`가 예전에는 표준 Go 라이브러리의 일부가 아닌 외부 Go 패키지였기 때문입니다.
|
||||
|
||||
`protoapi.pb.go`의 첫 줄은 다음과 같습니다:
|
||||
|
||||
```go
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v3.17.3
|
||||
// source: protoapi.proto
|
||||
|
||||
package protoapi
|
||||
```
|
||||
|
||||
`protoapi_grpc.pb.go`와 `protoapi.pb.go`는 모두 `protoapi` Go 패키지의 일부이므로, 코드에서 한 번만 포함하면 됩니다.
|
||||
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"grpccanary/protoapi"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func AskingDateTime(ctx context.Context, m protoapi.RandomClient) (*protoapi.DateTime, error) {
|
||||
request := &protoapi.RequestDateTime{
|
||||
Value: "Please send me the date and time",
|
||||
}
|
||||
|
||||
return m.GetDate(ctx, request)
|
||||
}
|
||||
|
||||
func AskPass(ctx context.Context, m protoapi.RandomClient, seed int64, length int64) (*protoapi.RandomPass, error) {
|
||||
request := &protoapi.RequestPass{
|
||||
Seed: seed,
|
||||
Length: length,
|
||||
}
|
||||
|
||||
return m.GetRandomPass(ctx, request)
|
||||
}
|
||||
|
||||
func AskRandom(ctx context.Context, m protoapi.RandomClient, seed int64, place int64) (*protoapi.RandomInt, error) {
|
||||
request := &protoapi.RandomParams{
|
||||
Seed: seed,
|
||||
Place: place,
|
||||
}
|
||||
|
||||
return m.GetRandom(ctx, request)
|
||||
}
|
||||
|
||||
func ClientRun(addr string) {
|
||||
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
fmt.Println("Dial:", err)
|
||||
return
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
seed := int64(rand.Intn(100))
|
||||
|
||||
client := protoapi.NewRandomClient(conn)
|
||||
r, err := AskingDateTime(context.Background(), client)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Server Date and Time:", r.Value)
|
||||
|
||||
length := int64(rand.Intn(20))
|
||||
p, err := AskPass(context.Background(), client, 100, length+1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Random Password:", p.Password)
|
||||
|
||||
place := int64(rand.Intn(100))
|
||||
i, err := AskRandom(context.Background(), client, seed, place)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Random Integer 1:", i.Value)
|
||||
|
||||
k, err := AskRandom(context.Background(), client, seed, place-1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Random Integer 2:", k.Value)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"grpccanary/protoapi"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
var min = 0
|
||||
var max = 100
|
||||
var port = ":8080"
|
||||
|
||||
func random(min, max int, src rand.Source) int {
|
||||
return rand.New(src).Intn(max-min) + min
|
||||
}
|
||||
|
||||
// Extra function for creating secure random numbers
|
||||
//
|
||||
// func randomSecure(min, max int) int {
|
||||
// v, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return min
|
||||
// }
|
||||
// fmt.Println("**", v, min, max)
|
||||
|
||||
// return min + int(v.Uint64())
|
||||
// }
|
||||
|
||||
func getString(len int64) string {
|
||||
temp := ""
|
||||
startChar := "!"
|
||||
var i int64 = 1
|
||||
for {
|
||||
// For getting valid ASCII characters
|
||||
myRand := random(0, 94, rand.NewSource(time.Now().UnixNano()))
|
||||
newChar := string(startChar[0] + byte(myRand))
|
||||
temp = temp + newChar
|
||||
if i == len {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
type RandomServer struct {
|
||||
protoapi.UnimplementedRandomServer
|
||||
}
|
||||
|
||||
func (RandomServer) GetDate(ctx context.Context, r *protoapi.RequestDateTime) (*protoapi.DateTime, error) {
|
||||
currentTime := time.Now()
|
||||
response := &protoapi.DateTime{
|
||||
Value: currentTime.String(),
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (RandomServer) GetRandom(ctx context.Context, r *protoapi.RandomParams) (*protoapi.RandomInt, error) {
|
||||
src := rand.NewSource(r.GetSeed())
|
||||
place := r.GetPlace()
|
||||
temp := random(min, max, src)
|
||||
for {
|
||||
place--
|
||||
if place <= 0 {
|
||||
break
|
||||
}
|
||||
temp = random(min, max, src)
|
||||
}
|
||||
|
||||
response := &protoapi.RandomInt{
|
||||
Value: int64(temp),
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (RandomServer) GetRandomPass(ctx context.Context, r *protoapi.RequestPass) (*protoapi.RandomPass, error) {
|
||||
rand.Seed(r.GetSeed())
|
||||
temp := getString(r.GetLength())
|
||||
|
||||
response := &protoapi.RandomPass{
|
||||
Password: temp,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func ServerRun(addr string) {
|
||||
server := grpc.NewServer()
|
||||
var randomServer RandomServer
|
||||
protoapi.RegisterRandomServer(server, randomServer)
|
||||
|
||||
reflection.Register(server)
|
||||
|
||||
listen, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Serving requests...")
|
||||
server.Serve(listen)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package httpentity
|
||||
@@ -0,0 +1,83 @@
|
||||
package httpentity
|
||||
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "strings"
|
||||
|
||||
// "github.com/gin-gonic/gin"
|
||||
// )
|
||||
|
||||
// func NewWebServer(addr string) *http.Server {
|
||||
// srv := &http.Server{
|
||||
// Addr: addr,
|
||||
// Handler: createRouter(),
|
||||
// }
|
||||
|
||||
// return srv
|
||||
// }
|
||||
|
||||
// func createRouter() *gin.Engine {
|
||||
// // Create a new gin router for api
|
||||
// // What is difference between gin.Default() and gin.New()?
|
||||
// // https://stackoverflow.com/questions/44318441/what-is-difference-between-gin-default-and-gin-new
|
||||
|
||||
// apiEngine := gin.New()
|
||||
// apiGroup := apiEngine.Group("/api")
|
||||
// {
|
||||
// apiGroup.GET("/randomNumber", GET_RandomNumber)
|
||||
// apiGroup.GET("/randomPassword", GET_RandomPassword)
|
||||
// apiGroup.GET("/randomDate", GET_RandomDate)
|
||||
// }
|
||||
|
||||
// // create a new gin router for static files
|
||||
// staticEngine := gin.New()
|
||||
// staticEngine.Static("/", "./web")
|
||||
|
||||
// // Create a new gin router
|
||||
// r := gin.Default()
|
||||
// // r can accept all messages from apiEngine and staticEngine
|
||||
// r.Any("/*any", func(c *gin.Context) {
|
||||
// defer handleError(c)
|
||||
// w := c.Writer
|
||||
// w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
// w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
|
||||
// path := c.Param("any")
|
||||
|
||||
// if strings.HasPrefix(path, "/api") {
|
||||
// apiEngine.ServeHTTP(c.Writer, c.Request)
|
||||
// } else {
|
||||
// staticEngine.HandleContext(c)
|
||||
// }
|
||||
|
||||
// })
|
||||
|
||||
// // Return the router
|
||||
// return r
|
||||
// }
|
||||
|
||||
// func GET_RandomNumber(c *gin.Context) {
|
||||
|
||||
// // make a json decoder
|
||||
// dec := json.NewDecoder(c.Request.Body)
|
||||
// obj := map[string]interface{}{}
|
||||
// dec.Decode(&obj)
|
||||
|
||||
// seed := obj["seed"]
|
||||
// place := obj["place"]
|
||||
|
||||
// response := map[string]interface{}{
|
||||
// "value": 10,
|
||||
// }
|
||||
|
||||
// c.JSON(http.StatusOK)
|
||||
// }
|
||||
|
||||
// func handleError(c *gin.Context) {
|
||||
// if r := recover(); r != nil {
|
||||
// log.Println(r)
|
||||
// c.String(http.StatusBadRequest, r.(error).Error())
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,62 @@
|
||||
package jsonexample
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
History []string `json:"history"`
|
||||
}
|
||||
|
||||
func JsonParsingExample() {
|
||||
obj := map[string]interface{}{
|
||||
"name": "홍길동",
|
||||
"age": 623,
|
||||
"history": []string{
|
||||
"1900-양반집을 털었다",
|
||||
"1910-왕에게 잡혀감",
|
||||
},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
obj2 := map[string]interface{}{}
|
||||
err = json.Unmarshal(b, &obj2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(obj2["age"])
|
||||
|
||||
p := Person{
|
||||
Name: "Godopu2",
|
||||
Age: 70,
|
||||
History: []string{
|
||||
"1900-양반집을 털었다",
|
||||
"1910-왕에게 잡혀감",
|
||||
},
|
||||
}
|
||||
b, err = json.Marshal(&p)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
var p2 Person
|
||||
err = json.Unmarshal(b, &p2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(p2.Age)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
entity "grpccanary/examples/grpcentity"
|
||||
"grpccanary/examples/jsonexample"
|
||||
"time"
|
||||
)
|
||||
|
||||
var port = ":8080"
|
||||
|
||||
func main() {
|
||||
jsonexample.JsonParsingExample()
|
||||
}
|
||||
|
||||
func grpcSample() {
|
||||
go entity.ServerRun(port)
|
||||
|
||||
// Just to be sure that the server is running
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
fmt.Println("Client:")
|
||||
entity.ClientRun("localhost" + port)
|
||||
}
|
||||
Reference in New Issue
Block a user