Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/dayitv89/go-exp
GRPC example
https://github.com/dayitv89/go-exp
Last synced: 9 days ago
JSON representation
GRPC example
- Host: GitHub
- URL: https://github.com/dayitv89/go-exp
- Owner: dayitv89
- Created: 2020-01-30T13:04:06.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2021-05-12T01:03:56.000Z (over 3 years ago)
- Last Synced: 2024-01-23T05:14:59.637Z (about 1 year ago)
- Language: JavaScript
- Homepage:
- Size: 77.1 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# go-lang experiments
### grpc calculator experiments
It has a demo of grpc implement of simple calculator with sum and subtract method.
Implemented:
- grpc-exp/calc/calcpb/calc.proto
- grpc server in golang
- grpc client in golang and nodejsAdditional Implemented:
- [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) for golang server
- swagger json file as available in [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway).
- grpc reverser proxy server in `runtime.ServeMux`
- **grpc reverser proxy server in [`gin gonic`](https://github.com/gin-gonic/gin).****\*why not default implementation?** Because, [gin gonic](https://github.com/gin-gonic/gin) supports multiple way to interact with client. My idea is to use grpc with my existing server and my existing server gives me a tons of features like http2 push, SSE, already implemented whole monolithic application on gin gonic and additionally control to pass to grpc like hijeck or middleware. So just want to plug grpc service to my http client.
BTW [default example](https://github.com/gin-gonic/examples/tree/master/grpc) for writing individual client is so much time taken and TDS job for me, so I just want to a wildcard option for that.
#### Setup protobuf:
```sh
go install \
github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger \
github.com/golang/protobuf/protoc-gen-gocd grpc-exp/calc
chmod +x gen_proto.sh
./gen_proto.sh```
#### Run Server
```
cd grpc-exp/calc
go run server/server.go
```#### Demo
- `GET /sum`
```sh
# gin gonic
curl --location --request GET 'http://localhost:3000/grpc/sum?n1=12&n2=10'# runtime.ServeMux (default)
curl --location --request GET 'http://localhost:3001/sum?n1=12&n2=10'
```Output:
```json
// gin gonic
{
"code": 200,
"grpc_res": {
"result": 22
}
}// runtime.ServeMux (default)
{
"result": 22
}
```**As Error:**
```sh
# gin gonic
curl --location --request GET 'http://localhost:3000/grpc/this_method_does_not_exist?n1=12&n2=10'# runtime.ServeMux (default)
curl --location --request GET 'http://localhost:3001/this_method_does_not_exist?n1=12&n2=10'
```Output:
```json
// gin gonic
{
"code": 404,
"error": "cannot convert to json string, use status code or grpc_res to find the issue.",
"error_detail": "invalid character 'N' looking for beginning of value",
"grpc_res": "Not Found\n"
}// runtime.ServeMux (default)
Not Found\n
```- `POST /subtract`
```sh
curl --location --request POST 'http://localhost:3000/grpc/subtract' \
--header 'Content-Type: application/json' \
--data-raw '{
"n1": 12,
"n2": 10
}'
``````json
{
"code": 200,
"grpc_res": {
"result": 2
}
}
```### Server file
```go
//grpc-exp/calc/server/server.go
// run as $ go run grpc-exp/calc/server/server.go
package mainimport (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"time"pb "github.com/dayitv89/go-exp/grpc-exp/calc/calcpb"
"github.com/gin-gonic/gin"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)type grpcController struct{}
func (*grpcController) Sum(ctx context.Context, req *pb.Request) (*pb.Response, error) {
return &pb.Response{Result: req.GetN1() + req.GetN2()}, nil
}func (*grpcController) Subtract(ctx context.Context, req *pb.Request) (*pb.Response, error) {
return &pb.Response{Result: req.GetN1() - req.GetN2()}, nil
}const grpcPort string = ":50051"
func grpcServer() {
lis, err := net.Listen("tcp", grpcPort)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}s := grpc.NewServer()
pb.RegisterCalculatorServer(s, &grpcController{})
reflection.Register(s)
fmt.Println("grpc server is running on", grpcPort)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
fmt.Println("grpc server closed from", grpcPort)
}func main() {
go grpcServer()
go webServerMux()if err := webServerGin(); err != nil {
fmt.Println("webServerMux error", err)
}
}//GRPCResponseHandler hijeck the response body
type GRPCResponseHandler struct {
gin.ResponseWriter
body []byte
statusCode int
}//NewGRPCResponseHandler get new instance for each request
func NewGRPCResponseHandler(w gin.ResponseWriter) *GRPCResponseHandler {
return &GRPCResponseHandler{w, []byte{}, http.StatusOK}
}//Write stop the data flush and pushing to the client as we need to override the body at the end from grpc response.
func (grh *GRPCResponseHandler) Write(data []byte) (int, error) {
grh.body = append(grh.body, data...)
return 0, nil
}//WriteHeader Holds the statusCode
func (grh *GRPCResponseHandler) WriteHeader(code int) {
grh.statusCode = code
grh.ResponseWriter.WriteHeader(code)
}func webServerGin() error {
r := gin.Default()
http.DefaultClient.Timeout = 30 * time.Second// setup global middlewares
r.Use(func(c *gin.Context) {
c.Next()
fmt.Println("CUSTOM LOG:\nRequest:", c.Request, "\nResponse:", c.Writer.Status())
})r.GET("/ping", func(c *gin.Context) {
msg := fmt.Sprintf("ping at the server: %d", time.Now().Unix())
c.JSON(http.StatusOK, gin.H{"message": msg})
})mux, cancel, err := setupMux()
defer cancel()
if err != nil {
return err
}
r.Any("/grpc/*path", func(c *gin.Context) {
path := c.Param("path")
c.Status(http.StatusOK)c.Request.URL.Path = path
fmt.Println("\ngin mux grpc request", c.Request.URL)
grpcResponse := NewGRPCResponseHandler(c.Writer)
mux.ServeHTTP(grpcResponse, c.Request)var jsonS interface{}
c.Header("Content-Type", "application/json")
if err := json.Unmarshal(grpcResponse.body, &jsonS); err != nil {
c.JSON(grpcResponse.statusCode, gin.H{
"code": grpcResponse.statusCode,
"error": "cannot convert to json string, use status code or grpc_res to find the issue.",
"error_detail": err.Error(),
"grpc_res": string(grpcResponse.body),
})
} else {
c.JSON(grpcResponse.statusCode, gin.H{"code": grpcResponse.statusCode, "grpc_res": jsonS})
}
})r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"code": http.StatusNotFound, "error": "route not available"})
})os.Setenv("PORT", "3000")
fmt.Printf("\n\nRunning SERVER on port :%s and GIN_MODE=%s\n\n", os.Getenv("PORT"), os.Getenv("GIN_MODE"))return r.Run(":" + os.Getenv("PORT"))
}func setupMux() (*runtime.ServeMux, context.CancelFunc, error) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := pb.RegisterCalculatorHandlerFromEndpoint(ctx, mux, "127.0.0.1"+grpcPort, opts)
return mux, cancel, err
}func webServerMux() error {
mux, cancel, err := setupMux()
defer cancel()
if err != nil {
return err
}
return http.ListenAndServe(":3001", mux)
}```