diff --git a/README.md b/README.md index 2674826..f1afdef 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - create proto file `health.proto` - compile / generate proto with `compile-proto.sh` in proto dir +### Setup config file - create `config.yaml` - create `pkg` dir , create versioning dir and create `configs` dir - `go get gopkg.in/yaml.v2` , is a lib for parsing yaml config file to struct @@ -15,4 +16,29 @@ - create `configs` dir on root project , create `configs.go`, this is file that bundle or wrap any services or packages - `go get github.com/sirupsen/logrus`, is a lib to show log on run - implement New to `configs.go` file -- create `main.go`, implement to call config and log environtment read is ready \ No newline at end of file +- create `main.go`, implement to call config and log environtment read is ready +- test `go run .` + +### Implement Server GRPC +- create `utils/constants` dir in `pkg/v1`, to create global constants, implement EnvProduction, Successcode, SuccessDesc +- implement grpc service create `api/v1/health` service, create `health.go` as server service +- create `api/v1/health/status.go` as method implment from protobuf / pb file +- create `router` dir in root project +- create `grpc.go` in router dir and implement NewGRPCServer and register health api service +- `go get github.com/soheilhy/cmux`, is for ? +- create `router.go` in router dir and implement IgnoreErr, this is for ignore error so can be safely ignore +- `go get golang.org/x/sync/errgroup`, is for ? +- implement `main.go` to create grpc server from grpc.go with errgroup handler +- `go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest` +- test `grpcurl -plaintext localhost:5566 list`, to show list name of services +- test `grpcurl -plaintext localhost:5566 list api.gogrpc.v1.health.HealthService`, to show list name of service methods +- test `grpcurl -plaintext localhost:5566 api.gogrpc.v1.health.HealthService.Status`, to test method call in grpc +- result +``` +{ + "success": true, + "code": "0000", + "desc": "SUCCESS" +} +``` +- or test via postman , new -> grpc request -> Enter Server Url : localhost:5566 -> Import proto file / Select Method : Status -> Click Invoke \ No newline at end of file diff --git a/api/v1/health/health.go b/api/v1/health/health.go new file mode 100644 index 0000000..464dc3f --- /dev/null +++ b/api/v1/health/health.go @@ -0,0 +1,18 @@ +package health + +import ( + "github.com/ajikamaludin/go-grpc_basic/configs" + "github.com/sirupsen/logrus" +) + +type Server struct { + config *configs.Configs + logger *logrus.Logger +} + +func New(config *configs.Configs, logger *logrus.Logger) *Server { + return &Server{ + config: config, + logger: logger, + } +} diff --git a/api/v1/health/status.go b/api/v1/health/status.go new file mode 100644 index 0000000..5beaaee --- /dev/null +++ b/api/v1/health/status.go @@ -0,0 +1,17 @@ +package health + +import ( + "context" + + "github.com/ajikamaludin/go-grpc_basic/pkg/v1/utils/constants" + hlpb "github.com/ajikamaludin/go-grpc_basic/proto/v1/health" + "github.com/golang/protobuf/ptypes/empty" +) + +func (s *Server) Status(ctx context.Context, req *empty.Empty) (*hlpb.Response, error) { + return &hlpb.Response{ + Success: true, + Code: constants.SuccessCode, + Desc: constants.SuccesDesc, + }, nil +} diff --git a/go.mod b/go.mod index 73fb19c..895dd77 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,16 @@ require ( google.golang.org/protobuf v1.28.0 ) -require github.com/sirupsen/logrus v1.8.1 // indirect +require github.com/sirupsen/logrus v1.8.1 + +require ( + github.com/soheilhy/cmux v0.1.5 // indirect + golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect +) require ( github.com/golang/protobuf v1.5.2 // indirect - golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect diff --git a/go.sum b/go.sum index 835247a..f3c4d73 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -46,14 +47,18 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -71,12 +76,16 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -125,11 +134,13 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index de05e47..a48ecd6 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,16 @@ package main import ( + "context" "log" + "net/http" + "os" + "os/signal" + "syscall" "github.com/ajikamaludin/go-grpc_basic/configs" + "github.com/ajikamaludin/go-grpc_basic/router" + "golang.org/x/sync/errgroup" ) func main() { @@ -16,4 +23,37 @@ func main() { } logger.Infof("[SERVER] Environment %s is ready", configs.Config.Env) + + g, _ := errgroup.WithContext(context.Background()) + + var servers []*http.Server + + g.Go(func() error { + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) + + select { + case sig := <-signalChannel: + logger.Infof("receive signal: %s\n", sig) + for i, s := range servers { + if err := s.Shutdown(context.Background()); err != nil { + if err == nil { + logger.Infof("error shutting down server %d: %v", i, err) + return err + } + } + } + os.Exit(1) + } + return nil + }) + + g.Go(func() error { return router.NewGRPCServer(configs, logger) }) + + if err := g.Wait(); !router.IgnoreErr(err) { + log.Printf("[SERVER] ERROR %v", err) + logger.Fatal(err) + } + + logger.Infoln("[APP] DONE.") } diff --git a/pkg/v1/utils/constants/constants.go b/pkg/v1/utils/constants/constants.go new file mode 100644 index 0000000..90da946 --- /dev/null +++ b/pkg/v1/utils/constants/constants.go @@ -0,0 +1,10 @@ +package constants + +const ( + EnvProduction = "production" +) + +const ( + SuccessCode = `0000` + SuccesDesc = `SUCCESS` +) diff --git a/router/grpc.go b/router/grpc.go new file mode 100644 index 0000000..4b31e9b --- /dev/null +++ b/router/grpc.go @@ -0,0 +1,36 @@ +package router + +import ( + "fmt" + "log" + "net" + + "github.com/ajikamaludin/go-grpc_basic/api/v1/health" + "github.com/ajikamaludin/go-grpc_basic/configs" + hlpb "github.com/ajikamaludin/go-grpc_basic/proto/v1/health" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +func NewGRPCServer(configs *configs.Configs, logger *logrus.Logger) error { + listen, err := net.Listen("tcp", fmt.Sprintf(":%v", configs.Config.Server.Grpc.Port)) + if err != nil { + return err + } + + // register grpc service server + grpcServer := grpc.NewServer() + + hlpb.RegisterHealthServiceServer(grpcServer, health.New(configs, logger)) + + // add reflection service + reflection.Register(grpcServer) + + // running gRPC server + log.Println("[SERVER] GRPC server is running") + + grpcServer.Serve(listen) + + return nil +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..196d820 --- /dev/null +++ b/router/router.go @@ -0,0 +1,21 @@ +package router + +import ( + "net" + "net/http" + + "github.com/soheilhy/cmux" +) + +func IgnoreErr(err error) bool { + switch { + case err == nil || err == http.ErrServerClosed || err == cmux.ErrListenerClosed: + return true + } + + if opErr, ok := err.(*net.OpError); ok { + return opErr.Err.Error() == "use of closed network connection" + } + + return false +}