├── Makefile ├── README.md ├── client └── main.go ├── docker-compose.yml ├── go.mod ├── go.sum ├── pkg ├── graceful.go └── rabbitmq.go └── server └── main.go /Makefile: -------------------------------------------------------------------------------- 1 | sv: 2 | cd server && go run . --race -v 3 | 4 | cl: 5 | cd client && go run . --race -v -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RABBITMQ RPC (Request & Reply Pattern) 2 | 3 | Check this tutorial about rpc queue using **rabbitmq** [here](https://www.rabbitmq.com/tutorials/tutorial-six-python.html) and check this tutorial about messaging pattern request & reply [here](https://www.enterpriseintegrationpatterns.com/RequestReply.html), if you need tutorial about rabbitmq check my repo [here](https://github.com/restuwahyu13/node-rabbitmq), or if you need other example rpc pattern using node [here](https://github.com/restuwahyu13/node-rabbitmq-rpc), if you need old example code check branch `old-master` for old version and check `new-master` for style like example like `old-master`. 4 | 5 | ## Server RPC 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "log" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | "time" 16 | 17 | "github.com/bytedance/sonic" 18 | "github.com/jaswdr/faker" 19 | "github.com/lithammer/shortuuid" 20 | "github.com/sirupsen/logrus" 21 | "github.com/wagslane/go-rabbitmq" 22 | 23 | "github.com/restuwahyu13/go-rabbitmq-rpc/pkg" 24 | ) 25 | 26 | type Person struct { 27 | ID string `json:"id"` 28 | Name string `json:"name"` 29 | Country string `json:"country"` 30 | City string `json:"city"` 31 | PostCode string `json:"postcode"` 32 | } 33 | 34 | func main() { 35 | var ( 36 | queue string = "account" 37 | data Person = Person{} 38 | fk faker.Faker = faker.New() 39 | ) 40 | 41 | rabbit := pkg.NewRabbitMQ(&pkg.RabbitMQOptions{ 42 | Url: "amqp://restuwahyu13:restuwahyu13@localhost:5672/", 43 | Exchange: "amqp.direct", 44 | Concurrency: "5", 45 | }) 46 | 47 | rabbit.ConsumerRpc(queue, func(d rabbitmq.Delivery) (action rabbitmq.Action) { 48 | data.ID = shortuuid.New() 49 | data.Name = fk.App().Name() 50 | data.Country = fk.Address().Country() 51 | data.City = fk.Address().City() 52 | data.PostCode = fk.Address().PostCode() 53 | 54 | dataByte, err := sonic.Marshal(&data) 55 | if err != nil { 56 | logrus.Fatal(err.Error()) 57 | return 58 | } 59 | 60 | defer rabbit.ReplyToDeliveryPublisher(dataByte, d) 61 | return rabbitmq.Ack 62 | }) 63 | 64 | signalChan := make(chan os.Signal, 1) 65 | signal.Notify(signalChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGALRM, syscall.SIGINT) 66 | 67 | for { 68 | select { 69 | case sigs := <-signalChan: 70 | log.Printf("Received Signal %s", sigs.String()) 71 | os.Exit(15) 72 | 73 | break 74 | default: 75 | time.Sleep(time.Duration(time.Second * 3)) 76 | log.Println("...........................") 77 | 78 | break 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | ## Client RPC 85 | 86 | ```go 87 | package main 88 | 89 | import ( 90 | "encoding/json" 91 | "log" 92 | "net/http" 93 | "os" 94 | "strings" 95 | 96 | "github.com/bytedance/sonic" 97 | "github.com/jaswdr/faker" 98 | "github.com/lithammer/shortuuid" 99 | 100 | "github.com/restuwahyu13/go-rabbitmq-rpc/pkg" 101 | ) 102 | 103 | func main() { 104 | var ( 105 | queue string = "account" 106 | fk faker.Faker = faker.New() 107 | req map[string]interface{} = make(map[string]interface{}) 108 | user map[string]interface{} = make(map[string]interface{}) 109 | ) 110 | 111 | rabbit := pkg.NewRabbitMQ(&pkg.RabbitMQOptions{ 112 | Url: "amqp://restuwahyu13:restuwahyu13@localhost:5672/", 113 | Exchange: "amqp.direct", 114 | Concurrency: "5", 115 | }) 116 | 117 | router := http.NewServeMux() 118 | 119 | router.HandleFunc("/rpc", func(w http.ResponseWriter, r *http.Request) { 120 | w.Header().Set("Content-Type", "application/json") 121 | 122 | req["id"] = shortuuid.New() 123 | req["name"] = fk.App().Name() 124 | req["country"] = fk.Address().Country() 125 | req["city"] = fk.Address().City() 126 | req["postcode"] = fk.Address().PostCode() 127 | 128 | delivery, err := rabbit.PublisherRpc(queue, req) 129 | if err != nil { 130 | statCode := http.StatusUnprocessableEntity 131 | 132 | if strings.Contains(err.Error(), "Timeout") { 133 | statCode = http.StatusRequestTimeout 134 | } 135 | 136 | w.WriteHeader(statCode) 137 | w.Write([]byte(err.Error())) 138 | return 139 | } 140 | 141 | if err := sonic.Unmarshal(delivery, &user); err != nil { 142 | w.WriteHeader(http.StatusUnprocessableEntity) 143 | w.Write([]byte(err.Error())) 144 | return 145 | } 146 | 147 | json.NewEncoder(w).Encode(&user) 148 | }) 149 | 150 | err := pkg.Graceful(func() *pkg.GracefulConfig { 151 | return &pkg.GracefulConfig{Handler: router, Port: "4000"} 152 | }) 153 | 154 | if err != nil { 155 | log.Fatalf("HTTP Server Shutdown: %s", err.Error()) 156 | os.Exit(1) 157 | } 158 | } 159 | ``` 160 | ## Noted Important 161 | 162 | In `PublisherRpc` method for returning response from `ConsumerRpc` you can use like this for the alternative. 163 | 164 | I'm recommended for you use example code in branch `new-master`, you can use redis or memcached for store your response from `ConsumerRpc`, because in my case let say if you have 2 services like **Service A (BCA)** and **Service B (MANDIRI)**, **User One** payment this merchant using `bca` and **User Two** payment this merchant using `mandiri`, if one service is crash **Service A (BCA)** or **Service B (MANDIRI)**, User Two ca'nt get the response back wait until **Service A (BCA)** is timeout, because response `ConsumerRpc` store in the same channel and if channel is empty process is blocked by channel, and the big problem is you must restart you app or you can wait **Service A (BCA)** until is running. 165 | 166 | Why is it recommended for you to use example code in branch `new-master` ?, because this is unique and you can store and get a response based on your request **(corellelationID)**, and this is not stored in the same variable because this is unique data is stored based on a key example like this `queuerpc:xxx`, and this does not interfere with other processes when one consumerRPC or others is dies. 167 | 168 | ```go 169 | for { 170 | select { 171 | case res := <-deliveryChan: 172 | log.Println("=============== START DUMP publisherRpc OUTPUT ================") 173 | fmt.Printf("\n") 174 | log.Printf("PUBLISHER RPC QUEUE: %s", queue) 175 | log.Printf("PUBLISHER RPC CORRELATION ID: %s", publishRequest.CorrelationId) 176 | log.Printf("PUBLISHER RPC REQ BODY: %s", string(bodyByte)) 177 | log.Printf("PUBLISHER RPC RES BODY: %s", string(res)) 178 | fmt.Printf("\n") 179 | log.Println("=============== END DUMP publisherRpc OUTPUT =================") 180 | fmt.Printf("\n") 181 | 182 | defer h.recovery() 183 | h.closeConnection(publisher, consumer, nil) 184 | 185 | return res, nil 186 | 187 | case <-time.After(time.Second * flushDeliveryChanTime): 188 | defer fmt.Errorf("PublisherRpc - publisher.Publish Empty Response") 189 | defer h.recovery() 190 | 191 | publisher.Close() 192 | return nil, errors.New("Request Timeout") 193 | } 194 | } 195 | ``` 196 | -------------------------------------------------------------------------------- /client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/bytedance/sonic" 11 | "github.com/jaswdr/faker" 12 | "github.com/lithammer/shortuuid" 13 | 14 | "github.com/restuwahyu13/go-rabbitmq-rpc/pkg" 15 | ) 16 | 17 | func main() { 18 | var ( 19 | queue string = "account" 20 | fk faker.Faker = faker.New() 21 | req map[string]interface{} = make(map[string]interface{}) 22 | user map[string]interface{} = make(map[string]interface{}) 23 | ) 24 | 25 | rabbit := pkg.NewRabbitMQ(&pkg.RabbitMQOptions{ 26 | Url: "amqp://restuwahyu13:restuwahyu13@localhost:5672/", 27 | Exchange: "amqp.direct", 28 | Concurrency: "5", 29 | }) 30 | 31 | router := http.NewServeMux() 32 | 33 | router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 34 | w.Header().Set("Content-Type", "application/json") 35 | 36 | req["id"] = shortuuid.New() 37 | req["name"] = fk.App().Name() 38 | req["country"] = fk.Address().Country() 39 | req["city"] = fk.Address().City() 40 | req["postcode"] = fk.Address().PostCode() 41 | 42 | delivery, err := rabbit.PublisherRpc(queue, req) 43 | if err != nil { 44 | statCode := http.StatusUnprocessableEntity 45 | 46 | if strings.Contains(err.Error(), "Timeout") { 47 | statCode = http.StatusRequestTimeout 48 | } 49 | 50 | w.WriteHeader(statCode) 51 | w.Write([]byte(err.Error())) 52 | return 53 | } 54 | 55 | if err := sonic.Unmarshal(delivery, &user); err != nil { 56 | w.WriteHeader(http.StatusUnprocessableEntity) 57 | w.Write([]byte(err.Error())) 58 | return 59 | } 60 | 61 | json.NewEncoder(w).Encode(&user) 62 | }) 63 | 64 | err := pkg.Graceful(func() *pkg.GracefulConfig { 65 | return &pkg.GracefulConfig{Handler: router, Port: "4000"} 66 | }) 67 | 68 | if err != nil { 69 | log.Fatalf("HTTP Server Shutdown: %s", err.Error()) 70 | os.Exit(1) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | ############################# 4 | # MESSAGE BROKER SERVICE 5 | ############################# 6 | broker: 7 | image: rabbitmq:3.11.7-management 8 | restart: on-failure 9 | healthcheck: 10 | test: 'env | grep HOME' 11 | interval: 60s 12 | timeout: 30s 13 | retries: 3 14 | start_period: 15s 15 | deploy: 16 | resources: 17 | limits: 18 | cpus: '2' 19 | memory: '2048MB' 20 | reservations: 21 | cpus: '1' 22 | memory: 1024MB 23 | environment: 24 | RABBITMQ_DEFAULT_VHOST: / 25 | RABBITMQ_DEFAULT_USER: restuwahyu13 26 | RABBITMQ_DEFAULT_PASS: restuwahyu13 27 | ports: 28 | - 5672:5672 29 | - 15672:15672 30 | volumes: 31 | - broker:/var/lib/rabbitmq/data 32 | networks: 33 | - rabbitmq 34 | ### ================================== 35 | ### VOLUMES PERSISTENT DATA 36 | ### ================================== 37 | volumes: 38 | broker: 39 | ### =================================== 40 | ### NETWORKS GROUP FOR SAME SERVICE 41 | ### =================================== 42 | networks: 43 | rabbitmq: 44 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/restuwahyu13/go-rabbitmq-rpc 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bytedance/sonic v1.10.0 7 | github.com/jaswdr/faker v1.16.0 8 | github.com/lithammer/shortuuid v3.0.0+incompatible 9 | github.com/rabbitmq/amqp091-go v1.7.0 10 | github.com/sirupsen/logrus v1.9.3 11 | github.com/wagslane/go-rabbitmq v0.12.0 12 | ) 13 | 14 | require github.com/pkg/errors v0.9.1 // indirect 15 | 16 | require ( 17 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 18 | github.com/chenzhuoyu/iasm v0.9.0 // indirect 19 | github.com/google/uuid v1.3.0 // indirect 20 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 21 | github.com/ory/graceful v0.1.3 22 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 23 | golang.org/x/arch v0.4.0 // indirect 24 | golang.org/x/sys v0.11.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 3 | github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= 4 | github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 5 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= 8 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= 9 | github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= 10 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 15 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 16 | github.com/jaswdr/faker v1.16.0 h1:5ZjusQbqIZwJnUymPirNKJI1yFCuozdSR9oeYPgD5Uk= 17 | github.com/jaswdr/faker v1.16.0/go.mod h1:x7ZlyB1AZqwqKZgyQlnqEG8FDptmHlncA5u2zY/yi6w= 18 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 19 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 20 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 21 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 22 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 23 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 24 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 25 | github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w= 26 | github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w= 27 | github.com/ory/graceful v0.1.3 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc= 28 | github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA= 29 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 30 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/rabbitmq/amqp091-go v1.7.0 h1:V5CF5qPem5OGSnEo8BoSbsDGwejg6VUJsKEdneaoTUo= 34 | github.com/rabbitmq/amqp091-go v1.7.0/go.mod h1:wfClAtY0C7bOHxd3GjmF26jEHn+rR/0B3+YV+Vn9/NI= 35 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 36 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 37 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 38 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 39 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 40 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 41 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 42 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 43 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 44 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 45 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 46 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 47 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 48 | github.com/wagslane/go-rabbitmq v0.12.0 h1:DdhO+50Hdy6ZUBO/jLvRCWSdjzNn9UelQr+JDdQr5M0= 49 | github.com/wagslane/go-rabbitmq v0.12.0/go.mod h1:1dov2VNMhiMWqCAG86ugGf6a0muj2ly4duIdV7oo7gQ= 50 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 51 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 52 | go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 53 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 54 | golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= 55 | golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 56 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 57 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 58 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 59 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 60 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 61 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 62 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 63 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 64 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 65 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 66 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 67 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 68 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 69 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 70 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 71 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 72 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 74 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 76 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 77 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 78 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 79 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 80 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 81 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 82 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 83 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 84 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 85 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 86 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 87 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 88 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 89 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 90 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 91 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 92 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 93 | -------------------------------------------------------------------------------- /pkg/graceful.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/tls" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/ory/graceful" 10 | ) 11 | 12 | type GracefulConfig struct { 13 | Handler *http.ServeMux 14 | Port string 15 | } 16 | 17 | func Graceful(Handler func() *GracefulConfig) error { 18 | 19 | h := Handler() 20 | 21 | server := http.Server{ 22 | Handler: h.Handler, 23 | Addr: ":" + h.Port, 24 | ReadTimeout: time.Duration(time.Second) * 30, 25 | WriteTimeout: time.Duration(time.Second) * 60, 26 | IdleTimeout: time.Duration(time.Second) * 60, 27 | TLSConfig: &tls.Config{ 28 | Rand: rand.Reader, 29 | InsecureSkipVerify: false, 30 | }, 31 | } 32 | 33 | return graceful.Graceful(server.ListenAndServe, server.Shutdown) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/rabbitmq.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "runtime" 11 | "strconv" 12 | "sync" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/bytedance/sonic" 17 | "github.com/lithammer/shortuuid" 18 | "github.com/rabbitmq/amqp091-go" 19 | "github.com/wagslane/go-rabbitmq" 20 | ) 21 | 22 | const ( 23 | Direct = "direct" 24 | Fanout = "fanout" 25 | Topic = "topic" 26 | Header = "header" 27 | ) 28 | 29 | type ( 30 | MessageBrokerHeader struct { 31 | PrivateKey string `json:"privateKey,omitempty"` 32 | ClientKey string `json:"clientKey"` 33 | Url string `json:"url"` 34 | Method string `json:"method"` 35 | AccessToken string `json:"accessToken"` 36 | ClientSecret string `json:"clientSecret"` 37 | TimeStamp string `json:"timeStamp,omitempty"` 38 | } 39 | 40 | MessageBrokerReq struct { 41 | Service string `json:"service"` 42 | Header MessageBrokerHeader `json:"header"` 43 | Body interface{} `json:"body"` 44 | } 45 | 46 | MessageBrokerRes struct { 47 | Code int `json:"code"` 48 | BankCode string `json:"bankCode"` 49 | Message string `json:"message,omitempty"` 50 | Error interface{} `json:"error,omitempty"` 51 | Data interface{} `json:"data"` 52 | } 53 | 54 | QueueResponse struct { 55 | Items interface{} 56 | } 57 | 58 | RabbitmqInterface interface { 59 | Connection() *rabbitmq.Conn 60 | Publisher(queue string, body interface{}) error 61 | Consumer(eventName string, callback func(d rabbitmq.Delivery) (action rabbitmq.Action)) 62 | PublisherRpc(queue string, body interface{}) ([]byte, error) 63 | ConsumerRpc(queue string, callback func(d rabbitmq.Delivery) (action rabbitmq.Action)) 64 | ReplyToDeliveryPublisher(deliveryBodyTo []byte, delivery rabbitmq.Delivery) 65 | } 66 | 67 | publishMetadata struct { 68 | CorrelationId string `json:"correlationId"` 69 | ReplyTo string `json:"replyTo"` 70 | ContentType string `json:"contentType"` 71 | Timestamp time.Time `json:"timestamp"` 72 | } 73 | 74 | consumerRpcResponse struct { 75 | Data interface{} `json:"data"` 76 | CorrelationId string `json:"correlationId"` 77 | ReplyTo string `json:"replyTo"` 78 | ContentType string `json:"contentType"` 79 | Timestamp time.Time `json:"timestamp"` 80 | } 81 | 82 | ConsumerOverwriteResponse struct { 83 | Res interface{} `json:"res"` 84 | } 85 | 86 | rabbitmqStruct struct { 87 | connection *rabbitmq.Conn 88 | rpcQueue string 89 | rpcConsumerId string 90 | rpcReplyTo string 91 | rpcConsumerRes []byte 92 | } 93 | 94 | RabbitMQOptions struct { 95 | Url string 96 | Exchange string 97 | Concurrency string 98 | Expired string 99 | } 100 | ) 101 | 102 | var ( 103 | publishRequest publishMetadata = publishMetadata{} 104 | publishRequests []publishMetadata = []publishMetadata{} 105 | url string = "amqp://gues:guest@localhost:5672/" 106 | exchangeName string = "amqp.direct" 107 | publisherExpired string = "1800" 108 | ack bool = false 109 | concurrency int = runtime.NumCPU() 110 | shortId string = shortuuid.New() 111 | mutex *sync.RWMutex = &sync.RWMutex{} 112 | wg *sync.WaitGroup = &sync.WaitGroup{} 113 | args amqp091.Table = amqp091.Table{} 114 | flushDeliveryChanTime time.Duration = time.Duration(30) // throw to request timeout because server not response 115 | deliveryChan chan []byte = make(chan []byte, 1) 116 | res []byte = []byte{} 117 | ) 118 | 119 | func NewRabbitMQ(options *RabbitMQOptions) RabbitmqInterface { 120 | url = options.Url 121 | exchangeName = options.Exchange 122 | concurrency, _ = strconv.Atoi(options.Concurrency) 123 | publisherExpired = options.Expired 124 | 125 | connection, err := rabbitmq.NewConn(url, 126 | rabbitmq.WithConnectionOptionsLogging, 127 | rabbitmq.WithConnectionOptionsConfig(rabbitmq.Config{ 128 | Heartbeat: time.Duration(time.Second * 3), 129 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 130 | })) 131 | 132 | if err != nil { 133 | defer log.Fatalf("NewRabbitMQ - rabbitmq.NewConn error: %s", err.Error()) 134 | connection.Close() 135 | } 136 | 137 | log.Println("RabbitMQ connection success") 138 | con := rabbitmqStruct{connection: connection} 139 | 140 | defer con.closeConnection(nil, nil, connection) 141 | return &con 142 | } 143 | 144 | /** 145 | * =========================================== 146 | * HANDLER METHOD - CONNECTION 147 | * =========================================== 148 | **/ 149 | func (h *rabbitmqStruct) Connection() *rabbitmq.Conn { 150 | return h.connection 151 | } 152 | 153 | /** 154 | * =========================================== 155 | * HANDLER METHOD - PUBLISHER 156 | * =========================================== 157 | **/ 158 | 159 | func (h *rabbitmqStruct) Publisher(queue string, body interface{}) error { 160 | log.Printf("START PUBLISHER -> %s", queue) 161 | 162 | publishRequest.ContentType = "application/json" 163 | publishRequest.Timestamp = time.Now().Local() 164 | 165 | publisher, err := rabbitmq.NewPublisher(h.connection, 166 | rabbitmq.WithPublisherOptionsExchangeName(exchangeName), 167 | rabbitmq.WithPublisherOptionsExchangeKind(Direct), 168 | rabbitmq.WithPublisherOptionsExchangeDeclare, 169 | rabbitmq.WithPublisherOptionsExchangeDurable, 170 | rabbitmq.WithPublisherOptionsLogging, 171 | ) 172 | 173 | if err != nil { 174 | defer fmt.Errorf("Publisher - rabbitmq.NewPublisher Error: %s", err.Error()) 175 | defer h.recovery() 176 | 177 | publisher.Close() 178 | return err 179 | } 180 | 181 | bodyByte, err := sonic.Marshal(&body) 182 | if err != nil { 183 | defer fmt.Errorf("Publisher - sonic.Marshal Error: %s", err.Error()) 184 | defer h.recovery() 185 | 186 | publisher.Close() 187 | return err 188 | } 189 | 190 | err = publisher.Publish(bodyByte, []string{queue}, 191 | rabbitmq.WithPublishOptionsPersistentDelivery, 192 | rabbitmq.WithPublishOptionsExchange(exchangeName), 193 | rabbitmq.WithPublishOptionsContentType(publishRequest.ContentType), 194 | rabbitmq.WithPublishOptionsTimestamp(publishRequest.Timestamp), 195 | rabbitmq.WithPublishOptionsExpiration(publisherExpired), 196 | ) 197 | 198 | if err != nil { 199 | defer fmt.Errorf("Publisher - publisher.Publish Error: %s", err.Error()) 200 | defer h.recovery() 201 | 202 | publisher.Close() 203 | return err 204 | } 205 | 206 | defer h.recovery() 207 | publisher.Close() 208 | 209 | return nil 210 | } 211 | 212 | /** 213 | * =========================================== 214 | * HANDLER METHOD - CONSUMER 215 | * =========================================== 216 | **/ 217 | 218 | func (h *rabbitmqStruct) Consumer(queue string, callback func(d rabbitmq.Delivery) (action rabbitmq.Action)) { 219 | log.Printf("START CONSUMER -> %s", queue) 220 | 221 | consumerId := shortuuid.New() 222 | consumer, err := rabbitmq.NewConsumer(h.connection, callback, queue, 223 | rabbitmq.WithConsumerOptionsExchangeName(exchangeName), 224 | rabbitmq.WithConsumerOptionsExchangeKind(Direct), 225 | rabbitmq.WithConsumerOptionsBinding(rabbitmq.Binding{ 226 | RoutingKey: queue, 227 | BindingOptions: rabbitmq.BindingOptions{ 228 | Declare: true, 229 | NoWait: false, 230 | Args: nil, 231 | }, 232 | }), 233 | rabbitmq.WithConsumerOptionsExchangeDurable, 234 | rabbitmq.WithConsumerOptionsQueueDurable, 235 | rabbitmq.WithConsumerOptionsConsumerName(consumerId), 236 | rabbitmq.WithConsumerOptionsConsumerAutoAck(ack), 237 | rabbitmq.WithConsumerOptionsConcurrency(concurrency), 238 | rabbitmq.WithConsumerOptionsLogging, 239 | ) 240 | 241 | if err != nil { 242 | defer fmt.Errorf("Consumer - rabbitmq.NewConsumer Error: %s", err.Error()) 243 | defer h.recovery() 244 | 245 | consumer.Close() 246 | return 247 | } 248 | 249 | closeChan := make(chan os.Signal, 1) 250 | signal.Notify(closeChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGALRM, syscall.SIGINT) 251 | 252 | for { 253 | select { 254 | case sigs := <-closeChan: 255 | log.Printf("Signal Received %s", sigs.String()) 256 | 257 | if os.Getenv("GO_ENV") != "development" { 258 | time.Sleep(10 * time.Second) 259 | } 260 | 261 | defer consumer.Close() 262 | os.Exit(15) 263 | 264 | break 265 | default: 266 | time.Sleep(3 * time.Second) 267 | 268 | if os.Getenv("GO_ENV") == "development" { 269 | log.Println(".") 270 | } 271 | 272 | defer consumer.Close() 273 | break 274 | } 275 | } 276 | } 277 | 278 | /** 279 | * =========================================== 280 | * HANDLER METHOD - PUBLISHER RPC 281 | * =========================================== 282 | **/ 283 | 284 | func (h *rabbitmqStruct) listeningConsumer(metadata *publishMetadata) *rabbitmq.Consumer { 285 | log.Printf("START CLIENT CONSUMER RPC -> %s", metadata.ReplyTo) 286 | 287 | h.rpcQueue = metadata.ReplyTo 288 | h.rpcConsumerId = metadata.CorrelationId 289 | 290 | consumer, err := rabbitmq.NewConsumer(h.connection, func(delivery rabbitmq.Delivery) (action rabbitmq.Action) { 291 | log.Println("=============== START DUMP listeningConsumerRpc REQUEST ================") 292 | fmt.Printf("\n") 293 | log.Printf("LISTENING RPC CONSUMER QUEUE: %s", h.rpcQueue) 294 | log.Printf("LISTENING RPC CONSUMER CORRELATION ID: %s", string(delivery.CorrelationId)) 295 | log.Printf("LISTENING RPC CONSUMER REPLY TO: %s", string(delivery.ReplyTo)) 296 | log.Printf("LISTENING RPC CONSUMER BODY: %s", string(delivery.Body)) 297 | fmt.Printf("\n") 298 | log.Println("info", "=============== END DUMP listeningConsumerRpc REQUEST =================") 299 | fmt.Printf("\n") 300 | 301 | for _, d := range publishRequests { 302 | if d.CorrelationId != delivery.CorrelationId { 303 | deliveryChan <- delivery.Body 304 | return rabbitmq.NackDiscard 305 | } 306 | } 307 | 308 | deliveryChan <- delivery.Body 309 | return rabbitmq.Ack 310 | }, 311 | h.rpcQueue, 312 | rabbitmq.WithConsumerOptionsExchangeName(exchangeName), 313 | rabbitmq.WithConsumerOptionsExchangeKind(Direct), 314 | rabbitmq.WithConsumerOptionsExchangeDeclare, 315 | rabbitmq.WithConsumerOptionsExchangeDurable, 316 | rabbitmq.WithConsumerOptionsQueueDurable, 317 | rabbitmq.WithConsumerOptionsQueueAutoDelete, 318 | rabbitmq.WithConsumerOptionsConsumerName(h.rpcConsumerId), 319 | rabbitmq.WithConsumerOptionsConsumerAutoAck(ack), 320 | rabbitmq.WithConsumerOptionsConcurrency(concurrency), 321 | rabbitmq.WithConsumerOptionsLogging, 322 | ) 323 | 324 | if err != nil { 325 | defer fmt.Errorf("RabbitMQ - rabbitmq.NewConsumer Error: %s", err.Error()) 326 | defer h.recovery() 327 | 328 | consumer.Close() 329 | return consumer 330 | } 331 | 332 | return consumer 333 | } 334 | 335 | func (h *rabbitmqStruct) PublisherRpc(queue string, body interface{}) ([]byte, error) { 336 | log.Printf("START CLIENT PUBLISHER RPC -> %s", queue) 337 | 338 | if len(publishRequests) > 0 { 339 | publishRequests = nil 340 | } 341 | 342 | publishRequest.CorrelationId = shortuuid.New() 343 | publishRequest.ReplyTo = fmt.Sprintf("rpc.%s", shortId) 344 | publishRequest.ContentType = "application/json" 345 | publishRequest.Timestamp = time.Now().Local() 346 | 347 | publishRequests = append(publishRequests, publishRequest) 348 | consumer := h.listeningConsumer(&publishRequest) 349 | 350 | publisher, err := rabbitmq.NewPublisher(h.connection, 351 | rabbitmq.WithPublisherOptionsExchangeName(exchangeName), 352 | rabbitmq.WithPublisherOptionsExchangeKind(Direct), 353 | rabbitmq.WithPublisherOptionsExchangeDeclare, 354 | rabbitmq.WithPublisherOptionsExchangeDurable, 355 | rabbitmq.WithPublisherOptionsLogging, 356 | ) 357 | 358 | if err != nil { 359 | defer fmt.Errorf("PublisherRpc - rabbitmq.NewPublisher Error: %s", err.Error()) 360 | defer h.recovery() 361 | 362 | publisher.Close() 363 | return nil, err 364 | } 365 | 366 | bodyByte, err := sonic.Marshal(&body) 367 | if err != nil { 368 | defer fmt.Errorf("PublisherRpc - sonic.Marshal Error: %s", err.Error()) 369 | defer h.recovery() 370 | 371 | publisher.Close() 372 | return nil, err 373 | } 374 | 375 | err = publisher.Publish(bodyByte, []string{queue}, 376 | rabbitmq.WithPublishOptionsPersistentDelivery, 377 | rabbitmq.WithPublishOptionsExchange(exchangeName), 378 | rabbitmq.WithPublishOptionsCorrelationID(publishRequest.CorrelationId), 379 | rabbitmq.WithPublishOptionsReplyTo(publishRequest.ReplyTo), 380 | rabbitmq.WithPublishOptionsContentType(publishRequest.ContentType), 381 | rabbitmq.WithPublishOptionsTimestamp(publishRequest.Timestamp), 382 | rabbitmq.WithPublishOptionsExpiration(publisherExpired), 383 | ) 384 | 385 | if err != nil { 386 | defer fmt.Errorf("PublisherRpc - publisher.Publish Error: %s", err.Error()) 387 | defer h.recovery() 388 | 389 | publisher.Close() 390 | return nil, err 391 | } 392 | 393 | wg.Add(2) 394 | go func() { 395 | wg.Done() 396 | 397 | mutex.Lock() 398 | defer mutex.Unlock() 399 | 400 | res = <-deliveryChan 401 | }() 402 | 403 | go func() { 404 | wg.Done() 405 | 406 | time.AfterFunc(time.Second*flushDeliveryChanTime, func() { 407 | deliveryChan <- nil 408 | res = <-deliveryChan 409 | }) 410 | }() 411 | wg.Wait() 412 | 413 | mutex.RLock() 414 | defer mutex.RUnlock() 415 | 416 | log.Println("=============== START DUMP publisherRpc OUTPUT ================") 417 | fmt.Printf("\n") 418 | log.Printf("PUBLISHER RPC QUEUE: %s", queue) 419 | log.Printf("PUBLISHER RPC CORRELATION ID: %s", publishRequest.CorrelationId) 420 | log.Printf("PUBLISHER RPC REQ BODY: %s", string(bodyByte)) 421 | log.Printf("PUBLISHER RPC RES BODY: %s", string(res)) 422 | fmt.Printf("\n") 423 | log.Println("=============== END DUMP publisherRpc OUTPUT =================") 424 | fmt.Printf("\n") 425 | 426 | if len(res) <= 0 { 427 | defer fmt.Errorf("PublisherRpc - publisher.Publish Empty Response") 428 | defer h.recovery() 429 | 430 | publisher.Close() 431 | return nil, errors.New("Request Timeout") 432 | } 433 | 434 | defer h.recovery() 435 | h.closeConnection(publisher, consumer, nil) 436 | 437 | return res, nil 438 | } 439 | 440 | func (h *rabbitmqStruct) ConsumerRpc(queue string, callback func(delivery rabbitmq.Delivery) (action rabbitmq.Action)) { 441 | log.Printf("START SERVER CONSUMER RPC -> %s", queue) 442 | 443 | h.rpcConsumerId = shortuuid.New() 444 | h.rpcReplyTo = queue 445 | ack = false 446 | 447 | consumer, err := rabbitmq.NewConsumer(h.connection, func(delivery rabbitmq.Delivery) (action rabbitmq.Action) { 448 | log.Println("=============== START DUMP ConsumerRpc REQUEST ================") 449 | fmt.Printf("\n") 450 | log.Printf("SERVER CONSUMER RPC QUEUE: %s", queue) 451 | log.Printf("SERVER CONSUMER RPC CORRELATION ID: %s", delivery.CorrelationId) 452 | log.Printf("SERVER CONSUMER RPC REPLY TO: %s", delivery.ReplyTo) 453 | log.Printf("SERVER CONSUMER RPC BODY: %s", string(delivery.Body)) 454 | fmt.Printf("\n") 455 | log.Println("=============== END DUMP ConsumerRpc REQUEST =================") 456 | fmt.Printf("\n") 457 | 458 | return callback(delivery) 459 | }, 460 | queue, 461 | rabbitmq.WithConsumerOptionsExchangeName(exchangeName), 462 | rabbitmq.WithConsumerOptionsExchangeKind(Direct), 463 | rabbitmq.WithConsumerOptionsExchangeDeclare, 464 | rabbitmq.WithConsumerOptionsBinding(rabbitmq.Binding{ 465 | RoutingKey: queue, 466 | BindingOptions: rabbitmq.BindingOptions{ 467 | Declare: true, 468 | NoWait: false, 469 | Args: nil, 470 | }, 471 | }), 472 | rabbitmq.WithConsumerOptionsExchangeDurable, 473 | rabbitmq.WithConsumerOptionsQueueDurable, 474 | rabbitmq.WithConsumerOptionsConsumerName(h.rpcConsumerId), 475 | rabbitmq.WithConsumerOptionsConsumerAutoAck(ack), 476 | rabbitmq.WithConsumerOptionsConcurrency(concurrency), 477 | rabbitmq.WithConsumerOptionsLogging, 478 | ) 479 | 480 | if err != nil { 481 | defer fmt.Errorf("ConsumerRpc - rabbitmq.NewConsumer Error: %s", err.Error()) 482 | defer h.recovery() 483 | 484 | consumer.Close() 485 | return 486 | } 487 | } 488 | 489 | func (h *rabbitmqStruct) ReplyToDeliveryPublisher(deliveryBodyTo []byte, delivery rabbitmq.Delivery) { 490 | if deliveryBodyTo != nil { 491 | h.rpcConsumerRes = deliveryBodyTo 492 | } else { 493 | h.rpcConsumerRes = delivery.Body 494 | } 495 | 496 | if len(delivery.ReplyTo) > 0 { 497 | h.rpcReplyTo = delivery.ReplyTo 498 | } 499 | 500 | publisher, err := rabbitmq.NewPublisher(h.connection, 501 | rabbitmq.WithPublisherOptionsExchangeName(exchangeName), 502 | rabbitmq.WithPublisherOptionsExchangeKind(Direct), 503 | rabbitmq.WithPublisherOptionsExchangeDeclare, 504 | rabbitmq.WithPublisherOptionsExchangeDurable, 505 | rabbitmq.WithPublisherOptionsExchangeNoWait, 506 | rabbitmq.WithPublisherOptionsLogging, 507 | ) 508 | 509 | if err != nil { 510 | defer fmt.Errorf("ReplyToDeliveryPublisher - rabbitmq.NewPublisher Error: %s", err.Error()) 511 | defer h.recovery() 512 | 513 | publisher.Close() 514 | return 515 | } 516 | 517 | err = publisher.Publish(h.rpcConsumerRes, []string{h.rpcReplyTo}, 518 | rabbitmq.WithPublishOptionsPersistentDelivery, 519 | rabbitmq.WithPublishOptionsCorrelationID(delivery.CorrelationId), 520 | rabbitmq.WithPublishOptionsContentType(delivery.ContentType), 521 | rabbitmq.WithPublishOptionsTimestamp(delivery.Timestamp), 522 | rabbitmq.WithPublishOptionsExpiration(publisherExpired), 523 | ) 524 | 525 | if err != nil { 526 | defer fmt.Errorf("ReplyToDeliveryPublisher - publisher.Publish Error: %s", err.Error()) 527 | defer h.recovery() 528 | 529 | publisher.Close() 530 | return 531 | } 532 | 533 | defer h.recovery() 534 | publisher.Close() 535 | } 536 | 537 | func (h *rabbitmqStruct) closeConnection(publisher *rabbitmq.Publisher, consumer *rabbitmq.Consumer, connection *rabbitmq.Conn) { 538 | if publisher != nil && consumer != nil && connection != nil { 539 | defer publisher.Close() 540 | defer consumer.Close() 541 | defer connection.Close() 542 | } else if publisher != nil && consumer != nil && connection == nil { 543 | defer publisher.Close() 544 | defer consumer.Close() 545 | } else if publisher != nil && consumer == nil && connection != nil { 546 | defer publisher.Close() 547 | defer connection.Close() 548 | } else if publisher == nil && consumer != nil && connection != nil { 549 | defer consumer.Close() 550 | defer connection.Close() 551 | } else { 552 | closeChan := make(chan os.Signal, 1) 553 | signal.Notify(closeChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGALRM, syscall.SIGINT) 554 | 555 | for { 556 | select { 557 | case <-closeChan: 558 | defer connection.Close() 559 | os.Exit(15) 560 | 561 | break 562 | default: 563 | return 564 | } 565 | } 566 | } 567 | } 568 | 569 | func (h *rabbitmqStruct) recovery() { 570 | if err := recover(); err != nil { 571 | return 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" 9 | 10 | "github.com/bytedance/sonic" 11 | "github.com/jaswdr/faker" 12 | "github.com/lithammer/shortuuid" 13 | "github.com/sirupsen/logrus" 14 | "github.com/wagslane/go-rabbitmq" 15 | 16 | "github.com/restuwahyu13/go-rabbitmq-rpc/pkg" 17 | ) 18 | 19 | type Person struct { 20 | ID string `json:"id"` 21 | Name string `json:"name"` 22 | Country string `json:"country"` 23 | City string `json:"city"` 24 | PostCode string `json:"postcode"` 25 | } 26 | 27 | func main() { 28 | var ( 29 | queue string = "account" 30 | data Person = Person{} 31 | fk faker.Faker = faker.New() 32 | ) 33 | 34 | rabbit := pkg.NewRabbitMQ(&pkg.RabbitMQOptions{ 35 | Url: "amqp://restuwahyu13:restuwahyu13@localhost:5672/", 36 | Exchange: "amqp.direct", 37 | Concurrency: "5", 38 | }) 39 | 40 | rabbit.ConsumerRpc(queue, func(d rabbitmq.Delivery) (action rabbitmq.Action) { 41 | data.ID = shortuuid.New() 42 | data.Name = fk.App().Name() 43 | data.Country = fk.Address().Country() 44 | data.City = fk.Address().City() 45 | data.PostCode = fk.Address().PostCode() 46 | 47 | dataByte, err := sonic.Marshal(&data) 48 | if err != nil { 49 | logrus.Fatal(err.Error()) 50 | return 51 | } 52 | 53 | defer rabbit.ReplyToDeliveryPublisher(dataByte, d) 54 | return rabbitmq.Ack 55 | }) 56 | 57 | signalChan := make(chan os.Signal, 1) 58 | signal.Notify(signalChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGALRM, syscall.SIGINT) 59 | 60 | for { 61 | select { 62 | case sigs := <-signalChan: 63 | log.Printf("Received Signal %s", sigs.String()) 64 | os.Exit(15) 65 | 66 | break 67 | default: 68 | time.Sleep(time.Duration(time.Second * 3)) 69 | log.Println("...........................") 70 | 71 | break 72 | } 73 | } 74 | } 75 | --------------------------------------------------------------------------------