Generate Echo backend code

  • By go-woo
  • Last update: Jan 3, 2023
  • Comments: 1

What is protoc-gen-echo?

protoc-gen-echo is a protoc plug-in that generates Echo server code from proto file.

If you want to create a Echo's http api /helloworld/:name/hi/:nice, you just need to add rpc in a proto file, and generate it.

/helloworld/:name/hi/:nice mapping http api will be generated by protoc-gen-echo.

protoc-gen-echo can generate complete backend all code combination with protoc-gen-ent.

** About ent you can find more help from ent.

Quick start

Step 0: Pre-installation on ubuntu

sudo apt install protobuf-compiler make

Step 1: Edit ./example/v1/greeter.proto

service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/helloworld/{name}/hi/{nice}"
    };
  }
  //...
}
//...

Step 2: Generate

You can generate code used by make:

make example

Or you can generate code used by below too:

go install google.golang.org/protobuf/cmd/[email protected]

go install github.com/go-woo/[email protected]

protoc --proto_path=. \
    --proto_path=./third_party \
    --go_out=paths=source_relative:. \
    --echo_out=paths=source_relative:. \
    ./example/v1/greeter.proto

The protoc-gen-echo generated 2 files:greeter_router.pb.go and greeter_handler.pb.go.

The greeter.pb.go was generated by protoc-gen-go

More help can be found in protoc.

Your business logic stubs has been generated in your_xxxx_handler.pb.go, You can edit business logic in stubs.

func $(YourService)$(RpcName)BusinessHandler(payload *YourRequest) (YourReply, error) {
	// Here can put your business logic, can use ORM:github.com/go-woo/protoc-gen-ent
	return YourReply{}, nil
}

All handlers typo can be found in your_xxxx_router.pb.go.

Step 3: Write example business logic

For this example, in greeter_router.pb.go. You can find generated echo's handler _Greeter_SayHello0_HTTP_Handler.

func RegisterGreeterRouter(e *echo.Echo) {
	e.GET("/helloworld/:name/hi/:nice", _Greeter_SayHello0_HTTP_Handler)
	//...
}

In same file, you can find _Greeter_SayHello0_HTTP_Handler's implement:

func _Greeter_SayHello0_HTTP_Handler(c echo.Context) error {
	var req *HelloRequest = new(HelloRequest)

	req.Name = c.Param(strings.ToLower("Name"))
	req.Nice = c.Param(strings.ToLower("Nice"))
	reply, err := GreeterSayHelloBusinessHandler(req, c)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, &reply)
}

Our focus is on GreeterSayHelloBusinessHandler(payload). In greeter_handler.pb.go, you can write business logic.

func GreeterSayHelloBusinessHandler(req *HelloRequest, c echo.Context) (HelloReply, error) {
	// Here can put your business logic,protoc-gen-ent soon coming
	reqJson, err := json.Marshal(req)
	if err != nil {
		return HelloReply{}, err
	}
	fmt.Printf("Got HelloRequest is: %v\n", string(reqJson))

	return HelloReply{}, nil
}

For running this example we need to write a main.go

package main

import (
	v1 "github.com/go-woo/protoc-gen-echo/example/v1"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	v1.RegisterGreeterRouter(e)
	// you can add custom router outside protoc-gen-echo too.
	// MyCustomRouter(e)

	e.Logger.Fatal(e.Start(":1323"))
}

More help and doc can be found on Echo , include rate limited, auto update TLS cert, etc.

Step 4: Run example

cd example && go run main.go

Open browser, URL:http://localhost:1223/helloworld/Lok-Tar/hi/Ogar

Or shell execute

curl -X GET http://localhost:1323/helloworld/Lok-Tar/hi/Ogar

Step 5: Optional JWT support

Service option

//get token URL
option (google.api.default_host) = "/login";
//need auth root path, can multi path
option (google.api.oauth_scopes) =
"/restricted,"

was used for JWT describer.

If you want to support JWT, can add it. Else you can comment it.

‼️ Special notes

  • Do not remove your_xxxx_handler.pb.go. It was generated only when the first time, and will not be generated or overwritten after that again, because the business logic code you added is already in it.

  • JSON format in http body. If http client request has body, header should has Content-Type: application/json

  • Validate. Single source(proto) is the most important means to ensure consistency. protobuf message filed validate can use protoc-gen-validate.

  • protoc-gen-echo follow google.api.httprule.

  • ent still does not support message nesting, so proto http rule body must be *.

Generate gRPC

gRPC generated by protoc-gen-go-grpc

go install google.golang.org/grpc/cmd/[email protected]

Add --go-grpc_out=paths=source_relative:. \

protoc --proto_path=. \
    --proto_path=./third_party \
    --go_out=paths=source_relative:. \
    --echo_out=paths=source_relative:. \
    --go-grpc_out=paths=source_relative:. \
    ./example/v1/greeter.proto

Generate Open Api Spec document

OAS generated by protoc-gen-openapi

go install github.com/google/gnostic/cmd/[email protected]

Add --openapi_out=paths=source_relative:. \

protoc --proto_path=. \
    --proto_path=./third_party \
    --go_out=paths=source_relative:. \
    --echo_out=paths=source_relative:. \
    --openapi_out=paths=source_relative:. \
    ./example/v1/greeter.proto

Generated yaml file can be browser on swagger or openapi

Validate

Fields validate generated by protoc-gen-validate

go install github.com/envoyproxy/[email protected]

Add --validate_out="lang=go:." \

protoc --proto_path=. \
    --proto_path=./third_party \
    --go_out=paths=source_relative:. \
    --echo_out=paths=source_relative:. \
    --validate_out="lang=go:." \
    ./example/v1/greeter.proto

Todo

  • Add generate jwt openapi/swagger support
https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject
https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto

Download

protoc-gen-echo.zip

Comments(1)

  • 1

    JWT scope detection issue

    Hi,

    i try to run this, but i get always an issue due to jwt scope detection:

    syntax = "proto3";
    
    package greet.v1;
    
    import "google/api/annotations.proto";
    import "google/api/client.proto";
    
    option go_package = "example/proto/gen/go/greet/v1;greet";
    
    
    message GreetRequest {
      string person = 1;
    }
    
    message GreetResponse {
      string greeting = 1;
    }
    
    service GreetService {
      option (google.api.default_host) = "/";
    
      rpc Greet(GreetRequest) returns (GreetResponse) {
        option (google.api.http) = {
          get: "/hello/{person}"
        };
      }
    }
    

    Generated code:

    Failure: plugin echo: greet/v1/greet_router.pb.go: unparsable Go source: 31:3: expected statement, found ':='
        1   // Code generated by protoc-gen-echo. DO NOT EDIT.
        2   // versions:
        3   // - protoc-gen-echo v0.1.1
        4   // - protoc  (unknown)
        5   // source: greet/v1/greet.proto
        6   
        7   package greet
        8   
        9   
       10   import (
       11           "net/http"
       12           "os"
       13   
       14           "github.com/go-woo/protoc-gen-echo/runtime"
       15           "github.com/labstack/echo/v4"
       16           "github.com/labstack/echo/v4/middleware"
       17   )
       18   
       19   
       20   
       21   func RegisterGreetServiceRouter(e *echo.Echo) {
       22           jwtKey := "dangerous"
       23           if os.Getenv("JWTKEY") != "" {
       24                   jwtKey = os.Getenv("JWTKEY")
       25           }
       26           config := middleware.JWTConfig{
       27                   Claims:     &runtime.JwtCustomClaims{},
       28                   SigningKey: []byte(jwtKey),
       29           }
       30           
       31            := e.Group("/")
       32           .Use(middleware.JWTWithConfig(config))
       33           
       34           .GET("/hello/:person", _GreetService_Greet0_HTTP_Handler)
       35   }
       36   
       37   func _GreetService_Greet0_HTTP_Handler(c echo.Context) error {
       38           var req *GreetRequest = new(GreetRequest)
       39           uv := c.QueryParams()
       40           return runtime.BindValues(req, uv)
       41           reply, err := GreetServiceGreetBusinessHandler(req, c)
       42           if err != nil {
       43                   return err
       44           }
       45           return c.JSON(http.StatusOK, &reply)
       46   }
    

    I use buf to generate these files:

    buf.yaml

    version: v1
    breaking:
      use:
        - FILE
    lint:
      use:
        - DEFAULT
    deps:
      - buf.build/googleapis/googleapis
    

    buf.gen.yaml

    version: v1
    plugins:
      - name: go
        out: gen/go
        opt: paths=source_relative
      - name: go-grpc
        out: gen/go
        opt: paths=source_relative
      - name: echo
        out: gen/go/echo
        opt: paths=source_relative