twirp ์‹œ์ž‘ํ•˜๊ธฐ ๐Ÿฆœ

9320 ๋‹จ์–ด apigogrpc
Twirp๋Š” gRPC์˜ ๋ชจ๋“  ์—„๊ฒฉํ•œ ์œ ํ˜•์˜ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ ํ†ต์‹ ์„ ์œ„ํ•œ HTTP/JSON์˜ ์œ ์—ฐ์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

ํ•˜๋‚˜์˜ protobuf ํŒŒ์ผ*.proto์—์„œ ๊ฒฝ๋กœ, ์š”์ฒญ ๋ฐ ์‘๋‹ต์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ API์˜ ๋ชจ๋“  ์†Œ๋น„์ž๋Š” protobuf ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ค๊ณ  ์„ ํƒํ•œ ์–ธ์–ด๋กœ ์ž์ฒด ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋น ๋ฅด๊ณ  ์˜ˆ์ œ๋ฅผ ์„ค์ •ํ•ด ๋ด…์‹œ๋‹ค.

๋จผ์ € protobuf๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•˜๋ฏ€๋กœ Mac์—์„œ๋Š”

brew install protobuf


์ด๋ฅผ ์„ค์น˜ํ–ˆ์œผ๋ฉด golang ์„ค์ •๊ณผ GOBIN๊ฐ€ ์ •์˜๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ .zshrc์— ์ถ”๊ฐ€ํ•  ํ•ฉ๋ฆฌ์ ์ธ ๊ธฐ๋ณธ๊ฐ’์ž…๋‹ˆ๋‹ค.

GOBIN="~/go/bin"
PATH="$PATH:~/go/bin"


๋‹ค์Œ์œผ๋กœ protobuf ๋ฐ”์ด๋„ˆ๋ฆฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•˜์—ฌ twirp ๋ฐ golang์šฉ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

go install github.com/twitchtv/twirp/protoc-gen-twirp@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest


"ProxyMe"๋ผ๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ด๋ฅผ ์˜ˆ๋กœ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹œ์ž‘ํ•  ๊ฒฝ๋กœ๋ฅผ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ„๋‹จํ•œ protobuf ํŒŒ์ผ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

syntax = "proto3";
package reaz.io.proxyme;
option go_package = "reaz.io/proxyme";

service ProxyMe {
  rpc ListProxies(ListReq) returns (ListRes);
}

message ListReq {
  string subject = 1;
}

message ListRes {
  string text = 1;
}


์ฃผ๋ชฉํ•ด์•ผ ํ•  ์ค‘์š”ํ•œ ๊ฒƒ์€;

# We'll need this later, this is just a namespace for my projects, yours might be more like '{username}.com.{project_name}'
package reaz.io.proxyme;


๊ทธ๋Ÿฐ ๋‹ค์Œ ๋‚˜๋จธ์ง€ ํŒŒ์ผ์€ ListProxies (๋ชฉ๋ก ์š”์ฒญ)์„ ์ˆ˜๋ฝํ•˜๊ณ  ListReq ๋ชฉ๋ก ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‹จ์ผListReq ์—”๋“œํฌ์ธํŠธ๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ twirp์™€ protobuf๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ถ€ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์Šค์บํด๋”ฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

protoc --go_out=. --twirp_out=. ProxyMe.proto


์ด ์‹œ์ ์—์„œ ์šฐ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๊ฐ–๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

โ”œโ”€โ”€ ProxyMe.proto
โ””โ”€โ”€ reaz.io
    โ””โ”€โ”€ proxyme
        โ”œโ”€โ”€ ProxyMe.pb.go
        โ””โ”€โ”€ ProxyMe.twirp.go

2 directories, 3 files


์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ ์›๋ณธ ํŒŒ์ผ ProxyMe.proto ๊ณผ go ์ƒ์„ฑ ๋ถ€๋ถ„์ธ .pb.go ์™€ HTTP ์„œ๋น„์Šค๋ฅผ ํฌํ•จํ•˜๋Š” twirp ๋งค์ง์ธ *.twirp.go ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋ฐ ์‚ฌ์šฉํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด server.go๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

package main

import (
    "context"
    "log"
    "net/http"

    pb "proxyme/reaz.io/proxyme"
)

type ProxyMeServer struct{}

func (s *ProxyMeServer) ListProxies(ctx context.Context, req *pb.ListReq) (*pb.ListRes, error) {
    return &pb.ListRes{Text: "Hello " + req.Subject}, nil
}

const PORT = "8011"

// Run the implementation in a local server
func main() {
    twirpHandler := pb.NewProxyMeServer(&ProxyMeServer{})
    // You can use any mux you like - NewHelloWorldServer gives you an http.Handler.
    mux := http.NewServeMux()
    // The generated code includes a method, PathPrefix(), which
    // can be used to mount your service on a mux.
    mux.Handle(twirpHandler.PathPrefix(), twirpHandler)
    log.Println("Listening on http://0.0.0.0:" + PORT + twirpHandler.PathPrefix())
    err := http.ListenAndServe(":"+PORT, mux)
    if err != nil {
        log.Fatal(err)
        return
    }
}


๊ทธ๊ฒŒ ์ „๋ถ€์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋Œ€๋ถ€๋ถ„ ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ListProxies , ListReq ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ListRes ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$ go run server.go
2022/04/18 07:07:16 Listening on http://0.0.0.0:8011/twirp/reaz.io.proxyme.ProxyMe/


์‹คํ–‰ ์ค‘์ธ ์ƒํƒœ์—์„œ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

curl --request POST \
  --url http://0.0.0.0:8011/twirp/reaz.io.proxyme.ProxyMe/ListProxies \
  --header 'Content-Type: application/json' \
  --data '{
    "subject": "mike"
}'

# Should see the following for a response
{
  "text": "Hello mike"
}


์ด์ œ ๋‹ค๋ฅธ ์–ธ์–ด๋กœ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ƒˆ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

Clients in other languages can also be generated by using the respective protoc plugins defined by their languages, for example --twirp_ruby_out.



๊ทธ๋ฆฌ๊ณ  ํ•˜๋‚˜์˜ protobuf ํŒŒ์ผ์—์„œ ์ƒ์„ฑ๋œ protobuf ๋˜๋Š” json์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

how to generate client code here์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ