├── .gitignore
├── .golangci.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cli
├── cmd
│ ├── add
│ │ ├── add.go
│ │ ├── method.go
│ │ └── plugin.go
│ ├── cmd.go
│ ├── default.go
│ ├── describe
│ │ └── describe.go
│ └── init
│ │ └── init.go
├── main.go
└── pkg
│ └── scaffold
│ ├── generator
│ ├── generator.go
│ └── options.go
│ ├── injector
│ ├── injector.go
│ ├── options.go
│ └── types.go
│ └── templates
│ ├── docker.go
│ ├── gitignore.go
│ ├── main.go
│ ├── makefile.go
│ ├── module.go
│ ├── proto.go
│ ├── script.go
│ └── service.go
├── docs
├── lastbackend.toolkit.flow.drawio
├── lastbackend.toolkit.flow.png
├── lastbackend.toolkit.schema.drawio
└── lastbackend.toolkit.schema.png
├── examples
├── README.md
├── gateway
│ ├── apis
│ │ └── server.proto
│ ├── config
│ │ └── config.go
│ ├── gen
│ │ └── server
│ │ │ ├── client
│ │ │ └── server.pb.toolkit.rpc.go
│ │ │ ├── server.pb.go
│ │ │ ├── server.pb.validate.go
│ │ │ ├── server_grpc.pb.go
│ │ │ └── server_service.pb.toolkit.go
│ ├── generate.go
│ ├── main.go
│ ├── middleware
│ │ ├── example.go
│ │ └── request.go
│ └── scripts
│ │ └── generate.sh
├── helloworld
│ ├── apis
│ │ └── helloworld.proto
│ ├── gen
│ │ ├── helloworld.pb.go
│ │ └── helloworld_grpc.pb.go
│ ├── generate.go
│ ├── main.go
│ └── scripts
│ │ └── generate.sh
├── http
│ ├── apis
│ │ └── server.proto
│ ├── gen
│ │ └── server
│ │ │ ├── client
│ │ │ └── server.pb.toolkit.rpc.go
│ │ │ ├── server.pb.go
│ │ │ ├── server.pb.validate.go
│ │ │ ├── server_grpc.pb.go
│ │ │ └── server_service.pb.toolkit.go
│ ├── generate.go
│ ├── main.go
│ ├── middleware
│ │ ├── example.go
│ │ └── request.go
│ └── scripts
│ │ └── generate.sh
├── service
│ ├── apis
│ │ ├── example.proto
│ │ └── ptypes
│ │ │ └── messages.proto
│ ├── config
│ │ └── config.go
│ ├── gen
│ │ ├── client
│ │ │ ├── demo
│ │ │ │ └── demo.go
│ │ │ └── example.pb.toolkit.rpc.go
│ │ ├── example.pb.go
│ │ ├── example.pb.validate.go
│ │ ├── example_grpc.pb.go
│ │ ├── example_service.pb.toolkit.go
│ │ ├── ptypes
│ │ │ ├── messages.pb.go
│ │ │ └── messages.pb.validate.go
│ │ └── tests
│ │ │ └── example.pb.toolkit.mockery.go
│ ├── generate.go
│ ├── internal
│ │ ├── controller
│ │ │ └── controller.go
│ │ ├── repository
│ │ │ └── repository.go
│ │ └── server
│ │ │ ├── handler.go
│ │ │ ├── interceptor.go
│ │ │ ├── middleware.go
│ │ │ ├── server.go
│ │ │ └── server_test.go
│ ├── mage.go
│ ├── magefile.go
│ ├── main.go
│ ├── scripts
│ │ └── generate.sh
│ └── test-results
│ │ └── junit
│ │ └── unit-tests.xml
└── wss
│ ├── apis
│ └── server.proto
│ ├── gen
│ └── server
│ │ ├── client
│ │ └── server.pb.toolkit.rpc.go
│ │ ├── server.pb.go
│ │ ├── server.pb.validate.go
│ │ ├── server_grpc.pb.go
│ │ └── server_service.pb.toolkit.go
│ ├── generate.go
│ ├── main.go
│ ├── middleware
│ ├── example.go
│ └── request.go
│ ├── scripts
│ └── generate.sh
│ └── swagger
│ ├── protoc-gen-openapiv2
│ └── options
│ │ ├── annotations.swagger.json
│ │ └── openapiv2.swagger.json
│ ├── server.swagger.json
│ └── validate
│ └── validate.swagger.json
├── generate.go
├── go.mod
├── go.sum
├── hack
└── generate.sh
├── interface.go
├── pkg
├── client
│ ├── client.go
│ ├── grpc
│ │ ├── client.go
│ │ ├── codec.go
│ │ ├── options.go
│ │ ├── pool.go
│ │ ├── resolver
│ │ │ ├── file
│ │ │ │ ├── file.go
│ │ │ │ └── table.go
│ │ │ ├── filter.go
│ │ │ ├── local
│ │ │ │ ├── local.go
│ │ │ │ └── table.go
│ │ │ ├── options.go
│ │ │ ├── resolver.go
│ │ │ └── route
│ │ │ │ └── route.go
│ │ ├── selector
│ │ │ ├── random.go
│ │ │ ├── roundrobin.go
│ │ │ └── selector.go
│ │ └── stream.go
│ └── http
│ │ └── client.go
├── context
│ ├── context.go
│ └── metadata
│ │ └── metadata.go
├── runtime
│ ├── controller
│ │ ├── client.go
│ │ ├── config.go
│ │ ├── controller.go
│ │ ├── help.go
│ │ ├── package.go
│ │ ├── plugin.go
│ │ ├── server.go
│ │ ├── service.go
│ │ ├── tools.go
│ │ └── util.go
│ ├── interface.go
│ ├── logger
│ │ ├── empty
│ │ │ └── empty.go
│ │ ├── logger.go
│ │ └── zap
│ │ │ └── zap.go
│ ├── meta
│ │ └── meta.go
│ └── options.go
├── server
│ ├── grpc
│ │ ├── grpc.go
│ │ ├── interceptor.go
│ │ └── options.go
│ ├── http
│ │ ├── client.go
│ │ ├── converter.go
│ │ ├── cors.go
│ │ ├── errors
│ │ │ ├── errors.go
│ │ │ └── http.go
│ │ ├── handler.go
│ │ ├── marshaler.go
│ │ ├── marshaler
│ │ │ ├── formpb
│ │ │ │ └── formpb.go
│ │ │ ├── jsonpb
│ │ │ │ └── jsonpb.go
│ │ │ ├── marshaler.go
│ │ │ └── util
│ │ │ │ └── convert_types.go
│ │ ├── middleware.go
│ │ ├── mux.go
│ │ ├── options.go
│ │ ├── server.go
│ │ └── websockets
│ │ │ ├── client.go
│ │ │ ├── event.go
│ │ │ └── manager.go
│ └── server.go
├── tools
│ ├── metrics
│ │ └── metrics.go
│ ├── probes
│ │ ├── probes.go
│ │ └── server
│ │ │ └── server.go
│ └── traces
│ │ └── traces.go
└── util
│ ├── addr
│ └── addr.go
│ ├── backoff
│ └── backoff.go
│ ├── converter
│ ├── converter.go
│ └── query.go
│ ├── filesystem
│ └── filesystem.go
│ ├── net
│ └── net.go
│ ├── parser
│ └── parser.go
│ ├── strings
│ └── strings.go
│ ├── tls
│ └── tls.go
│ └── types
│ └── types.go
├── protoc-gen-toolkit
├── descriptor
│ ├── descriptor.go
│ ├── services.go
│ └── types.go
├── generator
│ └── generator.go
├── gentoolkit
│ ├── gentoolkit.go
│ ├── template.go
│ ├── templates
│ │ ├── client.go
│ │ ├── header.go
│ │ ├── message.go
│ │ ├── plugin.go
│ │ ├── server.go
│ │ ├── service.go
│ │ └── test.go
│ └── utils.go
├── main.go
└── toolkit
│ └── options
│ ├── annotations.pb.go
│ ├── annotations.pb.validate.go
│ └── annotations.proto
└── toolkit.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Develop tools
2 | /.vscode/
3 | /.idea/
4 |
5 | # Binaries for programs and plugins
6 | *.exe
7 | *.exe~
8 | *.dll
9 | *.so
10 | *.dylib
11 |
12 | # Folders
13 | _obj
14 | _test
15 | _build
16 |
17 | # Architecture specific extensions/prefixes
18 | *.[568vq]
19 | [568vq].out
20 |
21 | *.cgo1.go
22 | *.cgo2.c
23 | _cgo_defun.c
24 | _cgo_gotypes.go
25 | _cgo_export.*
26 |
27 | # Test binary, build with `go test -c`
28 | *.test
29 |
30 | # Output of the go coverage tool, specifically when used with LiteIDE
31 | *.out
32 |
33 | # vim temp files
34 | *~
35 | *.swp
36 | *.swo
37 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | run:
2 | timeout: 2m
3 | modules-download-mode: readonly
4 |
5 | linters:
6 | enable:
7 | - deadcode
8 | - errcheck
9 | - gosimple
10 | - gofmt
11 | - govet
12 | - staticcheck
13 | - unused
14 | - typecheck
15 | - unconvert
16 |
17 | issues:
18 | # Independently of option `exclude` we use default exclude patterns,
19 | # it can be disabled by this option.
20 | # To list all excluded by default patterns execute `golangci-lint run --help`.
21 | # Default: true.
22 | exclude-use-default: true
23 | # If set to true exclude and exclude-rules regular expressions become case-sensitive.
24 | # Default: false
25 | exclude-case-sensitive: false
26 | # The list of ids of default excludes to include or disable.
27 | # Default: []
28 | exclude:
29 | - EXC0002 # disable excluding of issues about comments from golint.
30 | # Maximum issues count per one linter.
31 | # Set to 0 to disable.
32 | # Default: 50
33 | max-issues-per-linter: 0
34 | # Maximum count of issues with the same text.
35 | # Set to 0 to disable.
36 | # Default: 3
37 | max-same-issues: 0
38 | # Fix found issues (if it's supported by the linter).
39 | fix: true
40 |
41 | skip-files:
42 | - ".*\\.pb\\.go$"
43 | - ".*\\.pb\\.*\\.go$"
44 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastbackend/toolkit/e0774c46bc5aa97b349ccb594dd8d83edabaef61/CHANGELOG.md
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## How to contribute
2 |
3 | ### Code reviews
4 |
5 | All submissions, including submissions by project members, require review.
6 |
7 |
8 | ==== Coding Style
9 |
10 | 1. All code should be formatted with `gofmt`
11 | 2. All code should follow the guidelines covered in Effective Go and Go Code Review Comments.
12 | 3. Comment the code. Tell us the why, the history and the context.
13 | 4. Variable name length should not be too long.
14 |
15 | Great materials to read:
16 |
17 | * https://golang.org/doc/effective_go.html[Effective Go]
18 | * https://blog.golang.org[The Go Blog]
19 | * https://github.com/golang/go/wiki/CodeReviewComments[Code Review Comments]
20 |
21 | Great tools to use:
22 |
23 | - https://github.com/kisielk/errcheck[ErrCheck] - a program for checking for unchecked errors in Go programs.
24 | - https://github.com/golang/lint[GoLint] - a linter for Go source code.
25 |
26 | ==== Reporting issues
27 |
28 | 1. Tell us version of Toolkit you use
29 | 2. Include the steps required to reproduce the problem, if possible
30 |
--------------------------------------------------------------------------------
/cli/cmd/add/add.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package add
18 |
19 | import (
20 | tcli "github.com/lastbackend/toolkit/cli/cmd"
21 | "github.com/urfave/cli/v2"
22 | )
23 |
24 | func init() {
25 | tcli.Register(&cli.Command{
26 | Name: "add",
27 | Usage: "Add new entity to service template",
28 | Subcommands: []*cli.Command{
29 | {
30 | Name: "method",
31 | Usage: `Add a new method to service template
32 | Ex:
33 | ` + tcli.App().Name + ` add method --expose-http=get:/demo --proxy=helloworld:/helloworld.Greeter/SayHello Example
34 | ` + tcli.App().Name + ` add method --expose-ws=/demo Example
35 | ` + tcli.App().Name + ` add method --subscribe-ws --proxy=prime:/helloworld.Greeter/SayHello Example
36 | ` + tcli.App().Name + ` add method Example
37 | `,
38 | Action: AddMethod,
39 | Flags: methodFlags,
40 | },
41 | {
42 | Name: "plugin",
43 | Usage: `Add a new plugin to service template
44 | Ex:
45 | ` + tcli.App().Name + ` add plugin --prefix=redis redis
46 | `,
47 | Action: AddPlugin,
48 | Flags: pluginFlags,
49 | },
50 | },
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/cli/cmd/add/plugin.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package add
18 |
19 | import (
20 | "fmt"
21 | "os"
22 | "path"
23 | "strings"
24 |
25 | "github.com/lastbackend/toolkit/cli/pkg/scaffold/injector"
26 | "github.com/urfave/cli/v2"
27 | )
28 |
29 | var pluginFlags = []cli.Flag{
30 | &cli.StringFlag{
31 | Name: "path",
32 | Usage: "Path to proto directory",
33 | Value: "./proto",
34 | },
35 | &cli.StringFlag{
36 | Name: "prefix",
37 | Usage: "Set prefix for plugin",
38 | },
39 | }
40 |
41 | func AddPlugin(ctx *cli.Context) error {
42 | arg := ctx.Args().First()
43 | if len(arg) == 0 {
44 | return cli.ShowSubcommandHelp(ctx)
45 | }
46 |
47 | workdir := ctx.String("path")
48 | if path.IsAbs(workdir) {
49 | fmt.Println("must provide a relative path as service name")
50 | return nil
51 | }
52 |
53 | if _, err := os.Stat(workdir); os.IsNotExist(err) {
54 | return fmt.Errorf("%s not exists", workdir)
55 | }
56 |
57 | po := injector.PluginOption{
58 | Prefix: ctx.String("prefix"),
59 | Name: strings.ToLower(arg),
60 | }
61 |
62 | i := injector.New(
63 | injector.Workdir(workdir),
64 | injector.Plugin(po),
65 | )
66 |
67 | if err := i.Inject(); err != nil {
68 | return err
69 | }
70 |
71 | return nil
72 | }
73 |
--------------------------------------------------------------------------------
/cli/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package cmd
18 |
19 | import (
20 | "os"
21 | "runtime/debug"
22 |
23 | "github.com/urfave/cli/v2"
24 | )
25 |
26 | type Option func(o *Options)
27 |
28 | type Options struct {
29 | Name string
30 | Description string
31 | Version string
32 | }
33 |
34 | type CLI interface {
35 | App() *cli.App
36 | Options() Options
37 | Run() error
38 | }
39 |
40 | type cmd struct {
41 | app *cli.App
42 | opts Options
43 | }
44 |
45 | func (c *cmd) App() *cli.App {
46 | return c.app
47 | }
48 |
49 | func (c *cmd) Options() Options {
50 | return c.opts
51 | }
52 |
53 | func (c *cmd) Run() error {
54 | return c.app.Run(os.Args)
55 | }
56 |
57 | func NewCLI(opts ...Option) CLI {
58 | options := Options{}
59 |
60 | for _, o := range opts {
61 | o(&options)
62 | }
63 |
64 | if len(options.Name) == 0 {
65 | options.Name = name
66 | }
67 | if len(options.Description) == 0 {
68 | options.Description = description
69 | }
70 | if len(options.Version) == 0 {
71 | if bi, ok := debug.ReadBuildInfo(); ok {
72 | options.Version = bi.Main.Version
73 | } else {
74 | options.Version = version
75 | }
76 | }
77 |
78 | c := new(cmd)
79 | c.app = cli.NewApp()
80 | c.opts = options
81 | c.app.Name = c.opts.Name
82 | c.app.Usage = c.opts.Description
83 | c.app.Version = c.opts.Version
84 | c.app.EnableBashCompletion = true
85 | c.app.HideVersion = len(options.Version) == 0
86 |
87 | return c
88 | }
89 |
--------------------------------------------------------------------------------
/cli/cmd/default.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package cmd
18 |
19 | import (
20 | "fmt"
21 | "os"
22 |
23 | "github.com/urfave/cli/v2"
24 | )
25 |
26 | var (
27 | DefaultCLI = NewCLI()
28 |
29 | name = os.Args[0]
30 | description = "The Go Last.Backend Toolkit CLI tool"
31 | version = "latest"
32 | )
33 |
34 | func Register(commands ...*cli.Command) {
35 | app := DefaultCLI.App()
36 | app.Commands = append(app.Commands, commands...)
37 | }
38 |
39 | func App() *cli.App {
40 | return DefaultCLI.App()
41 | }
42 |
43 | func Run() {
44 | if err := DefaultCLI.Run(); err != nil {
45 | fmt.Println(err.Error())
46 | os.Exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/cli/cmd/describe/describe.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package add
18 |
19 | import (
20 | "fmt"
21 | "github.com/lastbackend/toolkit/pkg/util/filesystem"
22 | "os"
23 | "text/tabwriter"
24 |
25 | "github.com/jhump/protoreflect/desc/protoparse"
26 | tcli "github.com/lastbackend/toolkit/cli/cmd"
27 | "github.com/urfave/cli/v2"
28 | )
29 |
30 | var flags = []cli.Flag{
31 | &cli.StringFlag{
32 | Name: "path",
33 | Usage: "Path to proto dir",
34 | Value: "./proto",
35 | },
36 | }
37 |
38 | func init() {
39 | tcli.Register(&cli.Command{
40 | Name: "describe",
41 | Usage: "Get describe service info",
42 | Action: Describe,
43 | Flags: flags,
44 | })
45 | }
46 |
47 | func Describe(ctx *cli.Context) error {
48 | files, err := filesystem.WalkMatch(ctx.String("path"), "*.proto")
49 | if err != nil {
50 | return err
51 | }
52 |
53 | parser := protoparse.Parser{}
54 | desc, err := parser.ParseFilesButDoNotLink(files...)
55 | if err != nil {
56 | return err
57 | }
58 |
59 | w := new(tabwriter.Writer)
60 | w.Init(os.Stdout, 8, 8, 0, '\t', 0)
61 | defer w.Flush()
62 |
63 | for _, d := range desc {
64 | for _, svc := range d.Service {
65 | fmt.Fprintf(w, "\n Service: %s\n", svc.GetName())
66 | for index, meth := range svc.Method {
67 | if !meth.GetClientStreaming() && !meth.GetClientStreaming() {
68 | fmt.Fprintf(w, "\n %d. rpc %s(ctx context.Context, req *%s) returns (resp *%s, err error) \t", index+1, meth.GetName(), meth.GetInputType(), meth.GetOutputType())
69 | } else if meth.GetClientStreaming() {
70 | fmt.Fprintf(w, "\n %d. rpc %s(req *%s) returns (stream *%s, error) \t", index+1, meth.GetName(), meth.GetInputType(), meth.GetOutputType())
71 | } else {
72 | fmt.Fprintf(w, "\n %d. rpc %s(stream *%s) returns error \t", index+1, meth.GetName(), meth.GetInputType())
73 | }
74 | }
75 | fmt.Fprintf(w, "\n")
76 | }
77 | }
78 | fmt.Fprintf(w, "\n")
79 |
80 | return nil
81 | }
82 |
--------------------------------------------------------------------------------
/cli/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/cli/cmd"
21 |
22 | // register commands
23 | _ "github.com/lastbackend/toolkit/cli/cmd/add"
24 | _ "github.com/lastbackend/toolkit/cli/cmd/describe"
25 | _ "github.com/lastbackend/toolkit/cli/cmd/init"
26 | )
27 |
28 | func main() {
29 | cmd.Run()
30 | }
31 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/generator/generator.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package generator
18 |
19 | import (
20 | "os"
21 | "path/filepath"
22 | "text/template"
23 |
24 | ustrings "github.com/lastbackend/toolkit/pkg/util/strings"
25 | )
26 |
27 | type Generator interface {
28 | Generate([]File) error
29 | Options() Options
30 | }
31 |
32 | type generator struct {
33 | opts Options
34 | }
35 |
36 | type File struct {
37 | Path string
38 | Template string
39 | FileMode os.FileMode
40 | }
41 |
42 | func (g *generator) Generate(files []File) error {
43 | for _, file := range files {
44 | fp := filepath.Join(g.opts.Workdir, file.Path)
45 | dir := filepath.Dir(fp)
46 |
47 | if file.Template == "" {
48 | dir = fp
49 | }
50 |
51 | if _, err := os.Stat(dir); os.IsNotExist(err) {
52 | if err := os.MkdirAll(dir, 0755); err != nil {
53 | return err
54 | }
55 | }
56 |
57 | if file.Template == "" {
58 | continue
59 | }
60 |
61 | fn := template.FuncMap{
62 | "title": ustrings.Title,
63 | "lower": ustrings.ToLower,
64 | "upper": ustrings.ToUpper,
65 | "camel": ustrings.ToCamel,
66 | "git": ustrings.GitParse,
67 | "dehyphen": ustrings.DeHyphenFunc,
68 | "lowerhyphen": ustrings.LowerHyphenFunc,
69 | "tohyphen": ustrings.ToHyphen,
70 | }
71 |
72 | t, err := template.New(fp).Funcs(fn).Parse(file.Template)
73 | if err != nil {
74 | return err
75 | }
76 |
77 | f, err := os.Create(fp)
78 | if err != nil {
79 | return err
80 | }
81 |
82 | if file.FileMode != 0 {
83 | if err := f.Chmod(file.FileMode); err != nil {
84 | return err
85 | }
86 | }
87 |
88 | err = t.Execute(f, g.opts)
89 | if err != nil {
90 | return err
91 | }
92 | }
93 |
94 | return nil
95 | }
96 |
97 | func (g *generator) Options() Options {
98 | return g.opts
99 | }
100 |
101 | func New(opts ...Option) Generator {
102 | var options Options
103 | for _, o := range opts {
104 | o(&options)
105 | }
106 | return &generator{
107 | opts: options,
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/generator/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package generator
18 |
19 | type Options struct {
20 | Service string
21 | Vendor string
22 | Workdir string
23 |
24 | RedisPlugin bool
25 | RabbitMQPlugin bool
26 | PostgresPGPlugin bool
27 | PostgresGORMPlugin bool
28 | PostgresPlugin bool
29 | CentrifugePlugin bool
30 | annotationImport bool
31 | }
32 |
33 | func (o Options) AnnotationImport() bool {
34 | return o.annotationImport
35 | }
36 |
37 | type Option func(o *Options)
38 |
39 | func Service(s string) Option {
40 | return func(o *Options) {
41 | o.Service = s
42 | }
43 | }
44 | func Vendor(s string) Option {
45 | return func(o *Options) {
46 | o.Vendor = s
47 | }
48 | }
49 |
50 | func Workdir(d string) Option {
51 | return func(o *Options) {
52 | o.Workdir = d
53 | }
54 | }
55 |
56 | func RedisPlugin(b bool) Option {
57 | return func(o *Options) {
58 | if b {
59 | o.annotationImport = true
60 | }
61 | o.RedisPlugin = b
62 | }
63 | }
64 |
65 | func RabbitMQPlugin(b bool) Option {
66 | return func(o *Options) {
67 | if b {
68 | o.annotationImport = true
69 | }
70 | o.RabbitMQPlugin = b
71 | }
72 | }
73 |
74 | func PostgresPGPlugin(b bool) Option {
75 | return func(o *Options) {
76 | if b {
77 | o.annotationImport = true
78 | }
79 | o.PostgresPGPlugin = b
80 | }
81 | }
82 |
83 | func PostgresGORMPlugin(b bool) Option {
84 | return func(o *Options) {
85 | if b {
86 | o.annotationImport = true
87 | }
88 | o.PostgresGORMPlugin = b
89 | }
90 | }
91 |
92 | func PostgresPlugin(b bool) Option {
93 | return func(o *Options) {
94 | if b {
95 | o.annotationImport = true
96 | }
97 | o.PostgresPlugin = b
98 | }
99 | }
100 |
101 | func CentrifugePlugin(b bool) Option {
102 | return func(o *Options) {
103 | if b {
104 | o.annotationImport = true
105 | }
106 | o.CentrifugePlugin = b
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/injector/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package injector
18 |
19 | type Options struct {
20 | Workdir string
21 | Methods []MethodOption
22 | Plugins []PluginOption
23 | }
24 |
25 | type Option func(o *Options)
26 |
27 | func Workdir(d string) Option {
28 | return func(o *Options) {
29 | o.Workdir = d
30 | }
31 | }
32 |
33 | type MethodOption struct {
34 | Name string
35 | ExposeHTTP *ExposeHTTP
36 | ExposeWS *ExposeWS
37 | RPCProxy *RpcProxy
38 | SubscriptionWS bool
39 | }
40 |
41 | func Method(m MethodOption) Option {
42 | return func(o *Options) {
43 | if o.Methods == nil {
44 | o.Methods = make([]MethodOption, 0)
45 | }
46 | o.Methods = append(o.Methods, m)
47 | }
48 | }
49 |
50 | type PluginOption struct {
51 | Name string
52 | Prefix string
53 | }
54 |
55 | func Plugin(p PluginOption) Option {
56 | return func(o *Options) {
57 | if o.Plugins == nil {
58 | o.Plugins = make([]PluginOption, 0)
59 | }
60 | o.Plugins = append(o.Plugins, p)
61 | }
62 | }
63 |
64 | type ImportOption struct {
65 | Path string
66 | }
67 |
68 | type MessageOption struct {
69 | Name string
70 | }
71 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/injector/types.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package injector
18 |
19 | type ExposeHTTP struct {
20 | Method string
21 | Path string
22 | }
23 |
24 | type ExposeWS struct {
25 | Method string
26 | Path string
27 | }
28 |
29 | type SubscriptionWS struct {
30 | Service string
31 | Method string
32 | }
33 |
34 | type RpcProxy struct {
35 | Service string
36 | Method string
37 | }
38 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/templates/docker.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // Dockerfile is the Dockerfile template used for new services.
20 | var Dockerfile = `# Script generated by toolkit CLI.
21 | FROM golang:alpine as builder
22 |
23 | # Set necessary environmet variables needed for our image
24 | ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
25 |
26 | # Configure golang
27 | ENV GOPATH /go
28 | ENV GOROOT /usr/local/go
29 | ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
30 |
31 | # Install dependencies
32 | RUN apk --update add --no-cache ca-certificates \
33 | linux-headers \
34 | gcc \
35 | libtool \
36 | musl-dev
37 |
38 | RUN set -ex \
39 | && apk add --no-cache --virtual .build-deps \
40 | bash \
41 | git \
42 | curl \
43 | protoc \
44 | make \
45 | \
46 | && rm -rf /*.patch
47 |
48 | WORKDIR $GOPATH/src/{{.Service}}
49 |
50 | # Build Go binary
51 | COPY scripts ./scripts
52 | COPY Makefile go.mod go.sum ./
53 |
54 | RUN make init && go mod download
55 |
56 | COPY . .
57 |
58 | RUN make tidy build
59 |
60 |
61 | # Deployment container
62 | FROM golang:alpine
63 |
64 | COPY --from=builder /go/src/{{.Service}}/{{.Service}} /{{.Service}}
65 |
66 | ENTRYPOINT ["/{{.Service}}"]
67 | `
68 |
69 | // DockerIgnore is the .dockerignore template used for new services.
70 | var DockerIgnore = `.gitignore
71 | Dockerfile
72 | `
73 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/templates/gitignore.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // GitIgnore is the .gitignore template used for new services.
20 | var GitIgnore = `# This file generated by toolkit CLI.
21 | # Binaries for programs and plugins
22 | # Develop tools
23 | /.vscode/
24 | /.idea/
25 |
26 | # Binaries for programs and plugins
27 | *.exe
28 | *.exe~
29 | *.dll
30 | *.so
31 | *.dylib
32 |
33 | # Folders
34 | _obj
35 | _test
36 | _build
37 |
38 | # Architecture specific extensions/prefixes
39 | *.[568vq]
40 | [568vq].out
41 |
42 | *.cgo1.go
43 | *.cgo2.c
44 | _cgo_defun.c
45 | _cgo_gotypes.go
46 | _cgo_export.*
47 |
48 | # Test binary, build with 'go test -c'
49 | *.test
50 |
51 | # Output of the go coverage tool, specifically when used with LiteIDE
52 | *.out
53 |
54 | # vim temp files
55 | *~
56 | *.swp
57 | *.swo
58 |
59 | # Ignore the service binary to vcs
60 | {{.Service}}
61 | `
62 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/templates/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // Main is the main template used for new services.
20 | var Main = `package main
21 |
22 | import (
23 | "context"
24 | "os"
25 |
26 | servicepb "{{.Vendor}}{{lower .Service}}/gen"
27 | "{{.Vendor}}{{lower .Service}}/config"
28 | "{{.Vendor}}{{lower .Service}}/internal/server"
29 | )
30 |
31 | func main() {
32 |
33 | app, err := servicepb.NewExampleService("example",
34 | runtime.WithVersion("0.1.0"),
35 | runtime.WithDescription("Example microservice"),
36 | runtime.WithEnvPrefix("LB"),
37 | )
38 | if err != nil {
39 | fmt.Println(err)
40 | }
41 |
42 | // Config management
43 | cfg := config.New()
44 |
45 | if err := app.RegisterConfig(cfg); err != nil {
46 | app.Log().Error(err)
47 | return
48 | }
49 |
50 | // Add packages
51 | app.RegisterPackage(repository.NewRepository, controller.NewController)
52 |
53 | svc.SetServer(server.NewServer)
54 |
55 | if err := app.Start(context.Background()); err != nil {
56 | app.Log().Errorf("could not run the service %v", err)
57 | os.Exit(1)
58 | return
59 | }
60 |
61 | // time.Sleep(10 * time.Second)
62 | // app.Stop(context.Background())
63 |
64 | app.Log().Info("graceful stop")
65 | }
66 | `
67 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/templates/makefile.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // Makefile is the Makefile template used for new services.
20 | var Makefile = `GOPATH:=$(shell go env GOPATH)
21 |
22 | .PHONY: init
23 | init:
24 | @sh ./scripts/bootstrap.sh
25 |
26 | .PHONY: proto
27 | proto:
28 | @sh ./scripts/generate.sh
29 |
30 | .PHONY: update
31 | update:
32 | @go get -u
33 |
34 | .PHONY: tidy
35 | tidy:
36 | @go mod tidy
37 |
38 | .PHONY: build
39 | build:
40 | @go build -o {{.Service}} *.go
41 |
42 | .PHONY: test
43 | test:
44 | @go test -v ./... -cover
45 |
46 | .PHONY: gotest
47 | gotest:
48 | @gotestsum --format testname
49 |
50 | .PHONY: lint
51 | lint:
52 | @golangci-lint run -v
53 | `
54 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/templates/module.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // Module is the go.mod template used for new services.
20 | var Module = `module {{.Vendor}}{{.Service}}
21 |
22 | go 1.19
23 |
24 | {{ if not .Vendor }}
25 | replace {{lower .Service}} => ./
26 | {{ end }}
27 | `
28 |
--------------------------------------------------------------------------------
/cli/pkg/scaffold/templates/service.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // ServiceConfig is the config template used for new services.
20 | var ServiceConfig = `package config
21 |
22 | type Config struct {
23 | Nested NestedConfig
24 | }
25 |
26 | type NestedConfig struct {
27 | Demo string
28 | }
29 |
30 | func New() *Config {
31 | c := new(Config)
32 | return c
33 | }
34 | `
35 |
36 | // ServiceServer is the server template used for new services.
37 | var ServiceServer = `package server
38 |
39 | import (
40 | "context"
41 |
42 | "{{.Vendor}}{{lower .Service}}/config"
43 | servicepb "{{.Vendor}}{{lower .Service}}/gen"
44 | typespb "{{.Vendor}}{{lower .Service}}/gen"
45 | )
46 |
47 | type Handlers struct {
48 | svc servicepb.Service
49 | cfg *config.Config
50 | }
51 |
52 | func (h Handlers) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest) (*typespb.HelloWorldResponse, error) {
53 | //TODO implement me
54 | panic("implement me")
55 | }
56 |
57 | func NewServer(svc servicepb.Service, cfg *config.Config) servicepb.{{camel .Service}}RpcServer {
58 | return &Handlers{
59 | svc: svc,
60 | cfg: cfg,
61 | }
62 | }
63 | `
64 |
--------------------------------------------------------------------------------
/docs/lastbackend.toolkit.flow.drawio:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/lastbackend.toolkit.flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastbackend/toolkit/e0774c46bc5aa97b349ccb594dd8d83edabaef61/docs/lastbackend.toolkit.flow.png
--------------------------------------------------------------------------------
/docs/lastbackend.toolkit.schema.drawio:
--------------------------------------------------------------------------------
1 | 7V1Lk6M2EP41Pk6K9+M4O7NJDtmqTe0h2dxkkDG7GLFCnhnn10cYgQHhGXmwaTnei8sSL9H9datfEgv7YfPyG0XF+hOJcbawjPhlYT8uLCvwDf5bdezqDj+w6o6EpnHdZR46vqT/YtEprku2aYzL3omMkIylRb8zInmOI9brQ5SS5/5pK5L1n1qgBEsdXyKUyb1/pTFbi9ey/EP/7zhN1s2TTS+sj2xQc7J4k3KNYvLc6bI/LuwHSgir/21eHnBW0a6hS33dr0eOtgOjOGcqF/yJnv8Jf7j32eOduQ0+hX8Rl96Z7ejYrnllHHMKiCahbE0SkqPs46H3AyXbPMbVfQ3eOpzzByEF7zR55zfM2E6wE20Z4V1rtsnEUT5kuvtbXL9vfK0av7hN8/Gle/BxJ1oxKtf751Y3WZGciSeYAW/XL1KN/iiFRFdJtjTCr5FFIA3RBLNXznO8lpFcADDZYD5afiHFGWLpU38gSEAxac87cIv/EQw7hXn1fZ9QthVPqkTje8oWlpfxYX9YUv4vqf6VmD6l/JWHvD5wsqLo8zpl+EuB9rR55vLc55p4HKYMv7xOYpki4gJbwE1oA7NRD88H2Wohue7IlWdciIa+C4H/M0LVVoSqb0FC1ZagyhU2hxEDR2RoDCDpQEPS80FVsnmaSj6n1rUUsewa4yydSe1aEpiLbJukeQkOZtfrg9kNoMFshbcKZkcRzOZUxSwu/UxSPsYWCq3lLaAQ2gMW1wMTVw243A5jgpQ4kpTEiKElKuGtEN/t08ZzocXEhjXDAcXEvXExcSUxWVLyHVPthMS3wIXElIhyI0Li3biQeJKQlLuS4Y1+QhJCC4l3swaXP5eQTPMefAnLjKK8LDjVtYOzaRgutNKH8YZfUtbGJ/n/Dpp56wDmqrHrIhtYBgJVGYANXAaSDFBckuxJA7MntHTT6GYoUWvL0gw+2jDUFoEHTSo7+Kks2jTZmzognKgDxo3FoQAF4YDbtRaTjMU3rU7pRhe2Opv36Fqde2aAC55EYnDBs2AED37GVZc2UKuzuXE3AYOitX6hOPiIdZP/uT0oW6pQ9kGhbElQTmgRgSN5qJQ18J2cWw2YWapZ8clQPmK6OAMsmO7MtouccF8zVmgoJD60kNgwATMtfYa3BUs5q3kZ58IL+vgJh7C4tFzJWc1vCCcaxi3aGD2cYHk/BUtdsJTzoC6o8SWnKylaLlO2+aGdBGiQsAQpLrxWCVBOcjqgEiDnIgtSsoRi+IDsUAI0qGtxfkqAugSoZjCPwWAmCZAzmBTHqX7wh48leTAmEDyUHVUHHLaU1xmvS6ckyzQw6aVqXhMcz/at4lnV7wXGs+yecoB+r9a46QZmB3ydhXuzYFb1NSeDeTyI45hDveb2b3HhII4jO7HV+jgNNb4Nvj7Okd2d7RLTu4Tw261ewClmDZdvgVPMtW5VrYTXMUfK5VTc5lulCTiWJek3oLHc3PjmsNwETjRfAd4Ms2vvUfKUxvD2nh3qZu/5t5oNd1WdF2Awy85Llq5wtIsy/eCsgZ2h1TJxa048K/svoJkCHyT2DUFn2L0l/KsKsja5iSZPoZKbWKVZ9kAyQvdvZ6+CCEcR7y9ZtVS0c2QZuI5bXdHbKAcCEnVkFWwqkT18kt+VfNTwa6yGVj78hiOuVsXb/pwziWrOGXgmgZ3qu7P71+5BjRgEOwWZ1lX5Fq9uo3b6ZAPB78nzy/7Se0rRrnNCUYWcy86dB5FrfxBttEQo+dgSpuH5rtD1B6zVIzhreNuVQ7b7yY/Al/8O5z7LhK7+9S2JWFGWghPKHxaQgxsJpnFVZvYbWi1GOFiNajUvCvBydUETOlRdo+1MLaGeyG+t4gvnnNIUmA/Cb9DKXtPQygu4BX6D+hSmcVUbxfwf+A0aEjFHtpGQAJBw/hZHzRexxzZaNqcbp5o1HHRvh9HdMbumreU8vwWoQJeTzD9EIwFOb8SdWq2scXcq9pae66maj6/xWCZ/k4NTJbZxMSvSBAmKz689XpfBN5VKcJnlc+ag8soeMvrClVeBXG87W+XVaRLTlBdpt2F5IGdJ1yiP56lXfhcN7UA7GsrpAQitNIMaUS3DD0Bt0WBkf8fZPlhwFr0wNrXOjGl5VU53KYMhB6g1JaUNvsohkLenm61C8DyzFHjhYDBaZHlA40QTe0Y0gq8hazwmkDUdZ6GhA74MNZS3v0JFkaURn9xILtFx/2GSHrH6vkBOcjxwHEQXytIk582IU5AD3f5Q0Zc/JrsXBzZpHO+thzHu9C2KvZ8iBjXmt1yOgeawdlPmnz3qN7qX4p+skTX41NH7hEM3w8E0YXPX3YqPr4tu7E+XSOBcRniNcrgAIYgTdD0B4akwUC1tgIYByLLIM7PzqHY5Q+3SVByo7vIwGQd6xgDb5181wGbAiWpeCVpfwGxydBDwk+T76mDQmOK6w8CQXSwZF3l8X32Td9H6Sz12DKT1M2Lch8r3PZZht3xovsNrjXtMp30R9jQbvps4G82b1X1TNfR7d0wf+m1S0vTdqp03D18qrk8/fO7Z/vgf
--------------------------------------------------------------------------------
/docs/lastbackend.toolkit.schema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastbackend/toolkit/e0774c46bc5aa97b349ccb594dd8d83edabaef61/docs/lastbackend.toolkit.schema.png
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | ### 1. GRPC service
4 |
5 | ----
6 |
7 | Follow these run example:
8 |
9 | Run the GRPC service:
10 |
11 | ```console
12 | go run service/main.go
13 | ```
14 |
15 | if you see it:
16 | ```console
17 | ...
18 | server listening at [::]:50005
19 | ...
20 | ```
21 |
22 | _Great, you have succeeded!_
23 |
24 |
25 | ### 2. HTTP Gateway
26 |
27 | ----
28 |
29 | Follow these run example:
30 |
31 | Run the Gateway server:
32 |
33 | ```console
34 | env GTW_GATEWAY_SERVER_PORT=8082 go run main.go
35 | ```
36 |
37 | Run gRPC Hello World Server
38 |
39 | ```console
40 | go run helloworld/main.go
41 | ```
42 |
43 | Make a request to the Hello World Server
44 |
45 | ```console
46 | curl -i -X POST http://localhost:8082/hello -H 'Content-Type: application/json' --data '{"name":"world"}'
47 |
48 | Response:
49 | HTTP/1.1 200 OK
50 | Content-Type: application/json
51 | Date: Wed, 01 Feb 2023 20:40:03 GMT
52 | Content-Length: 25
53 |
54 | {"message":"Hello world"}
55 | ```
56 |
57 | Call custom handler
58 |
59 | ```console
60 | curl -i -X GET http://localhost:8080/health -H 'Content-Type: application/json'
61 |
62 | Response:
63 | HTTP/1.1 200 OK
64 | Content-Type: application/json
65 | Date: Wed, 01 Feb 2023 20:44:57 GMT
66 | Content-Length: 15
67 |
68 | {"alive": true}
69 | ```
70 |
71 |
72 | ### 3. WSS Proxy
73 |
74 | ----
75 |
76 | Follow these run example:
77 |
78 | Run the WSS server:
79 |
80 | ```console
81 | go run wss/main.go
82 | ```
83 |
84 | Run gRPC Hello World Server
85 |
86 | ```console
87 | go run helloworld/main.go
88 | ```
89 |
90 | Make a request to the Hello World Server
91 |
92 | ```console
93 | curl -i -X POST http://localhost:8080/hello -H 'Content-Type: application/json' --data '{"name":"world"}'
94 |
95 | Response:
96 | HTTP/1.1 200 OK
97 | Content-Type: application/json
98 | Date: Wed, 01 Feb 2023 20:40:03 GMT
99 | Content-Length: 24
100 |
101 | {"message":"Hello world"}
102 | ```
103 |
104 | Call wss events handler
105 |
106 | ```console
107 | websocat ws://localhost:8008/events
108 | {"type":"HelloWorld","payload":{"name":"world"}}
109 | {"message":"Hello world"}
110 | ```
111 |
112 |
113 | ### 4. HTTP server
114 |
115 | ----
116 |
117 | Follow these run example:
118 |
119 | Run the HTTP server:
120 |
121 | ```console
122 | env LB_HTTP_SERVER_PORT=8082 go run http/main.go
123 | ```
124 |
125 | Make a request to the HTTP Server
126 |
127 | ```console
128 | curl -X GET 'http://localhost:8082/health'
129 |
130 | Response:
131 | HTTP/1.1 200 OK
132 | Content-Type: application/json
133 | Date: Wed, 01 Feb 2023 20:44:57 GMT
134 | Content-Length: 15
135 |
136 | {"alive": true}
137 | ```
138 |
--------------------------------------------------------------------------------
/examples/gateway/apis/server.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package gateway;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/examples/gateway/gen/server;serverpb";
6 |
7 | import "google/api/annotations.proto";
8 |
9 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto";
10 | import "github.com/lastbackend/toolkit/examples/helloworld/apis/helloworld.proto";
11 |
12 | // =====================================================
13 | // HTTP proxy methods
14 | // =====================================================
15 |
16 | // Example methods
17 | service ProxyGateway {
18 | option (toolkit.runtime) = {
19 | servers: [GRPC, HTTP]
20 | };
21 | option (toolkit.server) = {
22 | middlewares: []
23 | };
24 | rpc HelloWorld(helloworld.HelloRequest) returns (helloworld.HelloReply) {
25 | option (toolkit.route) = {
26 | middlewares: [],
27 | http_proxy: {
28 | service: "helloworld",
29 | method: "/helloworld.Greeter/SayHello",
30 | }
31 | };
32 | option (google.api.http) = {
33 | post: "/hello"
34 | };
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/examples/gateway/config/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package config
18 |
19 | type Config struct {
20 | Demo string
21 | }
22 |
23 | func New() *Config {
24 | c := new(Config)
25 | return c
26 | }
27 |
--------------------------------------------------------------------------------
/examples/gateway/gen/server/client/server.pb.toolkit.rpc.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/gateway/apis/server.proto
3 |
4 | package serverpb
5 |
6 | import (
7 | context "context"
8 |
9 | "github.com/lastbackend/toolkit/examples/helloworld/gen"
10 | client "github.com/lastbackend/toolkit/pkg/client"
11 | emptypb "google.golang.org/protobuf/types/known/emptypb"
12 | )
13 |
14 | // Suppress "imported and not used" errors
15 | var _ context.Context
16 | var _ emptypb.Empty
17 |
18 | // Client gRPC API for ProxyGateway service
19 | func NewProxyGatewayRPCClient(service string, c client.GRPCClient) ProxyGatewayRPCClient {
20 | return &proxygatewayGrpcRPCClient{service, c}
21 | }
22 |
23 | // Client gRPC API for ProxyGateway service
24 | type ProxyGatewayRPCClient interface {
25 | HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error)
26 | }
27 |
28 | type proxygatewayGrpcRPCClient struct {
29 | service string
30 | cli client.GRPCClient
31 | }
32 |
33 | func (c *proxygatewayGrpcRPCClient) HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) {
34 | resp := new(servicepb.HelloReply)
35 | if err := c.cli.Call(ctx, c.service, ProxyGateway_HelloWorldMethod, req, resp, opts...); err != nil {
36 | return nil, err
37 | }
38 | return resp, nil
39 | }
40 |
41 | func (proxygatewayGrpcRPCClient) mustEmbedUnimplementedProxyGatewayClient() {}
42 |
43 | // Client methods for ProxyGateway service
44 | const (
45 | ProxyGateway_HelloWorldMethod = "/gateway.ProxyGateway/HelloWorld"
46 | )
47 |
--------------------------------------------------------------------------------
/examples/gateway/gen/server/server.pb.validate.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-validate. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/gateway/apis/server.proto
3 |
4 | package serverpb
5 |
6 | import (
7 | "bytes"
8 | "errors"
9 | "fmt"
10 | "net"
11 | "net/mail"
12 | "net/url"
13 | "regexp"
14 | "sort"
15 | "strings"
16 | "time"
17 | "unicode/utf8"
18 |
19 | "google.golang.org/protobuf/types/known/anypb"
20 | )
21 |
22 | // ensure the imports are used
23 | var (
24 | _ = bytes.MinRead
25 | _ = errors.New("")
26 | _ = fmt.Print
27 | _ = utf8.UTFMax
28 | _ = (*regexp.Regexp)(nil)
29 | _ = (*strings.Reader)(nil)
30 | _ = net.IPv4len
31 | _ = time.Duration(0)
32 | _ = (*url.URL)(nil)
33 | _ = (*mail.Address)(nil)
34 | _ = anypb.Any{}
35 | _ = sort.Sort
36 | )
37 |
--------------------------------------------------------------------------------
/examples/gateway/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate ./scripts/generate.sh
4 |
--------------------------------------------------------------------------------
/examples/gateway/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "io"
23 | "net/http"
24 | "os"
25 |
26 | "github.com/lastbackend/toolkit"
27 | servicepb "github.com/lastbackend/toolkit/examples/gateway/gen/server"
28 | typespb "github.com/lastbackend/toolkit/examples/helloworld/gen"
29 | "github.com/lastbackend/toolkit/pkg/runtime"
30 | )
31 |
32 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
33 | w.Header().Set("Content-Type", "application/json")
34 | w.WriteHeader(http.StatusOK)
35 | if _, err := io.WriteString(w, `{"alive": true}`); err != nil {
36 | fmt.Println(err)
37 | }
38 | }
39 | func main() {
40 | // define service with name and options
41 | app, err := servicepb.NewProxyGatewayService("gateway",
42 | runtime.WithVersion("0.1.0"),
43 | runtime.WithDescription("Example gateway microservice"),
44 | runtime.WithEnvPrefix("GTW"),
45 | )
46 | if err != nil {
47 | fmt.Println(err)
48 | }
49 |
50 | // Add server
51 | app.Server().HTTP().AddHandler(http.MethodGet, "/health", HealthCheckHandler)
52 |
53 | app.Server().GRPC().SetService(newServer)
54 |
55 | // Logger settings
56 | app.Log().Info("Run microservice")
57 |
58 | // Service run
59 | if err := app.Start(context.Background()); err != nil {
60 | app.Log().Errorf("could not run the service %v", err)
61 | os.Exit(1)
62 | return
63 | }
64 |
65 | app.Log().Info("graceful stop")
66 | }
67 |
68 | type Handlers struct {
69 | app toolkit.Service
70 | }
71 |
72 | // Implement server
73 | func newServer(app toolkit.Service) servicepb.ProxyGatewayRpcServer {
74 | return &Handlers{
75 | app: app,
76 | }
77 | }
78 |
79 | func (h *Handlers) HelloWorld(ctx context.Context, req *typespb.HelloRequest) (*typespb.HelloReply, error) {
80 | return &typespb.HelloReply{
81 | Message: "hello",
82 | }, nil
83 | }
84 |
--------------------------------------------------------------------------------
/examples/gateway/middleware/example.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package middleware
18 |
19 | import (
20 | "fmt"
21 | "net/http"
22 | )
23 |
24 | // ExampleMiddleware - show request info middleware
25 | func ExampleMiddleware(h http.Handler) http.Handler {
26 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 | fmt.Println("Call: ExampleMiddleware")
28 | h.ServeHTTP(w, r)
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/examples/gateway/middleware/request.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "crypto/rand"
6 | "encoding/base64"
7 | "fmt"
8 | "net/http"
9 | "os"
10 | "strings"
11 | "sync/atomic"
12 | )
13 |
14 | // KeyPEMBlock to use when setting the request UserID.
15 | type ctxKeyRequestID int
16 |
17 | // RequestIDKey is the key that holds th unique request UserID in a request context.
18 | const RequestIDKey ctxKeyRequestID = 0
19 |
20 | var (
21 | // prefix is const prefix for request UserID
22 | prefix string
23 |
24 | // reqID is counter for request UserID
25 | reqID uint64
26 | )
27 |
28 | // init Initializes constant part of request UserID
29 | func init() {
30 | hostname, err := os.Hostname()
31 | if hostname == "" || err != nil {
32 | hostname = "localhost"
33 | }
34 | var buf [12]byte
35 | var b64 string
36 | for len(b64) < 10 {
37 | _, _ = rand.Read(buf[:])
38 | b64 = base64.StdEncoding.EncodeToString(buf[:])
39 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
40 | }
41 |
42 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
43 | }
44 |
45 | // RequestID is a middleware that injects a request UserID into the context of each
46 | // request. A request UserID is a string of the form "host.example.com/random-0001",
47 | // where "random" is a base62 random string that uniquely identifies this go
48 | // process, and where the last number is an atomically incremented request
49 | // counter.
50 | func RequestID(h http.Handler) http.Handler {
51 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
52 | id := atomic.AddUint64(&reqID, 1)
53 | ctx := r.Context()
54 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, id))
55 | h.ServeHTTP(w, r.WithContext(ctx))
56 | })
57 | }
58 |
59 | // GetReqID returns a request UserID from the given context if one is present.
60 | // Returns the empty string if a request UserID cannot be found.
61 | func GetReqID(ctx context.Context) string {
62 | if ctx == nil {
63 | return ""
64 | }
65 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
66 | return reqID
67 | }
68 | return ""
69 | }
70 |
--------------------------------------------------------------------------------
/examples/gateway/scripts/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/gateway
4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE
5 | PROTO_DIR=$ROOT_DIR/apis
6 |
7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete
8 |
9 | mkdir -p $PROTO_DIR/google/api
10 | mkdir -p $PROTO_DIR/validate
11 |
12 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; }
13 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; }
14 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; }
15 |
16 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/router/options)
17 |
18 | # Generate for toolkit service
19 | for PROTO in $PROTOS; do
20 | protoc \
21 | -I. \
22 | -I$GOPATH/src \
23 | -I$PROTO_DIR \
24 | -I$(dirname $PROTO) \
25 | --validate_out=lang=go:$GOPATH/src \
26 | --go_out=:$GOPATH/src \
27 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \
28 | --toolkit_out=$GOPATH/src \
29 | $PROTO
30 | done
31 |
32 | rm -r $PROTO_DIR/google
33 | rm -r $PROTO_DIR/validate
34 |
35 | echo "Generation is ok"
36 |
--------------------------------------------------------------------------------
/examples/helloworld/apis/helloworld.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package helloworld;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/examples/helloworld/gen;servicepb";
6 |
7 | // The greeting service definition.
8 | service Greeter {
9 | // Sends a greeting
10 | rpc SayHello (HelloRequest) returns (HelloReply) {}
11 | }
12 |
13 | // The request message containing the user's name.
14 | message HelloRequest {
15 | string name = 1;
16 | }
17 |
18 | // The response message containing the greetings
19 | message HelloReply {
20 | string message = 1;
21 | }
22 |
--------------------------------------------------------------------------------
/examples/helloworld/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate ./scripts/generate.sh
4 |
--------------------------------------------------------------------------------
/examples/helloworld/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "context"
21 | "flag"
22 | "fmt"
23 | "log"
24 | "net"
25 |
26 | pb "github.com/lastbackend/toolkit/examples/helloworld/gen"
27 | "google.golang.org/grpc"
28 | )
29 |
30 | var (
31 | port = flag.Int("port", 9000, "The server port")
32 | )
33 |
34 | // server is used to implement lastbackend.helloworld.GreeterServer.
35 | type server struct {
36 | pb.UnimplementedGreeterServer
37 | }
38 |
39 | // SayHello implements lastbackend.helloworld.GreeterServer
40 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
41 | log.Printf("Received: %v", in.GetName())
42 | return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
43 | }
44 |
45 | func main() {
46 | flag.Parse()
47 |
48 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
49 | if err != nil {
50 | log.Fatalf("failed to listen: %v", err)
51 | }
52 |
53 | s := grpc.NewServer()
54 |
55 | pb.RegisterGreeterServer(s, &server{})
56 |
57 | log.Printf("server listening at %v", lis.Addr())
58 |
59 | if err := s.Serve(lis); err != nil {
60 | log.Fatalf("failed to serve: %v", err)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/examples/helloworld/scripts/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/helloworld
4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE
5 | PROTO_DIR=$ROOT_DIR/apis
6 |
7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete
8 |
9 | mkdir -p $PROTO_DIR/google/api
10 |
11 | curl -s -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
12 | curl -s -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
13 |
14 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/validate)
15 |
16 | # Generate GRPC service
17 | for PROTO in $PROTOS; do
18 | protoc \
19 | -I. \
20 | -I$GOPATH/src \
21 | -I$PROTO_DIR \
22 | -I$(dirname $PROTO) \
23 | --go_out=:$GOPATH/src \
24 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \
25 | $PROTO
26 | done
27 |
28 | rm -r $PROTO_DIR/google
29 |
--------------------------------------------------------------------------------
/examples/http/apis/server.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package http;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/examples/http/gen/server;serverpb";
6 |
7 | import "google/api/annotations.proto";
8 |
9 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto";
10 |
11 | // Messages
12 |
13 | // The request message containing the user's name.
14 | message HelloRequest {
15 | string name = 1;
16 | }
17 |
18 | // The response message containing the greetings
19 | message HelloResponse {
20 | string message = 1;
21 | }
22 |
23 | // =====================================================
24 | // HTTP methods
25 | // =====================================================
26 |
27 | // Example methods
28 | service Http {
29 | option (toolkit.runtime) = {
30 | servers: [HTTP]
31 | };
32 | option (toolkit.server) = {
33 | middlewares: []
34 | };
35 |
36 | rpc HelloWorld(HelloRequest) returns (HelloResponse) {
37 | option (google.api.http) = {
38 | get: "/hello"
39 | };
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/examples/http/gen/server/client/server.pb.toolkit.rpc.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/http/apis/server.proto
3 |
4 | package serverpb
5 |
6 | import (
7 | context "context"
8 |
9 | "github.com/lastbackend/toolkit/examples/http/gen/server"
10 | client "github.com/lastbackend/toolkit/pkg/client"
11 | emptypb "google.golang.org/protobuf/types/known/emptypb"
12 | )
13 |
14 | // Suppress "imported and not used" errors
15 | var _ context.Context
16 | var _ emptypb.Empty
17 |
18 | // Client gRPC API for Http service
19 | func NewHttpRPCClient(service string, c client.GRPCClient) HttpRPCClient {
20 | return &httpGrpcRPCClient{service, c}
21 | }
22 |
23 | // Client gRPC API for Http service
24 | type HttpRPCClient interface {
25 | HelloWorld(ctx context.Context, req *serverpb.HelloRequest, opts ...client.GRPCCallOption) (*serverpb.HelloResponse, error)
26 | }
27 |
28 | type httpGrpcRPCClient struct {
29 | service string
30 | cli client.GRPCClient
31 | }
32 |
33 | func (c *httpGrpcRPCClient) HelloWorld(ctx context.Context, req *serverpb.HelloRequest, opts ...client.GRPCCallOption) (*serverpb.HelloResponse, error) {
34 | resp := new(serverpb.HelloResponse)
35 | if err := c.cli.Call(ctx, c.service, Http_HelloWorldMethod, req, resp, opts...); err != nil {
36 | return nil, err
37 | }
38 | return resp, nil
39 | }
40 |
41 | func (httpGrpcRPCClient) mustEmbedUnimplementedHttpClient() {}
42 |
43 | // Client methods for Http service
44 | const (
45 | Http_HelloWorldMethod = "/http.Http/HelloWorld"
46 | )
47 |
--------------------------------------------------------------------------------
/examples/http/gen/server/server_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 | // versions:
3 | // - protoc-gen-go-grpc v1.3.0
4 | // - protoc v4.25.2
5 | // source: github.com/lastbackend/toolkit/examples/http/apis/server.proto
6 |
7 | package serverpb
8 |
9 | import (
10 | context "context"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | )
15 |
16 | // This is a compile-time assertion to ensure that this generated file
17 | // is compatible with the grpc package it is being compiled against.
18 | // Requires gRPC-Go v1.32.0 or later.
19 | const _ = grpc.SupportPackageIsVersion7
20 |
21 | const (
22 | Http_HelloWorld_FullMethodName = "/http.Http/HelloWorld"
23 | )
24 |
25 | // HttpClient is the client API for Http service.
26 | //
27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
28 | type HttpClient interface {
29 | HelloWorld(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
30 | }
31 |
32 | type httpClient struct {
33 | cc grpc.ClientConnInterface
34 | }
35 |
36 | func NewHttpClient(cc grpc.ClientConnInterface) HttpClient {
37 | return &httpClient{cc}
38 | }
39 |
40 | func (c *httpClient) HelloWorld(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
41 | out := new(HelloResponse)
42 | err := c.cc.Invoke(ctx, Http_HelloWorld_FullMethodName, in, out, opts...)
43 | if err != nil {
44 | return nil, err
45 | }
46 | return out, nil
47 | }
48 |
49 | // HttpServer is the server API for Http service.
50 | // All implementations should embed UnimplementedHttpServer
51 | // for forward compatibility
52 | type HttpServer interface {
53 | HelloWorld(context.Context, *HelloRequest) (*HelloResponse, error)
54 | }
55 |
56 | // UnimplementedHttpServer should be embedded to have forward compatible implementations.
57 | type UnimplementedHttpServer struct {
58 | }
59 |
60 | func (UnimplementedHttpServer) HelloWorld(context.Context, *HelloRequest) (*HelloResponse, error) {
61 | return nil, status.Errorf(codes.Unimplemented, "method HelloWorld not implemented")
62 | }
63 |
64 | // UnsafeHttpServer may be embedded to opt out of forward compatibility for this service.
65 | // Use of this interface is not recommended, as added methods to HttpServer will
66 | // result in compilation errors.
67 | type UnsafeHttpServer interface {
68 | mustEmbedUnimplementedHttpServer()
69 | }
70 |
71 | func RegisterHttpServer(s grpc.ServiceRegistrar, srv HttpServer) {
72 | s.RegisterService(&Http_ServiceDesc, srv)
73 | }
74 |
75 | func _Http_HelloWorld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
76 | in := new(HelloRequest)
77 | if err := dec(in); err != nil {
78 | return nil, err
79 | }
80 | if interceptor == nil {
81 | return srv.(HttpServer).HelloWorld(ctx, in)
82 | }
83 | info := &grpc.UnaryServerInfo{
84 | Server: srv,
85 | FullMethod: Http_HelloWorld_FullMethodName,
86 | }
87 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
88 | return srv.(HttpServer).HelloWorld(ctx, req.(*HelloRequest))
89 | }
90 | return interceptor(ctx, in, info, handler)
91 | }
92 |
93 | // Http_ServiceDesc is the grpc.ServiceDesc for Http service.
94 | // It's only intended for direct use with grpc.RegisterService,
95 | // and not to be introspected or modified (even as a copy)
96 | var Http_ServiceDesc = grpc.ServiceDesc{
97 | ServiceName: "http.Http",
98 | HandlerType: (*HttpServer)(nil),
99 | Methods: []grpc.MethodDesc{
100 | {
101 | MethodName: "HelloWorld",
102 | Handler: _Http_HelloWorld_Handler,
103 | },
104 | },
105 | Streams: []grpc.StreamDesc{},
106 | Metadata: "github.com/lastbackend/toolkit/examples/http/apis/server.proto",
107 | }
108 |
--------------------------------------------------------------------------------
/examples/http/gen/server/server_service.pb.toolkit.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/http/apis/server.proto
3 |
4 | package serverpb
5 |
6 | import (
7 | "context"
8 | "encoding/json"
9 | "io"
10 | "net/http"
11 |
12 | toolkit "github.com/lastbackend/toolkit"
13 | client "github.com/lastbackend/toolkit/pkg/client"
14 | runtime "github.com/lastbackend/toolkit/pkg/runtime"
15 | controller "github.com/lastbackend/toolkit/pkg/runtime/controller"
16 | tk_http "github.com/lastbackend/toolkit/pkg/server/http"
17 | errors "github.com/lastbackend/toolkit/pkg/server/http/errors"
18 | tk_ws "github.com/lastbackend/toolkit/pkg/server/http/websockets"
19 | emptypb "google.golang.org/protobuf/types/known/emptypb"
20 | )
21 |
22 | // This is a compile-time assertion to ensure that this generated file
23 | // is compatible with the toolkit package it is being compiled against and
24 | // suppress "imported and not used" errors
25 | var (
26 | _ context.Context
27 | _ emptypb.Empty
28 | _ http.Handler
29 | _ errors.Err
30 | _ io.Reader
31 | _ json.Marshaler
32 | _ tk_ws.Client
33 | _ tk_http.Handler
34 | _ client.GRPCClient
35 | )
36 |
37 | // Definitions
38 |
39 | // Service Http define
40 | type serviceHttp struct {
41 | runtime runtime.Runtime
42 | }
43 |
44 | func NewHttpService(name string, opts ...runtime.Option) (_ toolkit.Service, err error) {
45 | app := new(serviceHttp)
46 |
47 | app.runtime, err = controller.NewRuntime(context.Background(), name, opts...)
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | // loop over plugins and initialize plugin instance
53 |
54 | // loop over plugins and register plugin in toolkit
55 |
56 | // create new Http HTTP server
57 | app.runtime.Server().HTTPNew(name, nil)
58 |
59 | app.runtime.Server().HTTP().AddHandler(http.MethodGet, "/hello", app.handlerHTTPHttpHelloWorld)
60 |
61 | return app.runtime.Service(), nil
62 | }
63 |
64 | // Define services for Http HTTP server
65 |
66 | type HttpHTTPService interface {
67 | HelloWorld(ctx context.Context, req *HelloRequest) (*HelloResponse, error)
68 | }
69 |
70 | // Define HTTP handlers for Router HTTP server
71 |
72 | func (s *serviceHttp) handlerHTTPHttpHelloWorld(w http.ResponseWriter, r *http.Request) {
73 | ctx, cancel := context.WithCancel(r.Context())
74 | defer cancel()
75 |
76 | var protoRequest HelloRequest
77 | var protoResponse *HelloResponse
78 |
79 | _, om := tk_http.GetMarshaler(s.runtime.Server().HTTP(), r)
80 |
81 | if err := r.ParseForm(); err != nil {
82 | errors.HTTP.InternalServerError(w)
83 | return
84 | }
85 |
86 | if err := tk_http.ParseRequestQueryParametersToProto(&protoRequest, r.Form); err != nil {
87 | errors.HTTP.InternalServerError(w)
88 | return
89 | }
90 |
91 | headers, err := tk_http.PrepareHeaderFromRequest(r)
92 | if err != nil {
93 | errors.HTTP.InternalServerError(w)
94 | return
95 | }
96 |
97 | ctx = tk_http.NewIncomingContext(ctx, headers)
98 |
99 | protoResponse, err = s.runtime.Server().HTTP().GetService().(HttpHTTPService).HelloWorld(ctx, &protoRequest)
100 | if err != nil {
101 | errors.GrpcErrorHandlerFunc(w, err)
102 | return
103 | }
104 |
105 | buf, err := om.Marshal(protoResponse)
106 | if err != nil {
107 | errors.HTTP.InternalServerError(w)
108 | return
109 | }
110 |
111 | w.Header().Set("Content-Type", om.ContentType())
112 | if proceed, err := tk_http.HandleGRPCResponse(w, r, headers); err != nil || !proceed {
113 | return
114 | }
115 |
116 | if _, err = w.Write(buf); err != nil {
117 | return
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/examples/http/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate ./scripts/generate.sh
4 |
--------------------------------------------------------------------------------
/examples/http/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler/jsonpb"
23 | "google.golang.org/protobuf/encoding/protojson"
24 | "io"
25 | "net/http"
26 | "os"
27 |
28 | servicepb "github.com/lastbackend/toolkit/examples/http/gen/server"
29 | "github.com/lastbackend/toolkit/pkg/runtime"
30 | )
31 |
32 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
33 | w.Header().Set("Content-Type", "application/json")
34 | w.WriteHeader(http.StatusOK)
35 | if _, err := io.WriteString(w, `{"alive": true}`); err != nil {
36 | fmt.Println(err)
37 | }
38 | }
39 |
40 | func main() {
41 | // define service with name and options
42 | app, err := servicepb.NewHttpService("http",
43 | runtime.WithVersion("0.1.0"),
44 | runtime.WithDescription("Example http server"),
45 | runtime.WithEnvPrefix("LB"),
46 | )
47 | if err != nil {
48 | fmt.Println(err)
49 | }
50 |
51 | // Register marshaller
52 | jsonPb := &jsonpb.JSONPb{
53 | UnmarshalOptions: protojson.UnmarshalOptions{
54 | DiscardUnknown: true,
55 | },
56 | }
57 |
58 | if err = app.Server().HTTP().UseMarshaler("application/json", jsonPb); err != nil {
59 | app.Log().Errorf("could not use json marshaler %v", err)
60 | os.Exit(1)
61 | return
62 | }
63 |
64 | // Add server
65 | app.Server().HTTP().AddHandler(http.MethodGet, "/health", HealthCheckHandler)
66 |
67 | // Logger settings
68 | app.Log().Info("Run http server")
69 |
70 | // Service run
71 | if err := app.Start(context.Background()); err != nil {
72 | app.Log().Errorf("could not run the service %v", err)
73 | os.Exit(1)
74 | return
75 | }
76 |
77 | app.Log().Info("graceful stop")
78 | }
79 |
--------------------------------------------------------------------------------
/examples/http/middleware/example.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package middleware
18 |
19 | import (
20 | "fmt"
21 | "net/http"
22 | )
23 |
24 | // ExampleMiddleware - show request info middleware
25 | func ExampleMiddleware(h http.Handler) http.Handler {
26 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 | fmt.Println("Call: ExampleMiddleware")
28 | h.ServeHTTP(w, r)
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/examples/http/middleware/request.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "crypto/rand"
6 | "encoding/base64"
7 | "fmt"
8 | "net/http"
9 | "os"
10 | "strings"
11 | "sync/atomic"
12 | )
13 |
14 | // KeyPEMBlock to use when setting the request UserID.
15 | type ctxKeyRequestID int
16 |
17 | // RequestIDKey is the key that holds th unique request UserID in a request context.
18 | const RequestIDKey ctxKeyRequestID = 0
19 |
20 | var (
21 | // prefix is const prefix for request UserID
22 | prefix string
23 |
24 | // reqID is counter for request UserID
25 | reqID uint64
26 | )
27 |
28 | // init Initializes constant part of request UserID
29 | func init() {
30 | hostname, err := os.Hostname()
31 | if hostname == "" || err != nil {
32 | hostname = "localhost"
33 | }
34 | var buf [12]byte
35 | var b64 string
36 | for len(b64) < 10 {
37 | _, _ = rand.Read(buf[:])
38 | b64 = base64.StdEncoding.EncodeToString(buf[:])
39 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
40 | }
41 |
42 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
43 | }
44 |
45 | // RequestID is a middleware that injects a request UserID into the context of each
46 | // request. A request UserID is a string of the form "host.example.com/random-0001",
47 | // where "random" is a base62 random string that uniquely identifies this go
48 | // process, and where the last number is an atomically incremented request
49 | // counter.
50 | func RequestID(h http.Handler) http.Handler {
51 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
52 | id := atomic.AddUint64(&reqID, 1)
53 | ctx := r.Context()
54 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, id))
55 | h.ServeHTTP(w, r.WithContext(ctx))
56 | })
57 | }
58 |
59 | // GetReqID returns a request UserID from the given context if one is present.
60 | // Returns the empty string if a request UserID cannot be found.
61 | func GetReqID(ctx context.Context) string {
62 | if ctx == nil {
63 | return ""
64 | }
65 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
66 | return reqID
67 | }
68 | return ""
69 | }
70 |
--------------------------------------------------------------------------------
/examples/http/scripts/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/http
4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE
5 | PROTO_DIR=$ROOT_DIR/apis
6 |
7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete
8 |
9 | mkdir -p $PROTO_DIR/google/api
10 | mkdir -p $PROTO_DIR/validate
11 |
12 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; }
13 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; }
14 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; }
15 |
16 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/router/options)
17 |
18 | # Generate for toolkit service
19 | for PROTO in $PROTOS; do
20 | protoc \
21 | -I. \
22 | -I$GOPATH/src \
23 | -I$PROTO_DIR \
24 | -I$(dirname $PROTO) \
25 | --validate_out=lang=go:$GOPATH/src \
26 | --go_out=:$GOPATH/src \
27 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \
28 | --toolkit_out=$GOPATH/src \
29 | $PROTO
30 | done
31 |
32 | rm -r $PROTO_DIR/google
33 | rm -r $PROTO_DIR/validate
34 |
35 | echo "Generation is ok"
36 |
--------------------------------------------------------------------------------
/examples/service/apis/example.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package lastbackend.example;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/examples/service/gen;servicepb";
6 |
7 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto";
8 | import "github.com/lastbackend/toolkit/examples/service/apis/ptypes/messages.proto";
9 |
10 | // =====================================================
11 | // Generate mocks
12 | // =====================================================
13 | option (toolkit.tests_spec) = {
14 | mockery: {
15 | package: "github.com/lastbackend/toolkit/examples/service/tests/service_mocks"
16 | }
17 | };
18 |
19 | // =====================================================
20 | // Install clients
21 | // =====================================================
22 | option (toolkit.services) = {
23 | service: "example",
24 | package: "github.com/lastbackend/toolkit/examples/service/gen/client"
25 | };
26 |
27 | option (toolkit.plugins) = {
28 | prefix: "pgsql"
29 | plugin: "postgres_gorm"
30 | };
31 |
32 | option (toolkit.plugins) = {
33 | prefix: "redis"
34 | plugin: "redis"
35 | };
36 |
37 |
38 | // =====================================================
39 | // RPC methods
40 | // =====================================================
41 | service Example {
42 | option (toolkit.runtime) = {
43 | servers: [GRPC, HTTP]
44 | };
45 | option (toolkit.server) = {
46 | middlewares: [
47 | "request_id"
48 | ]
49 | };
50 |
51 | // Example methods
52 | rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse) {}
53 | };
54 |
55 | service Sample {
56 | option (toolkit.runtime).plugins = {
57 | prefix: "redis2"
58 | plugin: "redis"
59 | };
60 | };
61 |
--------------------------------------------------------------------------------
/examples/service/apis/ptypes/messages.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package lastbackend.example;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/examples/service/gen/ptypes;typespb";
6 |
7 | import "validate/validate.proto";
8 |
9 |
10 | // ============================================================================
11 | // Request ====================================================================
12 | // ============================================================================
13 |
14 | message HelloWorldRequest {
15 | string name = 1 [(validate.rules).string.min_len = 1];
16 | string type = 2 [(validate.rules).string.max_len = 1024];
17 | map data = 3 [(validate.rules).map.min_pairs = 1];
18 | }
19 |
20 | // ============================================================================
21 | // Response ===================================================================
22 | // ============================================================================
23 |
24 | message HelloWorldResponse {
25 | string id = 1;
26 | string name = 2;
27 | string type = 3;
28 | map data = 4;
29 | int64 created_at = 5;
30 | int64 updated_at = 6;
31 | }
32 |
--------------------------------------------------------------------------------
/examples/service/config/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package config
18 |
19 | type Config struct {
20 | DEMO string `env:"DEMO" required:"true" envDefault:"BAR"`
21 | Nested NestedConfig `envPrefix:"NESTED_"`
22 | }
23 |
24 | type NestedConfig struct {
25 | Demo string `env:"DEMO"`
26 | }
27 |
28 | func New() *Config {
29 | c := new(Config)
30 | return c
31 | }
32 |
--------------------------------------------------------------------------------
/examples/service/gen/client/demo/demo.go:
--------------------------------------------------------------------------------
1 | package demo
2 |
--------------------------------------------------------------------------------
/examples/service/gen/client/example.pb.toolkit.rpc.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/service/apis/example.proto
3 |
4 | package servicepb
5 |
6 | import (
7 | context "context"
8 |
9 | "github.com/lastbackend/toolkit/examples/service/gen/ptypes"
10 | client "github.com/lastbackend/toolkit/pkg/client"
11 | emptypb "google.golang.org/protobuf/types/known/emptypb"
12 | )
13 |
14 | // Suppress "imported and not used" errors
15 | var _ context.Context
16 | var _ emptypb.Empty
17 |
18 | // Client gRPC API for Example service
19 | func NewExampleRPCClient(service string, c client.GRPCClient) ExampleRPCClient {
20 | return &exampleGrpcRPCClient{service, c}
21 | }
22 |
23 | // Client gRPC API for Example service
24 | type ExampleRPCClient interface {
25 | HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest, opts ...client.GRPCCallOption) (*typespb.HelloWorldResponse, error)
26 | }
27 |
28 | // Client gRPC API for Sample service
29 | func NewSampleRPCClient(service string, c client.GRPCClient) SampleRPCClient {
30 | return &sampleGrpcRPCClient{service, c}
31 | }
32 |
33 | // Client gRPC API for Sample service
34 | type SampleRPCClient interface {
35 | }
36 |
37 | type exampleGrpcRPCClient struct {
38 | service string
39 | cli client.GRPCClient
40 | }
41 |
42 | func (c *exampleGrpcRPCClient) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest, opts ...client.GRPCCallOption) (*typespb.HelloWorldResponse, error) {
43 | resp := new(typespb.HelloWorldResponse)
44 | if err := c.cli.Call(ctx, c.service, Example_HelloWorldMethod, req, resp, opts...); err != nil {
45 | return nil, err
46 | }
47 | return resp, nil
48 | }
49 |
50 | func (exampleGrpcRPCClient) mustEmbedUnimplementedExampleClient() {}
51 |
52 | type sampleGrpcRPCClient struct {
53 | service string
54 | cli client.GRPCClient
55 | }
56 |
57 | func (sampleGrpcRPCClient) mustEmbedUnimplementedSampleClient() {}
58 |
59 | // Client methods for Example service
60 | const (
61 | Example_HelloWorldMethod = "/lastbackend.example.Example/HelloWorld"
62 | )
63 |
--------------------------------------------------------------------------------
/examples/service/gen/example.pb.validate.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-validate. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/service/apis/example.proto
3 |
4 | package servicepb
5 |
6 | import (
7 | "bytes"
8 | "errors"
9 | "fmt"
10 | "net"
11 | "net/mail"
12 | "net/url"
13 | "regexp"
14 | "sort"
15 | "strings"
16 | "time"
17 | "unicode/utf8"
18 |
19 | "google.golang.org/protobuf/types/known/anypb"
20 | )
21 |
22 | // ensure the imports are used
23 | var (
24 | _ = bytes.MinRead
25 | _ = errors.New("")
26 | _ = fmt.Print
27 | _ = utf8.UTFMax
28 | _ = (*regexp.Regexp)(nil)
29 | _ = (*strings.Reader)(nil)
30 | _ = net.IPv4len
31 | _ = time.Duration(0)
32 | _ = (*url.URL)(nil)
33 | _ = (*mail.Address)(nil)
34 | _ = anypb.Any{}
35 | _ = sort.Sort
36 | )
37 |
--------------------------------------------------------------------------------
/examples/service/gen/tests/example.pb.toolkit.mockery.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/service/apis/example.proto
3 |
4 | package servicepb
5 |
6 | // Warning: You have no mock in provided directory. Please check mockery docs for mocks generation.
7 |
--------------------------------------------------------------------------------
/examples/service/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate ./scripts/generate.sh
4 |
--------------------------------------------------------------------------------
/examples/service/internal/controller/controller.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "context"
5 | "github.com/lastbackend/toolkit/pkg/client"
6 | "time"
7 |
8 | "github.com/lastbackend/toolkit"
9 | "github.com/lastbackend/toolkit/examples/service/config"
10 | servicepb "github.com/lastbackend/toolkit/examples/service/gen"
11 | typespb "github.com/lastbackend/toolkit/examples/service/gen/ptypes"
12 | "github.com/lastbackend/toolkit/examples/service/internal/repository"
13 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
14 | )
15 |
16 | type Controller struct {
17 | app toolkit.Service
18 | log logger.Logger
19 | cfg *config.Config
20 | repo *repository.Repository
21 | services servicepb.ExampleServices
22 | }
23 |
24 | func (c *Controller) OnStart(ctx context.Context) error {
25 | c.log.Info("> service controller: on start")
26 | c.Call(ctx)
27 | return nil
28 | }
29 |
30 | func (c *Controller) Call(_ context.Context) error {
31 |
32 | header := make(map[string]string)
33 | header["x-req-id"] = time.Now().String()
34 |
35 | resp, err := c.services.Example().HelloWorld(context.Background(), &typespb.HelloWorldRequest{
36 | Name: "name",
37 | Type: "type",
38 | Data: nil,
39 | }, client.GRPCOptionHeaders(header))
40 | if err != nil {
41 | c.log.Error(err.Error())
42 | return err
43 | }
44 |
45 | c.log.Info("> service response from server:> name:", resp.Name)
46 | return nil
47 | }
48 |
49 | func (c *Controller) OnStop(context.Context) error {
50 | c.log.Info("> service controller: on stop")
51 | return nil
52 | }
53 |
54 | func NewController(app toolkit.Service, cfg *config.Config, repo *repository.Repository, services servicepb.ExampleServices) *Controller {
55 | app.Log().Info("> service controller ----")
56 | ctrl := &Controller{app: app, log: app.Log(), cfg: cfg, repo: repo, services: services}
57 | return ctrl
58 | }
59 |
--------------------------------------------------------------------------------
/examples/service/internal/repository/repository.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/lastbackend/toolkit"
7 | "github.com/lastbackend/toolkit-plugins/postgres_gorm"
8 | servicepb "github.com/lastbackend/toolkit/examples/service/gen"
9 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
10 | )
11 |
12 | // The Repository represents Model.
13 | type Repository struct {
14 | postgres_gorm.Plugin
15 | log logger.Logger
16 | }
17 |
18 | type DemoStruct struct {
19 | Id int
20 | Count int
21 | }
22 |
23 | func (r *Repository) Get(_ context.Context) *DemoStruct {
24 | val := new(DemoStruct)
25 | r.DB().First(val)
26 | return val
27 | }
28 |
29 | // NewRepository Model with given configurations.
30 | func NewRepository(app toolkit.Service, psql servicepb.PgsqlPlugin) *Repository {
31 | repo := &Repository{psql, app.Log()}
32 | app.Log().Info("repository init")
33 | return repo
34 | }
35 |
--------------------------------------------------------------------------------
/examples/service/internal/server/handler.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "net/http"
4 |
5 | // ExampleHTTPServerHandler server definitions
6 | func ExampleHTTPServerHandler(w http.ResponseWriter, r *http.Request) {
7 | _, _ = w.Write([]byte("ok"))
8 | }
9 |
--------------------------------------------------------------------------------
/examples/service/internal/server/interceptor.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "github.com/lastbackend/toolkit/pkg/runtime"
6 | "github.com/lastbackend/toolkit/pkg/server"
7 | "google.golang.org/grpc"
8 | "time"
9 | )
10 |
11 | type ExampleGRPCServerInterceptor struct {
12 | server.GRPCInterceptor
13 | name server.KindInterceptor
14 | r runtime.Runtime
15 | }
16 |
17 | func NewExampleGRPCServerInterceptor(r runtime.Runtime) server.GRPCInterceptor {
18 | i := ExampleGRPCServerInterceptor{r: r}
19 | return &i
20 | }
21 |
22 | func (i *ExampleGRPCServerInterceptor) Kind() server.KindInterceptor {
23 | return i.name
24 | }
25 |
26 | func (i *ExampleGRPCServerInterceptor) Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
27 | i.r.Log().Info("Log: 1")
28 | time.Sleep(time.Second)
29 |
30 | i.r.Log().Info("Logging")
31 |
32 | res, err := handler(ctx, req)
33 |
34 | i.r.Log().Info("Log: 2")
35 | time.Sleep(time.Second)
36 |
37 | return res, err
38 | }
39 |
--------------------------------------------------------------------------------
/examples/service/internal/server/middleware.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | servicepb "github.com/lastbackend/toolkit/examples/service/gen"
8 | typespb "github.com/lastbackend/toolkit/examples/service/gen/ptypes"
9 | "github.com/lastbackend/toolkit/pkg/runtime"
10 | "github.com/lastbackend/toolkit/pkg/server"
11 | )
12 |
13 | type Data string
14 |
15 | const MWAuthenticate server.KindMiddleware = "mwauthenticate"
16 |
17 | type ExampleHTTPServerMiddleware struct {
18 | server.DefaultHttpServerMiddleware
19 | name server.KindMiddleware
20 | runtime runtime.Runtime
21 | service servicepb.ExampleServices
22 | }
23 |
24 | func (e *ExampleHTTPServerMiddleware) Apply(h http.HandlerFunc) http.HandlerFunc {
25 | return func(w http.ResponseWriter, r *http.Request) {
26 | e.runtime.Log().Info("Call: ExampleMiddleware")
27 | resp, _ := e.service.Example().HelloWorld(context.Background(), &typespb.HelloWorldRequest{
28 | Name: "middleware",
29 | Type: "test",
30 | Data: nil,
31 | })
32 |
33 | e.runtime.Log().Info("middleware resp response:> ", resp.Name)
34 | // Set example data to request context
35 | ctx := context.WithValue(r.Context(), Data("test-data"), "example context data")
36 |
37 | h.ServeHTTP(w, r.WithContext(ctx))
38 | }
39 | }
40 |
41 | func (e *ExampleHTTPServerMiddleware) Kind() server.KindMiddleware {
42 | return e.name
43 | }
44 |
45 | func RegisterExampleHTTPServerMiddleware(runtime runtime.Runtime, service servicepb.ExampleServices) server.HttpServerMiddleware {
46 | return &ExampleHTTPServerMiddleware{name: MWAuthenticate, runtime: runtime, service: service}
47 | }
48 |
--------------------------------------------------------------------------------
/examples/service/internal/server/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package server
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "google.golang.org/grpc"
23 | "google.golang.org/grpc/codes"
24 | "google.golang.org/grpc/metadata"
25 | "google.golang.org/grpc/status"
26 | "github.com/lastbackend/toolkit/examples/service/internal/repository"
27 |
28 | "github.com/lastbackend/toolkit"
29 | "github.com/lastbackend/toolkit/examples/service/config"
30 | servicepb "github.com/lastbackend/toolkit/examples/service/gen"
31 | typespb "github.com/lastbackend/toolkit/examples/service/gen/ptypes"
32 | )
33 |
34 | type Handlers struct {
35 | servicepb.ExampleRpcServer
36 |
37 | app toolkit.Service
38 | cfg *config.Config
39 | repo *repository.Repository
40 | }
41 |
42 | func (h Handlers) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest) (*typespb.HelloWorldResponse, error) {
43 | h.app.Log().Info("ExamplseRpcServer: HelloWorld: call")
44 |
45 | md, ok := metadata.FromIncomingContext(ctx)
46 | if !ok {
47 | return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
48 | }
49 |
50 | demo := h.repo.Get(ctx)
51 |
52 | resp := typespb.HelloWorldResponse{
53 | Id: fmt.Sprintf("%d", demo.Id),
54 | Name: fmt.Sprintf("%s: %d", req.Name, demo.Count),
55 | Type: req.Type,
56 | }
57 |
58 | if len(md["x-req-id"]) > 0 {
59 | header := metadata.New(map[string]string{"x-response-id": md["x-req-id"][0]})
60 | grpc.SendHeader(ctx, header)
61 | }
62 |
63 | return &resp, nil
64 | }
65 |
66 | func NewServer(app toolkit.Service, cfg *config.Config, repo *repository.Repository) servicepb.ExampleRpcServer {
67 | return &Handlers{
68 | repo: repo,
69 | app: app,
70 | cfg: cfg,
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/service/internal/server/server_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "net"
8 | "testing"
9 |
10 | "github.com/lastbackend/toolkit/pkg/runtime/controller"
11 |
12 | "google.golang.org/grpc/credentials/insecure"
13 |
14 | servicepb "github.com/lastbackend/toolkit/examples/service/gen"
15 | ptypes "github.com/lastbackend/toolkit/examples/service/gen/ptypes"
16 | "google.golang.org/grpc"
17 | "google.golang.org/grpc/codes"
18 | "google.golang.org/grpc/status"
19 | "google.golang.org/grpc/test/bufconn"
20 | )
21 |
22 | type ExampleServer struct {
23 | servicepb.UnimplementedExampleServer
24 | }
25 |
26 | func dialer() func(context.Context, string) (net.Conn, error) {
27 | listener := bufconn.Listen(1024 * 1024)
28 |
29 | runtime, _ := controller.NewRuntime(context.Background(), "test")
30 | runtime.Server().GRPC().RegisterService(NewServer(runtime.Service(), nil, nil))
31 |
32 | go func() {
33 | runtime.Server().GRPC().Start(context.Background())
34 | }()
35 |
36 | return func(context.Context, string) (net.Conn, error) {
37 | return listener.Dial()
38 | }
39 | }
40 |
41 | func TestExampleServer_HelloWorld(t *testing.T) {
42 | tests := []struct {
43 | name string
44 | amount float32
45 | res *ptypes.HelloWorldResponse
46 | errCode codes.Code
47 | errMsg string
48 | }{
49 | {
50 | "empty hello world response",
51 | -1.11,
52 | nil,
53 | codes.InvalidArgument,
54 | fmt.Sprintf("cannot deposit %v", -1.11),
55 | },
56 | {
57 | "spaced hello world response",
58 | 0.00,
59 | &ptypes.HelloWorldResponse{
60 | Id: "1",
61 | Name: "",
62 | Type: "",
63 | Data: nil,
64 | CreatedAt: 0,
65 | UpdatedAt: 0,
66 | },
67 | codes.OK,
68 | "",
69 | },
70 | }
71 |
72 | ctx := context.Background()
73 |
74 | conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer()))
75 | if err != nil {
76 | log.Fatal(err)
77 | }
78 | defer conn.Close()
79 |
80 | client := servicepb.NewExampleClient(conn)
81 |
82 | for _, tt := range tests {
83 | t.Run(tt.name, func(t *testing.T) {
84 | request := &ptypes.HelloWorldRequest{}
85 |
86 | response, err := client.HelloWorld(ctx, request)
87 | if response != nil {
88 | if tt.res == nil {
89 | t.Error("rsponse: expected nil. received: ", response)
90 | } else {
91 | if response.Id != tt.res.Id {
92 | t.Error("response: expected", tt.res.Id, "received", response.Id)
93 | }
94 | }
95 | }
96 |
97 | if err != nil {
98 | if er, ok := status.FromError(err); ok {
99 | if er.Code() != tt.errCode {
100 | t.Error("error code: expected", codes.InvalidArgument, "received", er.Code())
101 | }
102 | if er.Message() != tt.errMsg {
103 | t.Error("error message: expected", tt.errMsg, "received", er.Message())
104 | }
105 | }
106 | }
107 | })
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/examples/service/mage.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 | // +build ignore
3 |
4 | package main
5 |
6 | import (
7 | "os"
8 |
9 | "github.com/magefile/mage/mage"
10 | )
11 |
12 | func main() { os.Exit(mage.Main()) }
13 |
--------------------------------------------------------------------------------
/examples/service/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "github.com/lastbackend/toolkit/examples/service/config"
23 | servicepb "github.com/lastbackend/toolkit/examples/service/gen"
24 | "github.com/lastbackend/toolkit/examples/service/internal/controller"
25 | "github.com/lastbackend/toolkit/examples/service/internal/repository"
26 | "github.com/lastbackend/toolkit/examples/service/internal/server"
27 | "github.com/lastbackend/toolkit/pkg/runtime"
28 | "github.com/lastbackend/toolkit/pkg/server/http"
29 | "os"
30 | "time"
31 | )
32 |
33 | func main() {
34 | // define service with name and options
35 | app, err := servicepb.NewExampleService("example",
36 | runtime.WithVersion("0.1.0"),
37 | runtime.WithDescription("Example microservice"),
38 | runtime.WithEnvPrefix("LB"),
39 | )
40 | if err != nil {
41 | fmt.Println(err)
42 | }
43 |
44 | // Config management
45 | cfg := config.New()
46 |
47 | if err := app.RegisterConfig(cfg); err != nil {
48 | app.Log().Error(err)
49 | return
50 | }
51 |
52 | // Add packages
53 | app.RegisterPackage(repository.NewRepository, controller.NewController)
54 |
55 | // Add server
56 | app.Server().GRPC().SetService(server.NewServer)
57 | app.Server().GRPC().SetInterceptor(server.NewExampleGRPCServerInterceptor)
58 |
59 | app.Server().HTTPNew("", nil)
60 | app.Server().HTTP().SetMiddleware(server.RegisterExampleHTTPServerMiddleware)
61 | app.Server().HTTP().AddHandler(http.MethodGet, "/", server.ExampleHTTPServerHandler, http.WithMiddleware(server.MWAuthenticate))
62 |
63 | app.RegisterOnStartHook(func(ctx context.Context) error {
64 | time.Sleep(3 * time.Second)
65 | app.Log().Info("call gracefully stop")
66 | app.Stop(ctx, fmt.Errorf("test error"))
67 | return nil
68 | })
69 |
70 | //go func() {
71 | // time.Sleep(5 * time.Second)
72 | // app.Log().Info("call gracefully stop")
73 | // app.Stop(context.Background())
74 | //}()
75 |
76 | // Service run
77 | if err := app.Start(context.Background()); err != nil {
78 | app.Log().Errorf("could not run the service %v", err)
79 | os.Exit(1)
80 | return
81 | }
82 |
83 | // time.Sleep(10 * time.Second)
84 | // app.Stop(context.Background())
85 |
86 | app.Log().Info("graceful stop")
87 | }
88 |
--------------------------------------------------------------------------------
/examples/service/scripts/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/service
4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE
5 | PROTO_DIR=$ROOT_DIR/apis
6 |
7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete
8 |
9 | mkdir -p $PROTO_DIR/google/api
10 | mkdir -p $PROTO_DIR/validate
11 |
12 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; }
13 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; }
14 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; }
15 |
16 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/validate)
17 |
18 | # Generate for toolkit service
19 | for PROTO in $PROTOS; do
20 | protoc \
21 | -I. \
22 | -I$GOPATH/src \
23 | -I$PROTO_DIR \
24 | -I$(dirname $PROTO) \
25 | --validate_out=lang=go:$GOPATH/src \
26 | --go_out=:$GOPATH/src \
27 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \
28 | --toolkit_out=$GOPATH/src \
29 | $PROTO
30 | done
31 |
32 | rm -r $PROTO_DIR/google
33 | rm -r $PROTO_DIR/validate
34 |
35 | echo "Generation is ok"
36 |
--------------------------------------------------------------------------------
/examples/wss/apis/server.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package gateway;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/examples/wss/gen/server;serverpb";
6 |
7 | import "google/api/annotations.proto";
8 | import "protoc-gen-openapiv2/options/annotations.proto";
9 |
10 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto";
11 | import "github.com/lastbackend/toolkit/examples/helloworld/apis/helloworld.proto";
12 |
13 | // =====================================================
14 | // Swagger spec
15 | // =====================================================
16 |
17 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
18 | info: {
19 | title: "Websocket proxy server"
20 | version: "1.0"
21 | contact: {
22 | name: "Last.Backend"
23 | url: "https://lastbackend.com"
24 | email: "teams@lastbackend.com"
25 | };
26 | };
27 | host: 'lastbackend.com'
28 | schemes: [HTTP, HTTPS]
29 | consumes: "application/json"
30 | produces: "application/json"
31 | responses: {
32 | key: "500"
33 | value: {
34 | description: "Internal Server Error"
35 | schema: {
36 | json_schema: {
37 | type: OBJECT,
38 | read_only: true,
39 | example: "{\"code\": 500, \"status\": \"Internal Server Error\", \"message\": \"Internal Server Error\"}"
40 | };
41 | };
42 | };
43 | },
44 | };
45 |
46 | // =====================================================
47 | // HTTP proxy methods
48 | // =====================================================
49 |
50 | // Example methods
51 | service Router {
52 | option (toolkit.runtime) = {
53 | servers: [HTTP, WEBSOCKET_PROXY, WEBSOCKET]
54 | plugins: [{
55 | prefix: "redis1"
56 | plugin: "redis"
57 | }]
58 | };
59 | option (toolkit.server) = {
60 | middlewares: [
61 | "example"
62 | ]
63 | };
64 |
65 | rpc Subscribe(SubscribeRequest) returns (SubscribeResponse) {
66 | option (toolkit.route).websocket = true;
67 | option (google.api.http) = {
68 | get: "/events"
69 | };
70 | };
71 | rpc SayHello(helloworld.HelloRequest) returns (helloworld.HelloReply) {
72 | option (toolkit.route).websocket_proxy = {
73 | service: "helloworld"
74 | method: "/helloworld.Greeter/SayHello"
75 | };
76 | };
77 | rpc HelloWorld(helloworld.HelloRequest) returns (helloworld.HelloReply) {
78 | option (toolkit.route) = {
79 | middlewares: ["example"]
80 | http_proxy: {
81 | service: "helloworld"
82 | method: "/helloworld.Greeter/SayHello"
83 | };
84 | };
85 | option (google.api.http) = {
86 | post: "/hello"
87 | };
88 | };
89 | }
90 |
91 | message SubscribeRequest {}
92 |
93 | message SubscribeResponse {}
94 |
--------------------------------------------------------------------------------
/examples/wss/gen/server/client/server.pb.toolkit.rpc.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT.
2 | // source: github.com/lastbackend/toolkit/examples/wss/apis/server.proto
3 |
4 | package serverpb
5 |
6 | import (
7 | context "context"
8 |
9 | "github.com/lastbackend/toolkit/examples/helloworld/gen"
10 | client "github.com/lastbackend/toolkit/pkg/client"
11 | emptypb "google.golang.org/protobuf/types/known/emptypb"
12 | )
13 |
14 | // Suppress "imported and not used" errors
15 | var _ context.Context
16 | var _ emptypb.Empty
17 |
18 | // Client gRPC API for Router service
19 | func NewRouterRPCClient(service string, c client.GRPCClient) RouterRPCClient {
20 | return &routerGrpcRPCClient{service, c}
21 | }
22 |
23 | // Client gRPC API for Router service
24 | type RouterRPCClient interface {
25 | SayHello(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error)
26 |
27 | HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error)
28 | }
29 |
30 | type routerGrpcRPCClient struct {
31 | service string
32 | cli client.GRPCClient
33 | }
34 |
35 | func (c *routerGrpcRPCClient) SayHello(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) {
36 | resp := new(servicepb.HelloReply)
37 | if err := c.cli.Call(ctx, c.service, Router_SayHelloMethod, req, resp, opts...); err != nil {
38 | return nil, err
39 | }
40 | return resp, nil
41 | }
42 |
43 | func (c *routerGrpcRPCClient) HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) {
44 | resp := new(servicepb.HelloReply)
45 | if err := c.cli.Call(ctx, c.service, Router_HelloWorldMethod, req, resp, opts...); err != nil {
46 | return nil, err
47 | }
48 | return resp, nil
49 | }
50 |
51 | func (routerGrpcRPCClient) mustEmbedUnimplementedRouterClient() {}
52 |
53 | // Client methods for Router service
54 | const (
55 | Router_SubscribeMethod = "/gateway.Router/Subscribe"
56 |
57 | Router_SayHelloMethod = "/gateway.Router/SayHello"
58 |
59 | Router_HelloWorldMethod = "/gateway.Router/HelloWorld"
60 | )
61 |
--------------------------------------------------------------------------------
/examples/wss/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate ./scripts/generate.sh
4 |
--------------------------------------------------------------------------------
/examples/wss/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "io"
23 | "net/http"
24 | "os"
25 |
26 | servicepb "github.com/lastbackend/toolkit/examples/wss/gen/server"
27 | "github.com/lastbackend/toolkit/examples/wss/middleware"
28 | "github.com/lastbackend/toolkit/pkg/runtime"
29 | tk_http "github.com/lastbackend/toolkit/pkg/server/http"
30 | "github.com/lastbackend/toolkit/pkg/server/http/websockets"
31 | )
32 |
33 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
34 | w.Header().Set("Content-Type", "application/json")
35 | w.WriteHeader(http.StatusOK)
36 | if _, err := io.WriteString(w, `{"alive": true}`); err != nil {
37 | fmt.Println(err)
38 | }
39 | }
40 |
41 | func TestWSHandler(ctx context.Context, event websockets.Event, c *websockets.Client) error {
42 | fmt.Println("Event:", event.Type, string(event.Payload))
43 | fmt.Println("Context:", ctx.Value("test-data"))
44 | return c.WriteMessage(websockets.TextMessage, event.Payload)
45 | }
46 |
47 | func main() {
48 |
49 | // define service with name and options
50 | app, err := servicepb.NewRouterService("wss",
51 | runtime.WithVersion("0.1.0"),
52 | runtime.WithDescription("Example router microservice"),
53 | runtime.WithEnvPrefix("WSS"),
54 | )
55 | if err != nil {
56 | fmt.Println(err)
57 | }
58 |
59 | // Logger settings
60 | app.Log().Info("Run microservice")
61 |
62 | // Add middleware
63 | app.Server().HTTP().SetMiddleware(middleware.RegisterExampleMiddleware)
64 | app.Server().HTTP().SetMiddleware(middleware.RegisterRequestID)
65 |
66 | // set middleware as global middleware
67 | app.Server().HTTP().UseMiddleware("request_id")
68 | app.Server().HTTP().Subscribe("event:name", TestWSHandler)
69 |
70 | // add handler to default http server
71 | app.Server().HTTP().
72 | AddHandler(http.MethodGet, "/health", HealthCheckHandler, tk_http.WithMiddleware("example"))
73 |
74 | // Service run
75 | if err := app.Start(context.Background()); err != nil {
76 | app.Log().Errorf("could not run the service %v", err)
77 | os.Exit(1)
78 | return
79 | }
80 |
81 | app.Log().Info("graceful stop")
82 | }
83 |
--------------------------------------------------------------------------------
/examples/wss/middleware/example.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package middleware
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "net/http"
23 |
24 | "github.com/lastbackend/toolkit/pkg/runtime"
25 | "github.com/lastbackend/toolkit/pkg/server"
26 | )
27 |
28 | // RegisterExampleMiddleware - show request info middleware
29 | func RegisterExampleMiddleware(runtime runtime.Runtime) server.HttpServerMiddleware {
30 | return &ExampleMiddleware{name: "example", runtime: runtime}
31 | }
32 |
33 | type ExampleMiddleware struct {
34 | name server.KindMiddleware
35 | runtime runtime.Runtime
36 | }
37 |
38 | func (e *ExampleMiddleware) Apply(h http.HandlerFunc) http.HandlerFunc {
39 | return func(w http.ResponseWriter, r *http.Request) {
40 | fmt.Println("Call: ExampleMiddleware")
41 |
42 | // Set example data to request context
43 | ctx := context.WithValue(r.Context(), "test-data", "example context data")
44 |
45 | h.ServeHTTP(w, r.WithContext(ctx))
46 | }
47 | }
48 |
49 | func (e *ExampleMiddleware) Order() int {
50 | return 0
51 | }
52 |
53 | func (e *ExampleMiddleware) Kind() server.KindMiddleware {
54 | return e.name
55 | }
56 |
--------------------------------------------------------------------------------
/examples/wss/middleware/request.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "crypto/rand"
6 | "encoding/base64"
7 | "fmt"
8 | "github.com/lastbackend/toolkit/pkg/runtime"
9 | "github.com/lastbackend/toolkit/pkg/server"
10 | "net/http"
11 | "os"
12 | "strings"
13 | "sync/atomic"
14 | )
15 |
16 | // KeyPEMBlock to use when setting the request UserID.
17 | type ctxKeyRequestID int
18 |
19 | // RequestIDKey is the key that holds th unique request UserID in a request context.
20 | const RequestIDKey ctxKeyRequestID = 0
21 |
22 | var (
23 | // prefix is const prefix for request UserID
24 | prefix string
25 |
26 | // reqID is counter for request UserID
27 | reqID uint64
28 | )
29 |
30 | // init Initializes constant part of request UserID
31 | func init() {
32 | hostname, err := os.Hostname()
33 | if hostname == "" || err != nil {
34 | hostname = "localhost"
35 | }
36 | var buf [12]byte
37 | var b64 string
38 | for len(b64) < 10 {
39 | _, _ = rand.Read(buf[:])
40 | b64 = base64.StdEncoding.EncodeToString(buf[:])
41 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
42 | }
43 |
44 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
45 | }
46 |
47 | // GetReqID returns a request UserID from the given context if one is present.
48 | // Returns the empty string if a request UserID cannot be found.
49 | func GetReqID(ctx context.Context) string {
50 | if ctx == nil {
51 | return ""
52 | }
53 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
54 | return reqID
55 | }
56 | return ""
57 | }
58 |
59 | // RegisterRequestID is a middleware that injects a request UserID into the context of each
60 | // request. A request UserID is a string of the form "host.example.com/random-0001",
61 | // where "random" is a base62 random string that uniquely identifies this go
62 | // process, and where the last number is an atomically incremented request
63 | // counter.
64 | func RegisterRequestID(runtime runtime.Runtime) server.HttpServerMiddleware {
65 | return &ExampleMiddleware{name: "request_id", runtime: runtime}
66 | }
67 |
68 | type RequestID struct {
69 | name server.KindMiddleware
70 | runtime runtime.Runtime
71 | }
72 |
73 | func (e *RequestID) Apply(h http.HandlerFunc) http.HandlerFunc {
74 | return func(w http.ResponseWriter, r *http.Request) {
75 | id := atomic.AddUint64(&reqID, 1)
76 | ctx := r.Context()
77 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, id))
78 | h.ServeHTTP(w, r.WithContext(ctx))
79 | }
80 | }
81 |
82 | func (e *RequestID) Order() int {
83 | return 0
84 | }
85 |
86 | func (e *RequestID) Kind() server.KindMiddleware {
87 | return e.name
88 | }
89 |
--------------------------------------------------------------------------------
/examples/wss/scripts/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/wss
4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE
5 | PROTO_DIR=$ROOT_DIR/apis
6 | SWAGGER_DIR_NAME=swagger
7 |
8 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete
9 |
10 | rm -rf ./${SWAGGER_DIR_NAME}
11 |
12 | mkdir -p ${SWAGGER_DIR_NAME}
13 | mkdir -p $PROTO_DIR/google/api
14 | mkdir -p $PROTO_DIR/validate
15 | mkdir -p $PROTO_DIR/protoc-gen-openapiv2/options
16 |
17 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; }
18 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; }
19 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; }
20 | curl -s -f -o $PROTO_DIR/protoc-gen-openapiv2/options/annotations.proto -L https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-openapiv2/options/annotations.proto || { echo "Error: Request failed"; exit 1; }
21 | curl -s -f -o $PROTO_DIR/protoc-gen-openapiv2/options/openapiv2.proto -L https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-openapiv2/options/openapiv2.proto || { echo "Error: Request failed"; exit 1; }
22 |
23 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/router/options)
24 |
25 | # Generate for toolkit service
26 | for PROTO in $PROTOS; do
27 | protoc \
28 | -I. \
29 | -I$GOPATH/src \
30 | -I$PROTO_DIR \
31 | -I$(dirname $PROTO) \
32 | --openapiv2_out ./${SWAGGER_DIR_NAME} --openapiv2_opt logtostderr=true \
33 | --validate_out=lang=go:$GOPATH/src \
34 | --go_out=:$GOPATH/src \
35 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \
36 | --toolkit_out=$GOPATH/src \
37 | $PROTO
38 | done
39 |
40 | mv ./${SWAGGER_DIR_NAME}/$SOURCE_PACKAGE/apis/* ./${SWAGGER_DIR_NAME}/
41 |
42 | rm -rf ./${SWAGGER_DIR_NAME}/github.com
43 | rm -r $PROTO_DIR/google
44 | rm -r $PROTO_DIR/validate
45 | rm -r $PROTO_DIR/protoc-gen-openapiv2
46 |
47 | echo "Generation is ok"
48 |
--------------------------------------------------------------------------------
/examples/wss/swagger/protoc-gen-openapiv2/options/annotations.swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "title": "github.com/lastbackend/toolkit/examples/wss/apis/protoc-gen-openapiv2/options/annotations.proto",
5 | "version": "version not set"
6 | },
7 | "consumes": [
8 | "application/json"
9 | ],
10 | "produces": [
11 | "application/json"
12 | ],
13 | "paths": {},
14 | "definitions": {
15 | "protobufAny": {
16 | "type": "object",
17 | "properties": {
18 | "@type": {
19 | "type": "string"
20 | }
21 | },
22 | "additionalProperties": {}
23 | },
24 | "rpcStatus": {
25 | "type": "object",
26 | "properties": {
27 | "code": {
28 | "type": "integer",
29 | "format": "int32"
30 | },
31 | "message": {
32 | "type": "string"
33 | },
34 | "details": {
35 | "type": "array",
36 | "items": {
37 | "type": "object",
38 | "$ref": "#/definitions/protobufAny"
39 | }
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/wss/swagger/protoc-gen-openapiv2/options/openapiv2.swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "title": "github.com/lastbackend/toolkit/examples/wss/apis/protoc-gen-openapiv2/options/openapiv2.proto",
5 | "version": "version not set"
6 | },
7 | "consumes": [
8 | "application/json"
9 | ],
10 | "produces": [
11 | "application/json"
12 | ],
13 | "paths": {},
14 | "definitions": {
15 | "protobufAny": {
16 | "type": "object",
17 | "properties": {
18 | "@type": {
19 | "type": "string"
20 | }
21 | },
22 | "additionalProperties": {}
23 | },
24 | "rpcStatus": {
25 | "type": "object",
26 | "properties": {
27 | "code": {
28 | "type": "integer",
29 | "format": "int32"
30 | },
31 | "message": {
32 | "type": "string"
33 | },
34 | "details": {
35 | "type": "array",
36 | "items": {
37 | "type": "object",
38 | "$ref": "#/definitions/protobufAny"
39 | }
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/wss/swagger/validate/validate.swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "title": "github.com/lastbackend/toolkit/examples/wss/apis/validate/validate.proto",
5 | "version": "version not set"
6 | },
7 | "consumes": [
8 | "application/json"
9 | ],
10 | "produces": [
11 | "application/json"
12 | ],
13 | "paths": {},
14 | "definitions": {
15 | "protobufAny": {
16 | "type": "object",
17 | "properties": {
18 | "@type": {
19 | "type": "string"
20 | }
21 | },
22 | "additionalProperties": {}
23 | },
24 | "rpcStatus": {
25 | "type": "object",
26 | "properties": {
27 | "code": {
28 | "type": "integer",
29 | "format": "int32"
30 | },
31 | "message": {
32 | "type": "string"
33 | },
34 | "details": {
35 | "type": "array",
36 | "items": {
37 | "type": "object",
38 | "$ref": "#/definitions/protobufAny"
39 | }
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/generate.go:
--------------------------------------------------------------------------------
1 | package toolkit
2 |
3 | //go:generate ./hack/generate.sh
4 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/lastbackend/toolkit
2 |
3 | go 1.21.5
4 |
5 | toolchain go1.21.6
6 |
7 | require (
8 | github.com/caarlos0/env/v7 v7.1.0
9 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
10 | github.com/envoyproxy/protoc-gen-validate v1.0.3
11 | github.com/fatih/color v1.16.0
12 | github.com/fatih/structs v1.1.0
13 | github.com/google/uuid v1.5.0
14 | github.com/gorilla/mux v1.8.1
15 | github.com/gorilla/websocket v1.5.1
16 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0
17 | github.com/improbable-eng/grpc-web v0.15.0
18 | github.com/jedib0t/go-pretty/v6 v6.5.3
19 | github.com/jhump/protoreflect v1.15.4
20 | github.com/lastbackend/toolkit-plugins/postgres_gorm v0.0.0-20240114174800-797efec18f22
21 | github.com/lastbackend/toolkit-plugins/redis v0.0.0-20240114174800-797efec18f22
22 | github.com/magefile/mage v1.15.0
23 | github.com/pkg/errors v0.9.1
24 | github.com/urfave/cli/v2 v2.27.1
25 | go.uber.org/fx v1.20.1
26 | go.uber.org/zap v1.26.0
27 | golang.org/x/net v0.20.0
28 | golang.org/x/sync v0.6.0
29 | golang.org/x/text v0.14.0
30 | google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1
31 | google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1
32 | google.golang.org/grpc v1.60.1
33 | google.golang.org/protobuf v1.35.1
34 | )
35 |
36 | require (
37 | github.com/bufbuild/protocompile v0.7.1 // indirect
38 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect
39 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
40 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
41 | github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
42 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
43 | github.com/go-pg/pg/v10 v10.12.0 // indirect
44 | github.com/go-pg/zerochecker v0.2.0 // indirect
45 | github.com/golang-migrate/migrate/v4 v4.17.0 // indirect
46 | github.com/golang/protobuf v1.5.3 // indirect
47 | github.com/hashicorp/errwrap v1.1.0 // indirect
48 | github.com/hashicorp/go-multierror v1.1.1 // indirect
49 | github.com/jackc/pgpassfile v1.0.0 // indirect
50 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
51 | github.com/jackc/pgx/v5 v5.4.3 // indirect
52 | github.com/jinzhu/inflection v1.0.0 // indirect
53 | github.com/jinzhu/now v1.1.5 // indirect
54 | github.com/klauspost/compress v1.15.11 // indirect
55 | github.com/lib/pq v1.10.9 // indirect
56 | github.com/mattn/go-colorable v0.1.13 // indirect
57 | github.com/mattn/go-isatty v0.0.20 // indirect
58 | github.com/mattn/go-runewidth v0.0.15 // indirect
59 | github.com/redis/go-redis/v9 v9.4.0 // indirect
60 | github.com/rivo/uniseg v0.2.0 // indirect
61 | github.com/rs/cors v1.7.0 // indirect
62 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
63 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
64 | github.com/vmihailenco/bufpool v0.1.11 // indirect
65 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
66 | github.com/vmihailenco/tagparser v0.1.2 // indirect
67 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
68 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
69 | go.uber.org/atomic v1.11.0 // indirect
70 | go.uber.org/dig v1.17.0 // indirect
71 | go.uber.org/multierr v1.11.0 // indirect
72 | golang.org/x/crypto v0.18.0 // indirect
73 | golang.org/x/sys v0.16.0 // indirect
74 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
75 | gorm.io/driver/postgres v1.5.4 // indirect
76 | gorm.io/gorm v1.25.5 // indirect
77 | mellium.im/sasl v0.3.1 // indirect
78 | nhooyr.io/websocket v1.8.6 // indirect
79 | )
80 |
--------------------------------------------------------------------------------
/hack/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | find . -type d -name example -prune -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete
4 |
5 | mkdir -p proto/google/api
6 |
7 | curl -s -f -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; }
8 |
9 | PROTOS=$(find . -type f -name '*.proto' | grep -v examples | grep -v proto/google/api | grep -v proto/toolkit )
10 |
11 | for PROTO in $PROTOS; do
12 | protoc \
13 | -I. \
14 | -I$GOPATH/src \
15 | -I$(dirname $PROTO) \
16 | --go_out=paths=source_relative:. \
17 | $PROTO
18 | done
19 |
20 | rm -r proto
21 |
22 | echo "Generation is ok"
23 |
--------------------------------------------------------------------------------
/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package toolkit
18 |
19 | import (
20 | "context"
21 | "github.com/lastbackend/toolkit/pkg/client"
22 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
23 | "github.com/lastbackend/toolkit/pkg/runtime/meta"
24 | "github.com/lastbackend/toolkit/pkg/server"
25 | )
26 |
27 | type Service interface {
28 | Meta() *meta.Meta
29 |
30 | Log() logger.Logger
31 | Client() Client
32 | Server() Server
33 |
34 | RegisterConfig(config ...any) error
35 | RegisterPlugin(constructor ...any)
36 | RegisterPackage(constructor ...any)
37 |
38 | Start(ctx context.Context) error
39 | Stop(ctx context.Context, err error)
40 |
41 | RegisterOnStartHook(...func(ctx context.Context) error)
42 | RegisterOnStartSyncHook(...func(ctx context.Context) error)
43 |
44 | RegisterOnStopHook(...func(ctx context.Context) error)
45 | RegisterOnStopSyncHook(...func(ctx context.Context) error)
46 | }
47 |
48 | type Client interface {
49 | GRPC() client.GRPCClient
50 | HTTP() client.HTTPClient
51 | }
52 |
53 | type Server interface {
54 | HTTP() server.HTTPServer
55 | GRPC() server.GRPCServer
56 |
57 | HTTPGet(name string) server.HTTPServer
58 | HTTPNew(name string, options *server.HTTPServerOptions) server.HTTPServer
59 |
60 | GRPCGet(name string) server.GRPCServer
61 | GRPCNew(name string, options *server.GRPCServerOptions) server.GRPCServer
62 | }
63 |
64 | type Config any
65 |
66 | type Package any
67 |
68 | type PackageItem struct {
69 | Index int
70 | Source any
71 | }
72 |
73 | type Plugin any
74 |
--------------------------------------------------------------------------------
/pkg/client/client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver"
6 | "google.golang.org/grpc"
7 | "time"
8 | )
9 |
10 | type GRPCStream grpc.ClientStream
11 | type GRPCClient interface {
12 | GetResolver() resolver.Resolver
13 | SetResolver(resolver resolver.Resolver)
14 | Conn(pool string) (grpc.ClientConnInterface, error)
15 | Call(ctx context.Context, service, method string, req, rsp interface{}, opts ...GRPCCallOption) error
16 | Stream(ctx context.Context, service, method string, body interface{}, opts ...GRPCCallOption) (grpc.ClientStream, error)
17 | }
18 |
19 | type GRPCCallOption func(*GRPCCallOptions)
20 |
21 | type GRPCCallOptions struct {
22 | Backoff GRPCBackoffFunc
23 | Retries time.Duration
24 | RequestTimeout time.Duration
25 | Context context.Context
26 | Headers map[string]string
27 | MaxCallSendMsgSize int
28 | MaxCallRecvMsgSize int
29 | MaxRetryRPCBufferSize int
30 | CallContentSubtype string
31 | }
32 |
33 | func GRPCOptionHeaders(h map[string]string) GRPCCallOption {
34 | return func(o *GRPCCallOptions) {
35 | o.Headers = h
36 | }
37 | }
38 |
39 | func GRPCOptionMaxCallSendMsgSize(bytes int) GRPCCallOption {
40 | return func(o *GRPCCallOptions) {
41 | o.MaxCallSendMsgSize = bytes
42 | }
43 | }
44 |
45 | func GRPCOptionMaxCallRecvMsgSize(bytes int) GRPCCallOption {
46 | return func(o *GRPCCallOptions) {
47 | o.MaxCallRecvMsgSize = bytes
48 | }
49 | }
50 |
51 | func GRPCOptionRequestTimeout(timeout time.Duration) GRPCCallOption {
52 | return func(o *GRPCCallOptions) {
53 | o.RequestTimeout = timeout
54 | }
55 | }
56 |
57 | type GRPCBackoffFunc func(ctx context.Context, req *GRPCRequest, attempts int) (time.Duration, error)
58 | type GRPCRetryFunc func(ctx context.Context, req *GRPCRequest, retryCount int, err error) (bool, error)
59 |
60 | type GRPCRequest struct {
61 | service string
62 | method string
63 | headers map[string]string
64 | body interface{}
65 | }
66 |
67 | func NewGRPCRequest(method, service string, body interface{}, headers map[string]string) *GRPCRequest {
68 | r := new(GRPCRequest)
69 | r.service = method
70 | r.method = service
71 | r.body = body
72 | if headers == nil {
73 | headers = make(map[string]string, 0)
74 | }
75 | r.headers = headers
76 | return r
77 | }
78 |
79 | func (r *GRPCRequest) Service() string {
80 | return r.service
81 | }
82 |
83 | func (r *GRPCRequest) Method() string {
84 | return r.method
85 | }
86 |
87 | func (r *GRPCRequest) Body() interface{} {
88 | return r.body
89 | }
90 |
91 | func (r *GRPCRequest) Headers() map[string]string {
92 | return r.headers
93 | }
94 |
95 | type HTTPClient interface {
96 | Get() error
97 | Post() error
98 | }
99 |
--------------------------------------------------------------------------------
/pkg/client/grpc/codec.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package grpc
18 |
19 | import (
20 | "google.golang.org/protobuf/proto"
21 | )
22 |
23 | type Codec interface {
24 | Marshal(v interface{}) ([]byte, error)
25 | Unmarshal(data []byte, v interface{}) error
26 | Name() string
27 | }
28 |
29 | type protoCodec struct{}
30 |
31 | func (protoCodec) Marshal(v interface{}) ([]byte, error) {
32 | return proto.Marshal(v.(proto.Message))
33 | }
34 |
35 | func (protoCodec) Unmarshal(data []byte, v interface{}) error {
36 | return proto.Unmarshal(data, v.(proto.Message))
37 | }
38 |
39 | func (protoCodec) Name() string {
40 | return "proto"
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/file/file.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package file
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver"
23 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route"
24 | "github.com/lastbackend/toolkit/pkg/runtime"
25 | "github.com/lastbackend/toolkit/pkg/util/addr"
26 | "os"
27 | "path"
28 | )
29 |
30 | const (
31 | prefix string = "resolver"
32 | defaultFileName string = ".lb.resolver"
33 | )
34 |
35 | type Plugin interface {
36 | Print()
37 | }
38 |
39 | type Resolver struct {
40 | prefix string
41 | runtime runtime.Runtime
42 | table *table
43 | opts Config
44 | }
45 |
46 | type Config struct {
47 | Cache bool
48 | File string `env:"FILEPATH" comment:"Filepath to resolver storage file. default(.lb.resolver in home dir)"`
49 | }
50 |
51 | type Options struct {
52 | Name string
53 | }
54 |
55 | func NewResolver(runtime runtime.Runtime) resolver.Resolver {
56 |
57 | var err error
58 |
59 | r := &Resolver{
60 | prefix: prefix,
61 | runtime: runtime,
62 | }
63 |
64 | if err := runtime.Config().Parse(&r.opts, prefix); err != nil {
65 | runtime.Log().Errorf("Can not parse config %s: %s", prefix, err.Error())
66 | }
67 |
68 | if r.opts.File == "" {
69 | dirname, err := os.UserHomeDir()
70 | if err != nil {
71 | runtime.Log().Errorf("Can not obtain default home dir: %s", prefix, err.Error())
72 | return nil
73 | }
74 | r.opts.File = path.Join(dirname, defaultFileName)
75 | }
76 |
77 | if r.table, err = newTable(r.opts.File, runtime.Log()); err != nil {
78 | return nil
79 | }
80 |
81 | return r
82 | }
83 |
84 | func (c *Resolver) OnStart(ctx context.Context) error {
85 | c.runtime.Log().Info("resolver file on-start call")
86 |
87 | ip, err := addr.DetectIP()
88 | if err != nil {
89 | c.runtime.Log().Errorf("Can not detect local ip: %s", err.Error())
90 | return err
91 | }
92 |
93 | srvs := c.runtime.Server().GRPCList()
94 | for name, srv := range srvs {
95 | c.table.Create(route.Route{
96 | Service: name,
97 | Address: fmt.Sprintf("%s:%d", ip, srv.Info().Port),
98 | })
99 | }
100 | return nil
101 | }
102 |
103 | func (c *Resolver) Lookup(service string, opts ...resolver.LookupOption) (route.List, error) {
104 | q := resolver.NewLookup(opts...)
105 | routes, err := c.table.Find(service)
106 | if err != nil {
107 | return nil, err
108 | }
109 | routes = resolver.Filter(routes, q)
110 | if len(routes) == 0 {
111 | return nil, route.ErrRouteNotFound
112 | }
113 | return routes, nil
114 | }
115 |
116 | func (c *Resolver) Table() resolver.Table {
117 | return c.table
118 | }
119 |
120 | func (c *Resolver) Print() {
121 | return
122 | }
123 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/filter.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package resolver
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route"
21 | )
22 |
23 | func isMatch(route route.Route, address string) bool {
24 | match := func(a, b string) bool {
25 | if a == "*" || b == "*" || a == b {
26 | return true
27 | }
28 | return false
29 | }
30 | type matcher struct {
31 | a string
32 | b string
33 | }
34 | values := []matcher{
35 | {address, route.Address},
36 | }
37 | for _, v := range values {
38 | if !match(v.a, v.b) {
39 | return false
40 | }
41 | }
42 | return true
43 | }
44 |
45 | func Filter(routes []route.Route, opts LookupOptions) []route.Route {
46 | address := opts.Address
47 | routeMap := make(map[string][]route.Route, 0)
48 |
49 | for _, r := range routes {
50 | if isMatch(r, address) {
51 | routeKey := r.Service
52 | routeMap[routeKey] = append(routeMap[routeKey], r)
53 | }
54 | }
55 |
56 | var results []route.Route
57 |
58 | for _, r := range routeMap {
59 | results = append(results, r...)
60 | }
61 |
62 | return results
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/local/local.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package local
18 |
19 | import (
20 | "context"
21 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver"
22 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route"
23 | "github.com/lastbackend/toolkit/pkg/runtime"
24 | "strings"
25 | )
26 |
27 | const (
28 | prefix string = "resolver"
29 | separator string = "="
30 | )
31 |
32 | type Resolver struct {
33 | runtime runtime.Runtime
34 | table *table
35 | options *Options
36 | }
37 |
38 | type Options struct {
39 | Cache bool
40 | Endpoints []string `env:"ENDPOINTS" envSeparator:","`
41 | }
42 |
43 | func NewResolver(runtime runtime.Runtime) resolver.Resolver {
44 |
45 | opts := new(Options)
46 | if err := runtime.Config().Parse(opts, prefix); err != nil {
47 | runtime.Log().Errorf("Can not parse config %s: $s", prefix, err.Error())
48 | }
49 |
50 | table := newTable()
51 | r := &Resolver{
52 | table: table,
53 | options: opts,
54 | }
55 |
56 | for _, s := range opts.Endpoints {
57 | if s == "" {
58 | continue
59 | }
60 |
61 | parts := strings.Split(s, separator)
62 | if len(parts) < 2 {
63 | continue
64 | }
65 |
66 | table.Create(route.Route{
67 | Service: parts[0],
68 | Address: parts[1],
69 | })
70 | }
71 |
72 | return r
73 | }
74 |
75 | func (c *Resolver) OnStart(context.Context) error {
76 | return nil
77 | }
78 |
79 | func (c *Resolver) Lookup(service string, opts ...resolver.LookupOption) (route.List, error) {
80 | q := resolver.NewLookup(opts...)
81 | routes, err := c.table.Find(service)
82 | if err != nil {
83 | return nil, err
84 | }
85 | routes = resolver.Filter(routes, q)
86 | if len(routes) == 0 {
87 | return nil, route.ErrRouteNotFound
88 | }
89 | return routes, nil
90 | }
91 |
92 | func (c *Resolver) Table() resolver.Table {
93 | return c.table
94 | }
95 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/local/table.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package local
18 |
19 | import (
20 | rt "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route"
21 | "sync"
22 | "time"
23 | )
24 |
25 | type table struct {
26 | sync.RWMutex
27 | x int
28 | routes map[string]map[string]*resolverRoute
29 | }
30 |
31 | type resolverRoute struct {
32 | route rt.Route
33 | updated time.Time
34 | }
35 |
36 | func newTable() *table {
37 | return &table{
38 | routes: make(map[string]map[string]*resolverRoute, 0),
39 | }
40 | }
41 |
42 | func (t *table) Find(service string) ([]rt.Route, error) {
43 | var routes []rt.Route
44 |
45 | t.RLock()
46 | defer t.RUnlock()
47 |
48 | if len(service) > 0 {
49 | routeMap, ok := t.routes[service]
50 | if !ok {
51 | return nil, rt.ErrRouteNotFound
52 | }
53 | for _, rm := range routeMap {
54 | routes = append(routes, rm.route)
55 | }
56 | return routes, nil
57 | }
58 | for _, serviceRoutes := range t.routes {
59 | for _, sr := range serviceRoutes {
60 | routes = append(routes, sr.route)
61 | }
62 | }
63 | return routes, nil
64 | }
65 |
66 | func (t *table) Create(r rt.Route) error {
67 | service := r.Service
68 | sum := r.Hash()
69 | t.x = 100
70 | t.Lock()
71 | defer t.Unlock()
72 |
73 | if _, ok := t.routes[service]; !ok {
74 | t.routes[service] = make(map[string]*resolverRoute)
75 | }
76 |
77 | if _, ok := t.routes[service][sum]; ok {
78 | return rt.ErrDuplicateRoute
79 | }
80 |
81 | t.routes[service][sum] = &resolverRoute{r, time.Now()}
82 |
83 | return nil
84 | }
85 |
86 | func (t *table) Delete(r rt.Route) error {
87 |
88 | service := r.Service
89 | sum := r.Hash()
90 |
91 | t.Lock()
92 | defer t.Unlock()
93 |
94 | if _, ok := t.routes[service]; !ok {
95 | return rt.ErrRouteNotFound
96 | }
97 |
98 | if _, ok := t.routes[service][sum]; !ok {
99 | return rt.ErrRouteNotFound
100 | }
101 |
102 | delete(t.routes[service], sum)
103 |
104 | if len(t.routes[service]) == 0 {
105 | delete(t.routes, service)
106 | }
107 | return nil
108 | }
109 |
110 | func (t *table) Update(r rt.Route) error {
111 |
112 | service := r.Service
113 | sum := r.Hash()
114 |
115 | t.Lock()
116 | defer t.Unlock()
117 |
118 | if _, ok := t.routes[service]; !ok {
119 | t.routes[service] = make(map[string]*resolverRoute)
120 | }
121 |
122 | if _, ok := t.routes[service][sum]; !ok {
123 | t.routes[service][sum] = &resolverRoute{r, time.Now()}
124 | return nil
125 | }
126 | t.routes[service][sum] = &resolverRoute{r, time.Now()}
127 | return nil
128 | }
129 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package resolver
18 |
19 | func DefaultOptions() Options {
20 | return Options{
21 | Cache: false,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/resolver.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package resolver
18 |
19 | import (
20 | "context"
21 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route"
22 | "github.com/pkg/errors"
23 | )
24 |
25 | type ResolveType string
26 |
27 | func (r ResolveType) String() string {
28 | return string(r)
29 | }
30 |
31 | const (
32 | LocalResolver ResolveType = "local"
33 | ConsulResolver ResolveType = "consul"
34 | )
35 |
36 | var (
37 | DefaultResolver Resolver
38 | )
39 |
40 | var (
41 | ErrResolverNotDetected = errors.New("resolver not detected")
42 | ErrNotAvailable = errors.New("not available")
43 | )
44 |
45 | type Option func(*Options)
46 |
47 | type Options struct {
48 | Cache bool
49 | Endpoint string
50 | }
51 |
52 | type LookupOption func(*LookupOptions)
53 |
54 | type Resolver interface {
55 | OnStart(ctx context.Context) error
56 | Table() Table
57 | Lookup(service string, opts ...LookupOption) (route.List, error)
58 | }
59 |
60 | type Table interface {
61 | Create(route.Route) error
62 | Delete(route.Route) error
63 | Update(route.Route) error
64 | Find(service string) ([]route.Route, error)
65 | }
66 |
67 | type LookupOptions struct {
68 | Address string
69 | }
70 |
71 | func NewLookup(opts ...LookupOption) LookupOptions {
72 | qopts := LookupOptions{
73 | Address: "*",
74 | }
75 | for _, o := range opts {
76 | o(&qopts)
77 | }
78 | return qopts
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/client/grpc/resolver/route/route.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package route
18 |
19 | import (
20 | "crypto/sha256"
21 | "encoding/hex"
22 | "github.com/pkg/errors"
23 | )
24 |
25 | var (
26 | ErrRouteNotFound = errors.New("route not found")
27 | ErrDuplicateRoute = errors.New("duplicate route")
28 | )
29 |
30 | type Route struct {
31 | Service string `json:"service"`
32 | Address string `json:"address"`
33 | }
34 |
35 | func (r *Route) Hash() string {
36 | hash := sha256.Sum256([]byte(r.Service + r.Address))
37 | return hex.EncodeToString(hash[:])
38 | }
39 |
40 | type List []Route
41 |
42 | func (r *List) Addresses() []string {
43 | list := make([]string, 0)
44 | for _, item := range *r {
45 | list = append(list, item.Address)
46 | }
47 | return list
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/client/grpc/selector/random.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package selector
18 |
19 | import (
20 | "math/rand"
21 | )
22 |
23 | func newRandomSelector() Selector {
24 | return new(random)
25 | }
26 |
27 | type random struct{}
28 |
29 | func (s *random) Select(routes []string) (Next, error) {
30 | if len(routes) == 0 {
31 | return nil, ErrNotAvailable
32 | }
33 | return func() string {
34 | if len(routes) == 1 {
35 | return routes[0]
36 | }
37 | return routes[rand.Intn(len(routes))]
38 | }, nil
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/client/grpc/selector/roundrobin.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package selector
18 |
19 | import (
20 | "math/rand"
21 | )
22 |
23 | func newRRSelector() Selector {
24 | return new(roundRobin)
25 | }
26 |
27 | type roundRobin struct{}
28 |
29 | func (s *roundRobin) Select(routes []string) (Next, error) {
30 | if len(routes) == 0 {
31 | return nil, ErrNotAvailable
32 | }
33 | i := rand.Intn(len(routes))
34 | return func() string {
35 | route := routes[i%len(routes)]
36 | i++
37 | return route
38 | }, nil
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/client/grpc/selector/selector.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package selector
18 |
19 | import (
20 | "github.com/pkg/errors"
21 | )
22 |
23 | type Type int
24 |
25 | const (
26 | Random Type = iota
27 | RoundRobin
28 | )
29 |
30 | var (
31 | ErrSelectorNotDetected = errors.New("selector not detected")
32 | ErrNotAvailable = errors.New("not available")
33 | )
34 |
35 | type Selector interface {
36 | Select([]string) (Next, error)
37 | }
38 |
39 | type Next func() string
40 |
41 | func New(t Type) (selector Selector, err error) {
42 | switch t {
43 | case Random:
44 | selector = newRandomSelector()
45 | case RoundRobin:
46 | selector = newRRSelector()
47 | default:
48 | err = ErrSelectorNotDetected
49 | }
50 | return selector, err
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/client/grpc/stream.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package grpc
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/pkg/client"
21 | "google.golang.org/grpc"
22 |
23 | "context"
24 | "sync"
25 | )
26 |
27 | type stream struct {
28 | sync.RWMutex
29 | context context.Context
30 |
31 | grpc.ClientStream
32 |
33 | request *client.GRPCRequest
34 | conn *poolConn
35 | close func(err error)
36 | }
37 |
38 | func (s *stream) Context() context.Context {
39 | return s.context
40 | }
41 |
42 | func (s *stream) Headers() map[string]string {
43 | return s.request.Headers()
44 | }
45 |
46 | func (s *stream) SendMsg(msg interface{}) error {
47 | return s.ClientStream.SendMsg(msg)
48 | }
49 |
50 | func (s *stream) RecvMsg(msg interface{}) (err error) {
51 | return s.ClientStream.RecvMsg(msg)
52 | }
53 |
54 | func (s *stream) CloseSend() error {
55 | return s.ClientStream.CloseSend()
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/client/http/client.go:
--------------------------------------------------------------------------------
1 | package http
2 |
--------------------------------------------------------------------------------
/pkg/context/context.go:
--------------------------------------------------------------------------------
1 | package context
2 |
--------------------------------------------------------------------------------
/pkg/context/metadata/metadata.go:
--------------------------------------------------------------------------------
1 | package metadata
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | const emptyString = ""
8 |
9 | type MD map[string]string
10 | type mdKey struct{}
11 |
12 | func (m MD) Get(key string) (string, bool) {
13 | val, ok := m[key]
14 | if ok {
15 | return val, ok
16 | }
17 | val, ok = m[key]
18 | return val, ok
19 | }
20 |
21 | func (m MD) Set(key, val string) {
22 | m[key] = val
23 | }
24 |
25 | func (m MD) Del(key string) {
26 | delete(m, key)
27 | }
28 |
29 | func NewContext(ctx context.Context, md MD) context.Context {
30 | return context.WithValue(ctx, mdKey{}, md)
31 | }
32 |
33 | func DeleteContextValue(ctx context.Context, k string) context.Context {
34 | return SetContextValue(ctx, k, emptyString)
35 | }
36 |
37 | func SetContextValue(ctx context.Context, k, v string) context.Context {
38 | md, ok := LoadFromContext(ctx)
39 | if !ok {
40 | md = make(MD, 0)
41 | }
42 | if len(v) != 0 {
43 | md[k] = v
44 | } else {
45 | delete(md, k)
46 | }
47 | return context.WithValue(ctx, mdKey{}, md)
48 | }
49 |
50 | func GetContextValue(ctx context.Context, key string) (string, bool) {
51 | md, ok := LoadFromContext(ctx)
52 | if !ok {
53 | return emptyString, ok
54 | }
55 | val, ok := md[key]
56 | return val, ok
57 | }
58 |
59 | func LoadFromContext(ctx context.Context) (MD, bool) {
60 | md, ok := ctx.Value(mdKey{}).(MD)
61 | if !ok {
62 | return nil, ok
63 | }
64 | newMD := make(MD, len(md))
65 | for k, v := range md {
66 | newMD[k] = v
67 | }
68 | return newMD, ok
69 | }
70 |
71 | func MergeContext(ctx context.Context, metadata MD, overwrite bool) context.Context {
72 | if ctx == nil {
73 | ctx = context.Background()
74 | }
75 | md, ok := ctx.Value(mdKey{}).(MD)
76 | if !ok {
77 | return ctx
78 | }
79 | cp := make(MD, len(md))
80 | for k, v := range md {
81 | cp[k] = v
82 | }
83 | for k, v := range metadata {
84 | if _, ok := cp[k]; ok && !overwrite {
85 | continue
86 | }
87 | if len(v) != 0 {
88 | cp[k] = v
89 | } else {
90 | delete(cp, k)
91 | }
92 | }
93 | return context.WithValue(ctx, mdKey{}, cp)
94 | }
95 |
--------------------------------------------------------------------------------
/pkg/runtime/controller/client.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "context"
5 | "github.com/lastbackend/toolkit/pkg/client"
6 | "github.com/lastbackend/toolkit/pkg/client/grpc"
7 | "github.com/lastbackend/toolkit/pkg/runtime"
8 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
9 | )
10 |
11 | type clientManager struct {
12 | log logger.Logger
13 | grpc client.GRPCClient
14 | http client.HTTPClient
15 | }
16 |
17 | func (c *clientManager) GRPC() client.GRPCClient {
18 | return c.grpc
19 | }
20 |
21 | func (c *clientManager) HTTP() client.HTTPClient {
22 | return c.http
23 | }
24 |
25 | func newClientController(ctx context.Context, runtime runtime.Runtime) runtime.Client {
26 | cl := new(clientManager)
27 |
28 | cl.log = runtime.Log()
29 | cl.grpc = grpc.NewClient(ctx, runtime)
30 |
31 | return cl
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/runtime/controller/help.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | const templateHelpText string = `
9 | NAME:
10 | {{ .Name }} - {{ .Desc }}
11 |
12 | USAGE:
13 | {{ .Name }} [global options] command [command options] [arguments...]
14 |
15 | COMMANDS:
16 | help, h Shows a list of commands or help for one command
17 |
18 | GLOBAL OPTIONS:
19 | --help, -h show help
20 | --all show all available variables. by default show only required
21 | --yaml show environment variables as a yaml, table by default
22 | --without-comments disable printing envs comments
23 |
24 | ENVIRONMENT VARIABLES
25 |
26 | {{ .Envs }}
27 |
28 | `
29 |
30 | func (c *controller) help() bool {
31 |
32 | var (
33 | help, all, yaml, nocomments bool
34 | )
35 |
36 | for _, arg := range os.Args {
37 | switch arg {
38 | case "-h":
39 | help = true
40 | case "--help":
41 | help = true
42 | case "h":
43 | help = true
44 | case "help":
45 | help = true
46 | case "--all":
47 | all = true
48 | case "--yaml":
49 | yaml = true
50 | case "--without-comments":
51 | nocomments = true
52 | }
53 |
54 | }
55 |
56 | if !help {
57 | return false
58 | }
59 |
60 | type data struct {
61 | Name string
62 | Desc string
63 | Envs string
64 | }
65 |
66 | tpl, err := template.New("help").Parse(templateHelpText)
67 | if err != nil {
68 | panic(err)
69 | }
70 |
71 | var envs = c.Config().PrintTable(all, nocomments)
72 |
73 | if yaml {
74 | envs = c.Config().PrintYaml(all, nocomments)
75 | }
76 |
77 | if err := tpl.Execute(os.Stdout, data{
78 | Name: c.meta.GetName(),
79 | Desc: c.meta.GetDescription(),
80 | Envs: envs,
81 | }); err != nil {
82 | panic(err)
83 | }
84 |
85 | return true
86 | }
87 |
--------------------------------------------------------------------------------
/pkg/runtime/controller/service.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "context"
5 | "github.com/lastbackend/toolkit"
6 | "github.com/lastbackend/toolkit/pkg/runtime"
7 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
8 | "github.com/lastbackend/toolkit/pkg/runtime/meta"
9 | )
10 |
11 | type service struct {
12 | toolkit.Service
13 | runtime runtime.Runtime
14 | start func(ctx context.Context, invoke ...interface{}) error
15 | }
16 |
17 | func (s *service) Meta() *meta.Meta {
18 | return s.runtime.Meta()
19 | }
20 |
21 | func (s *service) Log() logger.Logger {
22 | return s.runtime.Log()
23 | }
24 |
25 | func (s *service) Client() toolkit.Client {
26 | return s.runtime.Client()
27 | }
28 |
29 | func (s *service) Server() toolkit.Server {
30 | return s.runtime.Server()
31 | }
32 |
33 | func (s *service) RegisterConfig(items ...any) error {
34 | return s.runtime.Config().Provide(items...)
35 | }
36 |
37 | func (s *service) RegisterPackage(items ...any) {
38 | s.runtime.Package().Provide(items...)
39 | }
40 |
41 | func (s *service) RegisterPlugin(items ...any) {
42 | s.runtime.Plugin().Provide(items...)
43 | }
44 |
45 | func (s *service) Start(ctx context.Context) error {
46 | return s.runtime.Start(ctx)
47 | }
48 |
49 | func (s *service) Stop(ctx context.Context, err error) {
50 | s.runtime.Stop(ctx, err)
51 | }
52 |
53 | func (s *service) RegisterOnStartHook(fn ...func(ctx context.Context) error) {
54 | s.runtime.RegisterOnStartHook(fn...)
55 | }
56 |
57 | func (s *service) RegisterOnStopHook(fn ...func(ctx context.Context) error) {
58 | s.runtime.RegisterOnStopHook(fn...)
59 | }
60 |
61 | func (s *service) RegisterOnStartSyncHook(fn ...func(ctx context.Context) error) {
62 | s.runtime.RegisterOnStartSyncHook(fn...)
63 | }
64 |
65 | func (s *service) RegisterOnStopSyncHook(fn ...func(ctx context.Context) error) {
66 | s.runtime.RegisterOnStopSyncHook(fn...)
67 | }
68 |
--------------------------------------------------------------------------------
/pkg/runtime/controller/tools.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/lastbackend/toolkit/pkg/runtime"
7 | "github.com/lastbackend/toolkit/pkg/tools/metrics"
8 | "github.com/lastbackend/toolkit/pkg/tools/probes"
9 | "github.com/lastbackend/toolkit/pkg/tools/probes/server"
10 | "github.com/lastbackend/toolkit/pkg/tools/traces"
11 | )
12 |
13 | type Tools struct {
14 | runtime.Tools
15 |
16 | runtime runtime.Runtime
17 | metrics metrics.Metrics
18 | probes probes.Probes
19 | traces traces.Traces
20 | }
21 |
22 | func (t *Tools) Probes() probes.Probes {
23 | return t.probes
24 | }
25 |
26 | func (t *Tools) Traces() traces.Traces {
27 | return t.traces
28 | }
29 |
30 | func (t *Tools) Metrics() metrics.Metrics {
31 | return t.metrics
32 | }
33 |
34 | func (t *Tools) OnStart(ctx context.Context) error {
35 | return t.probes.Start(ctx)
36 | }
37 |
38 | func newToolsRegistration(runtime runtime.Runtime) (runtime.Tools, error) {
39 | var (
40 | tools = new(Tools)
41 | err error
42 | )
43 | tools.runtime = runtime
44 |
45 | if tools.probes, err = server.NewProbesServer(runtime); err != nil {
46 | return nil, err
47 | }
48 |
49 | return tools, nil
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/runtime/controller/util.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | var (
8 | _typeOfError reflect.Type = reflect.TypeOf((*error)(nil)).Elem()
9 | _nilError = reflect.Zero(_typeOfError)
10 | )
11 |
12 | func build(target interface{}, kind interface{}) interface{} {
13 |
14 | paramTypes, remap := parameters(target)
15 | resultTypes, _ := currentResultTypes(target, kind)
16 |
17 | origFn := reflect.ValueOf(target)
18 | newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
19 | newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
20 | args = remap(args)
21 | values := origFn.Call(args)
22 |
23 | for _, v := range values {
24 | values = append(values, v.Convert(reflect.TypeOf(kind).Elem()))
25 | }
26 |
27 | return values
28 | })
29 | return newFn.Interface()
30 | }
31 |
32 | func buildPackage(target interface{}, kind interface{}) interface{} {
33 |
34 | paramTypes, remap := parameters(target)
35 | resultTypes, _ := currentResultTypes(target, kind)
36 |
37 | origFn := reflect.ValueOf(target)
38 | newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
39 | newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
40 | args = remap(args)
41 | values := origFn.Call(args)
42 |
43 | for _, v := range values {
44 | rv := reflect.ValueOf(kind).Elem()
45 | rv.FieldByName("Source").Set(v)
46 | values = append(values, rv)
47 | }
48 |
49 | return values
50 | })
51 | return newFn.Interface()
52 | }
53 |
54 | func currentResultTypes(target interface{}, kind interface{}) (resultTypes []reflect.Type, hasError bool) {
55 | ft := reflect.TypeOf(target)
56 | numOut := ft.NumOut()
57 | resultTypes = make([]reflect.Type, numOut)
58 |
59 | for i := 0; i < numOut; i++ {
60 | resultTypes[i] = ft.Out(i)
61 | if resultTypes[i] == _typeOfError && i == numOut-1 {
62 | hasError = true
63 | }
64 | }
65 | for i := 0; i < numOut; i++ {
66 | resultTypes = append(resultTypes, reflect.TypeOf(kind).Elem())
67 | }
68 | return resultTypes, hasError
69 | }
70 |
71 | func parameters(target interface{}) (
72 | types []reflect.Type,
73 | remap func([]reflect.Value) []reflect.Value,
74 | ) {
75 | ft := reflect.TypeOf(target)
76 | types = make([]reflect.Type, ft.NumIn())
77 | for i := 0; i < ft.NumIn(); i++ {
78 | types[i] = ft.In(i)
79 | }
80 |
81 | return types, func(args []reflect.Value) []reflect.Value {
82 | return args
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/runtime/interface.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | import (
4 | "context"
5 | "github.com/caarlos0/env/v7"
6 | "github.com/lastbackend/toolkit"
7 | "github.com/lastbackend/toolkit/pkg/client"
8 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
9 | "github.com/lastbackend/toolkit/pkg/runtime/meta"
10 | "github.com/lastbackend/toolkit/pkg/server"
11 | "github.com/lastbackend/toolkit/pkg/tools/metrics"
12 | "github.com/lastbackend/toolkit/pkg/tools/probes"
13 | "github.com/lastbackend/toolkit/pkg/tools/traces"
14 | )
15 |
16 | type Runtime interface {
17 | Meta() *meta.Meta
18 | Log() logger.Logger
19 |
20 | Client() Client
21 | Package() Package
22 | Server() Server
23 | Config() Config
24 | Plugin() Plugin
25 |
26 | Tools() Tools
27 |
28 | Service() toolkit.Service
29 |
30 | Provide(constructor interface{})
31 | Invoke(constructor interface{})
32 |
33 | Start(ctx context.Context) error
34 | Stop(ctx context.Context, err error)
35 |
36 | RegisterOnStartHook(fn ...func(ctx context.Context) error)
37 | RegisterOnStartSyncHook(fn ...func(ctx context.Context) error)
38 |
39 | RegisterOnStopHook(fn ...func(ctx context.Context) error)
40 | RegisterOnStopSyncHook(fn ...func(ctx context.Context) error)
41 | }
42 |
43 | type Client interface {
44 | GRPC() client.GRPCClient
45 | HTTP() client.HTTPClient
46 | }
47 |
48 | type HTTPClient interface {
49 | Get() error
50 | Post() error
51 | }
52 |
53 | type Server interface {
54 | toolkit.Server
55 |
56 | Start(ctx context.Context) error
57 | Stop(ctx context.Context) error
58 |
59 | HTTPList() map[string]server.HTTPServer
60 | GRPCList() map[string]server.GRPCServer
61 |
62 | Provides() []interface{}
63 | Constructors() []interface{}
64 | }
65 |
66 | type Config interface {
67 | Provide(...any) error
68 |
69 | SetMeta(meta *meta.Meta)
70 | Parse(v interface{}, prefix string, opts ...env.Options) error
71 | Print(v interface{}, prefix string)
72 | PrintTable(all, nocomments bool) string
73 | PrintYaml(all, nocomments bool) string
74 |
75 | Configs() []any
76 | }
77 |
78 | type Plugin interface {
79 | Provide(constructor ...interface{})
80 |
81 | Constructors() []interface{}
82 | Register(plugins []toolkit.Plugin)
83 |
84 | PreStart(ctx context.Context) error
85 | OnStart(ctx context.Context) error
86 | OnStop(ctx context.Context) error
87 | }
88 |
89 | type Package interface {
90 | Provide(constructor ...interface{})
91 |
92 | Constructors() []interface{}
93 | Register(packages []toolkit.PackageItem)
94 |
95 | PreStart(ctx context.Context) error
96 | OnStart(ctx context.Context) error
97 | OnStop(ctx context.Context) error
98 | }
99 |
100 | type Tools interface {
101 | OnStart(ctx context.Context) error
102 |
103 | Metrics() metrics.Metrics
104 | Probes() probes.Probes
105 | Traces() traces.Traces
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/runtime/logger/empty/empty.go:
--------------------------------------------------------------------------------
1 | package empty
2 |
3 | import (
4 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
5 | "go.uber.org/fx/fxevent"
6 | )
7 |
8 | type emptyLogger struct {
9 | logger.Logger
10 | }
11 |
12 | func (l *emptyLogger) WithFields(_ logger.Fields) logger.Logger {
13 | return l
14 | }
15 |
16 | func (l *emptyLogger) Init(_ logger.Options) logger.Logger {
17 | return l
18 | }
19 |
20 | func (l *emptyLogger) Options() logger.Options {
21 | return logger.Options{}
22 | }
23 |
24 | func (l *emptyLogger) Debug(_ ...interface{}) {}
25 | func (l *emptyLogger) Debugf(_ string, _ ...interface{}) {}
26 | func (l *emptyLogger) Info(_ ...interface{}) {}
27 | func (l *emptyLogger) Infof(_ string, _ ...interface{}) {}
28 | func (l *emptyLogger) Warn(_ ...interface{}) {}
29 | func (l *emptyLogger) Warnf(_ string, _ ...interface{}) {}
30 | func (l *emptyLogger) Error(_ ...interface{}) {}
31 | func (l *emptyLogger) Errorf(_ string, _ ...interface{}) {}
32 | func (l *emptyLogger) Panic(_ ...interface{}) {}
33 | func (l *emptyLogger) Panicf(_ string, _ ...interface{}) {}
34 | func (l *emptyLogger) Fatal(_ ...interface{}) {}
35 | func (l *emptyLogger) Fatalf(_ string, _ ...interface{}) {}
36 | func (l *emptyLogger) V(logger.Level) logger.Logger {
37 | return l
38 | }
39 |
40 | func (l *emptyLogger) Inject(_ func(level logger.Level)) {
41 |
42 | }
43 |
44 | func (l *emptyLogger) Fx() fxevent.Logger {
45 | return fxevent.NopLogger
46 | }
47 |
48 | func NewLogger() logger.Logger {
49 | return new(emptyLogger)
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/runtime/logger/logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package logger
18 |
19 | import (
20 | "go.uber.org/fx/fxevent"
21 | "io"
22 | )
23 |
24 | type Fields map[string]interface{}
25 |
26 | type Level int8
27 |
28 | const (
29 | FatalLevel Level = iota
30 | PanicLevel
31 | ErrorLevel
32 | WarnLevel
33 | InfoLevel
34 | DebugLevel
35 | )
36 |
37 | type Options struct {
38 | Verbose Level `env:"VERBOSE"`
39 | JSONFormat bool `env:"JSON_FORMAT"`
40 | CallerSkipCount int
41 | Fields Fields
42 | Out io.Writer
43 | Tags map[string]string
44 | }
45 |
46 | type Logger interface {
47 | Debug(args ...interface{})
48 | Debugf(format string, args ...interface{})
49 | Info(args ...interface{})
50 | Infof(format string, args ...interface{})
51 | Warn(args ...interface{})
52 | Warnf(format string, args ...interface{})
53 | Error(args ...interface{})
54 | Errorf(format string, args ...interface{})
55 | Panic(args ...interface{})
56 | Panicf(format string, args ...interface{})
57 | Fatal(args ...interface{})
58 | Fatalf(format string, args ...interface{})
59 | V(Level) Logger
60 | Inject(fn func(level Level))
61 | Fx() fxevent.Logger
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/runtime/meta/meta.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 | )
7 |
8 | type Meta struct {
9 | name string
10 | version string
11 | description string
12 | prefix string
13 | }
14 |
15 | func (m *Meta) SetName(name string) *Meta {
16 | m.name = name
17 | return m
18 | }
19 |
20 | func (m *Meta) GetName() string {
21 | return m.name
22 | }
23 |
24 | func (m *Meta) GetSlug() string {
25 | slug := regexp.MustCompile(`[^_a-zA-Z0-9 ]+`).ReplaceAllString(m.name, "_")
26 | return slug
27 | }
28 |
29 | func (m *Meta) SetVersion(version string) *Meta {
30 | m.version = version
31 | return m
32 | }
33 |
34 | func (m *Meta) GetVersion() string {
35 | return m.version
36 | }
37 |
38 | func (m *Meta) SetDescription(description string) *Meta {
39 | m.description = description
40 | return m
41 | }
42 |
43 | func (m *Meta) GetDescription() string {
44 | return m.description
45 | }
46 |
47 | func (m *Meta) SetEnvPrefix(prefix string) *Meta {
48 | m.prefix = strings.ToUpper(regexp.MustCompile(`[^_a-zA-Z0-9 ]+`).ReplaceAllString(prefix, ""))
49 | return m
50 | }
51 |
52 | func (m *Meta) GetEnvPrefix() string {
53 | return m.prefix
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/runtime/options.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | const (
4 | MetaOptionEnvPrefix = "envPrefix"
5 | MetaOptionVersion = "version"
6 | MetaOptionDescription = "description"
7 | )
8 |
9 | type Option interface {
10 | Name() string
11 | Value() string
12 | }
13 |
14 | type Options []Option
15 |
16 | type option struct {
17 | name string
18 | value string
19 | }
20 |
21 | func (o *option) Name() string {
22 | return o.name
23 | }
24 |
25 | func (o *option) Value() string {
26 | return o.value
27 | }
28 |
29 | func WithEnvPrefix(prefix string) Option {
30 | return &option{name: MetaOptionEnvPrefix, value: prefix}
31 | }
32 |
33 | func WithVersion(version string) Option {
34 | return &option{name: MetaOptionVersion, value: version}
35 | }
36 |
37 | func WithDescription(description string) Option {
38 | return &option{name: MetaOptionDescription, value: description}
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/server/grpc/interceptor.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package grpc
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/pkg/runtime/logger"
21 | "github.com/lastbackend/toolkit/pkg/server"
22 | )
23 |
24 | type Interceptors struct {
25 | log logger.Logger
26 | constructors []interface{}
27 | items map[server.KindInterceptor]server.GRPCInterceptor
28 | }
29 |
30 | func (i *Interceptors) AddConstructor(h interface{}) {
31 | i.constructors = append(i.constructors, h)
32 | }
33 |
34 | func (i *Interceptors) Add(h server.GRPCInterceptor) {
35 | i.items[h.Kind()] = h
36 | }
37 |
38 | func newInterceptors(log logger.Logger) *Interceptors {
39 | interceptors := Interceptors{
40 | log: log,
41 | constructors: make([]interface{}, 0),
42 | items: make(map[server.KindInterceptor]server.GRPCInterceptor),
43 | }
44 |
45 | return &interceptors
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/server/grpc/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package grpc
18 |
19 | import (
20 | "github.com/google/uuid"
21 | "github.com/improbable-eng/grpc-web/go/grpcweb"
22 | "google.golang.org/grpc"
23 |
24 | "crypto/tls"
25 | "time"
26 | )
27 |
28 | const (
29 | defaultPort = 9000
30 | defaultName = "go.toolkit.server"
31 | defaultRegisterInterval = time.Second * 30
32 | defaultRegisterTTL = time.Second * 90
33 | )
34 |
35 | type Config struct {
36 | ID string
37 | Name string `env:"GRPC_SERVER_NAME" envDefault:"" comment:"Set GRPC server name"`
38 |
39 | Host string `env:"GRPC_SERVER_LISTEN" envDefault:"0.0.0.0" comment:"Set GRPC server listen host"`
40 | Port int `env:"GRPC_SERVER_PORT" envDefault:"9000" comment:"Set GRPC server listen port"`
41 |
42 | MaxConnSize int `env:"GRPC_SERVER_MAX_CONNECTION_SIZE" comment:"Sets the max simultaneous connections for server (default unlimited)"`
43 | MaxRecvMsgSize int `env:"GRPC_SERVER_MAX_RECEIVE_MESSAGE_SIZE" envDefault:"16777216" comment:"Sets the max message size in bytes the server can receive (default 16 MB)"`
44 | MaxSendMsgSize int `env:"GRPC_SERVER_MAX_SEND_MESSAGE_SIZE" envDefault:"16777216" comment:"Sets the max message size in bytes the server can send (default 16 MB)"`
45 |
46 | IsDisable bool `env:"GRPC_SERVER_DISABLED" envDefault:"false" comment:"GRPC server disable (default: false)"`
47 |
48 | GrpcOptions []grpc.ServerOption `env:"GRPC_SERVER_OPTIONS" envSeparator:"," comment:"Set GRPC server additional options (key=value,key2=value2)"`
49 | TLSConfig *tls.Config
50 |
51 | GRPCWebHost string `env:"GRPC_WEB_SERVER_LISTEN" envDefault:"0.0.0.0" comment:"Set GRPC WEB server listen host"`
52 | GRPCWebPort int `env:"GRPC_WEB_SERVER_PORT" comment:"Set GRPC WEB server listen host"`
53 |
54 | GrpcWebOptions []grpcweb.Option
55 |
56 | RegisterInterval time.Duration
57 | RegisterTTL time.Duration
58 | }
59 |
60 | func defaultOptions() Config {
61 | return Config{
62 | ID: uuid.New().String(),
63 | Name: defaultName,
64 | Port: defaultPort,
65 | RegisterInterval: defaultRegisterInterval,
66 | RegisterTTL: defaultRegisterTTL,
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/server/http/client.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "errors"
21 | "net/http"
22 | "strconv"
23 | )
24 |
25 | func HandleGRPCResponse(w http.ResponseWriter, r *http.Request, headers map[string]string) (bool, error) {
26 |
27 | var statusCode, ok = headers["x-http-status-code"]
28 | if !ok {
29 | w.WriteHeader(http.StatusOK)
30 | return true, nil
31 | }
32 |
33 | i, err := strconv.Atoi(statusCode)
34 | if err != nil {
35 | w.WriteHeader(http.StatusInternalServerError)
36 | return false, err
37 | }
38 |
39 | if i < 200 || i > 505 {
40 | w.WriteHeader(http.StatusInternalServerError)
41 | return false, errors.New("invalid status code")
42 | }
43 |
44 | if i == 302 {
45 | if redirectUrl, ok := headers["x-http-redirect-uri"]; ok {
46 | http.Redirect(w, r, redirectUrl, http.StatusFound)
47 | return false, nil
48 | }
49 | }
50 |
51 | w.WriteHeader(i)
52 | return true, nil
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/server/http/converter.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/pkg/util/converter"
21 | )
22 |
23 | var (
24 | NewReader = converter.NewReader
25 | ParseRequestQueryParametersToProto = converter.ParseRequestQueryParametersToProto
26 | ParseRequestUrlParametersToProto = converter.ParseRequestUrlParametersToProto
27 | SetRawBodyToProto = converter.SetRawBodyToProto
28 | PrepareHeaderFromRequest = converter.PrepareHeaderFromRequest
29 | )
30 |
--------------------------------------------------------------------------------
/pkg/server/http/cors.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "github.com/lastbackend/toolkit/pkg/server"
5 | "net/http"
6 | )
7 |
8 | const corsMiddlewareKind server.KindMiddleware = "corsMiddleware"
9 |
10 | type corsMiddleware struct {
11 | server.DefaultHttpServerMiddleware
12 | handler http.HandlerFunc
13 | }
14 |
15 | func (corsMiddleware) Kind() server.KindMiddleware {
16 | return corsMiddlewareKind
17 | }
18 |
19 | func (corsMiddleware) Order() int {
20 | return 999
21 | }
22 |
23 | func (s *corsMiddleware) Apply(h http.HandlerFunc) http.HandlerFunc {
24 | return func(w http.ResponseWriter, r *http.Request) {
25 | s.handler(w, r)
26 | h.ServeHTTP(w, r)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/server/http/handler.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "context"
21 | "encoding/json"
22 | "net/http"
23 | "strings"
24 |
25 | "github.com/lastbackend/toolkit/pkg/context/metadata"
26 | "github.com/lastbackend/toolkit/pkg/server"
27 | grpc_md "google.golang.org/grpc/metadata"
28 | )
29 |
30 | type HandleOption func(*HandleOptions)
31 |
32 | type HandleOptions struct {
33 | Middlewares server.HttpServerMiddleware
34 | }
35 |
36 | type Handler func(w http.ResponseWriter, r *http.Request) error
37 |
38 | func corsHandlerFunc(w http.ResponseWriter, r *http.Request) {
39 | w.Header().Add("Access-Control-Allow-Origin", r.Header.Get("Origin"))
40 | w.Header().Add("Access-Control-Allow-Credentials", "true")
41 | w.Header().Add("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,DELETE")
42 | w.Header().Add("Access-Control-Expose-Headers", "Content-Disposition")
43 | w.Header().Add("Access-Control-Allow-Headers", "Authorization,Content-Type,Origin,X-Tools-Name,X-Requested-With,Content-Name,Accept,Accept-Range,Range")
44 | w.Header().Add("Content-Type", "application/json")
45 | }
46 |
47 | func (s *httpServer) methodNotAllowedHandler() http.Handler {
48 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
49 | if s.opts.EnableCORS {
50 | corsHandlerFunc(w, r)
51 | }
52 | w.WriteHeader(http.StatusMethodNotAllowed)
53 | w.Write(makeResponse(http.StatusMethodNotAllowed, "Method Not Allowed"))
54 | })
55 | }
56 |
57 | func (s *httpServer) methodNotFoundHandler() http.Handler {
58 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
59 | if s.opts.EnableCORS {
60 | corsHandlerFunc(w, r)
61 | }
62 | w.WriteHeader(http.StatusNotFound)
63 | w.Write(makeResponse(http.StatusNotFound, "Not Found"))
64 | })
65 | }
66 |
67 | type response struct {
68 | Code int `json:"code"`
69 | Status string `json:"status"`
70 | Message string `json:"message"`
71 | }
72 |
73 | func makeResponse(code int, message string) []byte {
74 | r, _ := json.Marshal(response{
75 | Code: code,
76 | Status: http.StatusText(code),
77 | Message: message,
78 | })
79 | return r
80 | }
81 |
82 | func NewIncomingContext(ctx context.Context, headers map[string]string) context.Context {
83 | if headers == nil {
84 | headers = make(map[string]string)
85 | }
86 | if md, ok := metadata.LoadFromContext(ctx); ok {
87 | for k, v := range md {
88 | headers[strings.ToLower(k)] = v
89 | }
90 | return grpc_md.NewIncomingContext(ctx, grpc_md.New(md))
91 | }
92 | return grpc_md.NewIncomingContext(ctx, grpc_md.New(headers))
93 | }
94 |
--------------------------------------------------------------------------------
/pkg/server/http/marshaler.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler"
21 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler/formpb"
22 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler/jsonpb"
23 | "google.golang.org/protobuf/encoding/protojson"
24 | )
25 |
26 | const (
27 | ContentTypeForm = "application/x-www-form-urlencoded"
28 | ContentTypeJSON = "application/json"
29 | )
30 |
31 | var (
32 | DefaultMarshaler = &jsonpb.JSONPb{
33 | MarshalOptions: defaultMarshalOptions,
34 | UnmarshalOptions: defaultUnmarshalOptions,
35 | }
36 | )
37 |
38 | var (
39 | defaultMarshalOptions = protojson.MarshalOptions{
40 | EmitUnpopulated: true, // Include fields with default values in the JSON output
41 | UseProtoNames: true, // Use original proto field names (e.g., 'start_date') instead of CamelCase (e.g., 'StartDate')
42 | }
43 | defaultUnmarshalOptions = protojson.UnmarshalOptions{
44 | DiscardUnknown: true, // Ignores unknown fields during deserialization
45 | }
46 | )
47 |
48 | func GetMarshalerMap() map[string]marshaler.Marshaler {
49 | return map[string]marshaler.Marshaler{
50 | ContentTypeJSON: &jsonpb.JSONPb{
51 | MarshalOptions: defaultMarshalOptions,
52 | UnmarshalOptions: defaultUnmarshalOptions,
53 | },
54 | ContentTypeForm: &formpb.FORMPb{
55 | MarshalOptions: defaultMarshalOptions,
56 | UnmarshalOptions: defaultUnmarshalOptions,
57 | },
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/server/http/marshaler/marshaler.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package marshaler
18 |
19 | import (
20 | "io"
21 | )
22 |
23 | type Marshaler interface {
24 | Marshal(v interface{}) ([]byte, error)
25 | Unmarshal(data []byte, v interface{}) error
26 | NewDecoder(r io.Reader) Decoder
27 | NewEncoder(w io.Writer) Encoder
28 | ContentType() string
29 | }
30 |
31 | type Decoder interface {
32 | Decode(v interface{}) error
33 | }
34 |
35 | type Encoder interface {
36 | Encode(v interface{}) error
37 | }
38 |
39 | type DecoderFunc func(v interface{}) error
40 |
41 | func (f DecoderFunc) Decode(v interface{}) error { return f(v) }
42 |
43 | type EncoderFunc func(v interface{}) error
44 |
45 | func (f EncoderFunc) Encode(v interface{}) error { return f(v) }
46 |
47 | type Delimited interface {
48 | Delimiter() []byte
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/server/http/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "crypto/tls"
21 | "github.com/lastbackend/toolkit/pkg/server"
22 | "net/http"
23 | )
24 |
25 | const (
26 | defaultAddress = ":8080"
27 | optionKindMiddleware server.HttpOptionKind = "middleware"
28 | optionKindExcludeGlobalMiddleware server.HttpOptionKind = "excludeGlobalMiddleware"
29 | )
30 |
31 | const (
32 | MethodGet = "GET"
33 | MethodHead = "HEAD"
34 | MethodPost = "POST"
35 | MethodPut = "PUT"
36 | MethodPatch = "PATCH" // RFC 5789
37 | MethodDelete = "DELETE"
38 | MethodConnect = "CONNECT"
39 | MethodOptions = "OPTIONS"
40 | MethodTrace = "TRACE"
41 | )
42 |
43 | type optionMiddleware struct {
44 | kind server.HttpOptionKind
45 | middleware server.KindMiddleware
46 | }
47 |
48 | func (optionMiddleware) Kind() server.HttpOptionKind {
49 | return optionKindMiddleware
50 | }
51 |
52 | func WithMiddleware(middleware server.KindMiddleware) server.HTTPServerOption {
53 | return &optionMiddleware{kind: optionKindMiddleware, middleware: middleware}
54 | }
55 |
56 | type optionExcludeGlobalMiddleware struct {
57 | kind server.HttpOptionKind
58 | regexp string
59 | }
60 |
61 | func (optionExcludeGlobalMiddleware) Kind() server.HttpOptionKind {
62 | return optionKindExcludeGlobalMiddleware
63 | }
64 |
65 | func WithExcludeGlobalMiddleware(regexp string) server.HTTPServerOption {
66 | return &optionExcludeGlobalMiddleware{kind: optionKindMiddleware, regexp: regexp}
67 | }
68 |
69 | type Config struct {
70 | Id string
71 |
72 | Host string `env:"SERVER_LISTEN" envDefault:"0.0.0.0" comment:"Set HTTP server listen host"`
73 | Port int `env:"SERVER_PORT" envDefault:"8080" comment:"Set HTTP server listen port"`
74 |
75 | Prefix string
76 |
77 | EnableCORS bool `env:"SERVER_CORS_ENABLED" envDefault:"false" comment:"Enable Cross-Origin Resource Sharing header"`
78 | IsDisable bool
79 |
80 | TLSConfig *tls.Config
81 | }
82 |
83 | type Wrapper func(h http.Handler) http.Handler
84 |
--------------------------------------------------------------------------------
/pkg/server/http/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package http
18 |
--------------------------------------------------------------------------------
/pkg/server/http/websockets/event.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package websockets
18 |
19 | import (
20 | "context"
21 | "encoding/json"
22 | )
23 |
24 | type Event struct {
25 | Type string `json:"type"`
26 | Payload json.RawMessage `json:"payload"`
27 | }
28 |
29 | type EventHandler func(ctx context.Context, event Event, c *Client) error
30 |
--------------------------------------------------------------------------------
/pkg/server/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package server
18 |
19 | import (
20 | "context"
21 | "crypto/tls"
22 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler"
23 | "github.com/lastbackend/toolkit/pkg/server/http/websockets"
24 | "google.golang.org/grpc"
25 | "net/http"
26 | )
27 |
28 | type HTTPServer interface {
29 | Start(ctx context.Context) error
30 | Stop(ctx context.Context) error
31 |
32 | UseMiddleware(...KindMiddleware)
33 | UseMarshaler(contentType string, marshaler marshaler.Marshaler) error
34 |
35 | GetMiddlewares() []any
36 | SetMiddleware(middleware any)
37 |
38 | SetService(service interface{})
39 | GetService() interface{}
40 |
41 | AddHandler(method, path string, h http.HandlerFunc, opts ...HTTPServerOption)
42 |
43 | GetConstructor() interface{}
44 |
45 | Subscribe(event string, h websockets.EventHandler)
46 | Info() ServerInfo
47 |
48 | ServerWS(w http.ResponseWriter, r *http.Request)
49 | SetCorsHandlerFunc(hf http.HandlerFunc)
50 | SetErrorHandlerFunc(hf func(http.ResponseWriter, error))
51 | }
52 |
53 | type HTTPServerOptions struct {
54 | Host string
55 | Port int
56 |
57 | TLSConfig *tls.Config
58 | }
59 |
60 | type ServerInfo struct {
61 | Kind ServerKind
62 | Host string
63 | Port int
64 |
65 | TLSConfig *tls.Config
66 | }
67 |
68 | type HTTPServerHandler struct {
69 | Method string
70 | Path string
71 | Handler http.HandlerFunc
72 | Options []HTTPServerOption
73 | }
74 |
75 | type HttpOptionKind string
76 |
77 | type HTTPServerOption interface {
78 | Kind() HttpOptionKind
79 | }
80 |
81 | type DefaultHttpServerMiddleware struct{}
82 |
83 | func (DefaultHttpServerMiddleware) Order() int {
84 | return 0
85 | }
86 |
87 | type HttpServerMiddleware interface {
88 | Apply(h http.HandlerFunc) http.HandlerFunc
89 | Kind() KindMiddleware
90 | Order() int
91 | }
92 |
93 | type KindMiddleware string
94 | type KindInterceptor string
95 |
96 | type GRPCServer interface {
97 | Start(ctx context.Context) error
98 | Stop() error
99 |
100 | SetDescriptor(descriptor grpc.ServiceDesc)
101 |
102 | SetService(constructor interface{})
103 | SetConstructor(fn interface{})
104 |
105 | GetInterceptors() []interface{}
106 | SetInterceptor(interceptor any)
107 |
108 | GetService() interface{}
109 | GetConstructor() interface{}
110 | GetInterceptorsConstructor() interface{}
111 |
112 | RegisterService(service interface{})
113 | Info() ServerInfo
114 | }
115 |
116 | type GRPCServerOptions struct {
117 | Host string
118 | Port int
119 |
120 | TLSConfig *tls.Config
121 | }
122 |
123 | type ServerKind string
124 |
125 | type GRPCInterceptor interface {
126 | Kind() KindInterceptor
127 | Order() int
128 | Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error)
129 | }
130 |
131 | const (
132 | ServerKindHTTPServer = "http"
133 | ServerKindGRPCServer = "grpc"
134 | )
135 |
--------------------------------------------------------------------------------
/pkg/tools/metrics/metrics.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | type Metrics interface {
4 | }
5 |
--------------------------------------------------------------------------------
/pkg/tools/probes/probes.go:
--------------------------------------------------------------------------------
1 | package probes
2 |
3 | import "context"
4 |
5 | type ProbeKind int
6 | type HandleFunc func() error
7 |
8 | const (
9 | ReadinessProbe ProbeKind = iota
10 | LivenessProbe
11 | )
12 |
13 | type Probes interface {
14 | Start(ctx context.Context) error
15 | RegisterCheck(name string, kind ProbeKind, fn HandleFunc) error
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/tools/traces/traces.go:
--------------------------------------------------------------------------------
1 | package traces
2 |
3 | type Traces interface {
4 | }
5 |
--------------------------------------------------------------------------------
/pkg/util/addr/addr.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package addr
18 |
19 | import (
20 | "net"
21 | )
22 |
23 | func addrToIP(addr net.Addr) net.IP {
24 | switch v := addr.(type) {
25 | case *net.IPAddr:
26 | return v.IP
27 | case *net.IPNet:
28 | return v.IP
29 | default:
30 | return nil
31 | }
32 | }
33 |
34 | func localIPs() []string {
35 | ifaces, err := net.Interfaces()
36 | if err != nil {
37 | return nil
38 | }
39 |
40 | var ipAddrs []string
41 |
42 | for _, iface := range ifaces {
43 | addrs, err := iface.Addrs()
44 | if err != nil {
45 | continue // ignore error
46 | }
47 |
48 | for _, addr := range addrs {
49 | if ip := addrToIP(addr); ip != nil {
50 | ipAddrs = append(ipAddrs, ip.String())
51 | }
52 | }
53 | }
54 |
55 | return ipAddrs
56 | }
57 |
58 | func DetectIP() (string, error) {
59 | conn, err := net.Dial("udp", "127.0.0.1:9000")
60 | if err != nil {
61 | return "", err
62 | }
63 |
64 | defer conn.Close()
65 | addr := conn.LocalAddr().(*net.UDPAddr)
66 | return addr.IP.String(), nil
67 | }
68 |
69 | func IPs() []string {
70 | return localIPs()
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/util/backoff/backoff.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package backoff
18 |
19 | import (
20 | "math"
21 | "sync/atomic"
22 | "time"
23 | )
24 |
25 | const maxInt64 = float64(math.MaxInt64 - 512)
26 |
27 | type Backoff struct {
28 | attempt uint64
29 | // Factor are the multiplication factor
30 | Factor float64
31 | // Min and Max are the minimum and maximum counter values.
32 | Min, Max time.Duration
33 | }
34 |
35 | func (b *Backoff) Duration() time.Duration {
36 | d := b.ForAttempt(float64(atomic.AddUint64(&b.attempt, 1) - 1))
37 | return d
38 | }
39 |
40 | func (b *Backoff) ForAttempt(attempt float64) time.Duration {
41 | min := b.Min
42 | if min <= 0 {
43 | min = 100 * time.Millisecond
44 | }
45 | max := b.Max
46 | if max <= 0 {
47 | max = 10 * time.Second
48 | }
49 | if min >= max {
50 | return max
51 | }
52 |
53 | factor := b.Factor
54 | if factor <= 0 {
55 | factor = 2
56 | }
57 |
58 | duration := float64(min) * math.Pow(factor, attempt)
59 |
60 | if duration > maxInt64 {
61 | return max
62 | }
63 |
64 | d := time.Duration(duration)
65 | if d < min {
66 | return min
67 | }
68 | if d > max {
69 | return max
70 | }
71 | return d
72 | }
73 |
74 | func (b *Backoff) Reset() {
75 | atomic.StoreUint64(&b.attempt, 0)
76 | }
77 |
78 | func (b *Backoff) Attempt() float64 {
79 | return float64(atomic.LoadUint64(&b.attempt))
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/util/converter/converter.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package converter
18 |
19 | import (
20 | "encoding/base64"
21 | "fmt"
22 | "math"
23 | "reflect"
24 | "strconv"
25 | "strings"
26 | "time"
27 | )
28 |
29 | func ToTrimRegexFromQueryParameter(v string) string {
30 | return strings.Split(v, ":")[0]
31 | }
32 |
33 | func StringToInt64(s string) int64 {
34 | i, _ := strconv.ParseInt(s, 10, 64)
35 | return i
36 | }
37 |
38 | func StringToInt(s string) int {
39 | i, _ := strconv.Atoi(s)
40 | return i
41 | }
42 |
43 | func StringToFloat(s string) float64 {
44 | i, _ := strconv.ParseFloat(s, 64)
45 | return i
46 | }
47 |
48 | func IntToString(i int) string {
49 | return strconv.Itoa(i)
50 | }
51 |
52 | func StringToBool(s string) bool {
53 | s = strings.ToLower(s)
54 | if s == "true" || s == "1" || s == "t" {
55 | return true
56 | }
57 | return false
58 | }
59 |
60 | func StringToUint(s string) uint {
61 | i, _ := strconv.ParseUint(s, 10, 64)
62 | return uint(i)
63 | }
64 |
65 | func BoolToString(str string) (bool, error) {
66 | switch str {
67 | case "":
68 | return false, nil
69 | case "1", "t", "T", "true", "TRUE", "True":
70 | return true, nil
71 | case "0", "f", "F", "false", "FALSE", "False":
72 | return false, nil
73 | }
74 | return false, fmt.Errorf("parse bool string: %s", str)
75 | }
76 |
77 | func Int64ToInt(i int64) int {
78 | return StringToInt(strconv.FormatInt(i, 10))
79 | }
80 |
81 | func DecodeBase64(s string) string {
82 | buf, _ := base64.StdEncoding.DecodeString(s)
83 | return string(buf)
84 | }
85 |
86 | func EncodeBase64(s string) string {
87 | return base64.StdEncoding.EncodeToString([]byte(s))
88 | }
89 |
90 | func HumanizeTimeDuration(duration time.Duration) string {
91 | hours := int64(math.Mod(duration.Hours(), 24))
92 | minutes := int64(math.Mod(duration.Minutes(), 60))
93 | seconds := int64(math.Mod(duration.Seconds(), 60))
94 |
95 | chunks := []struct {
96 | amount int64
97 | }{
98 | {hours},
99 | {minutes},
100 | {seconds},
101 | }
102 |
103 | parts := make([]string, 0)
104 |
105 | for _, chunk := range chunks {
106 | switch chunk.amount {
107 | case 0:
108 | parts = append(parts, "00")
109 | continue
110 | default:
111 | if chunk.amount >= 10 {
112 | parts = append(parts, fmt.Sprintf("%d", chunk.amount))
113 | } else {
114 | parts = append(parts, fmt.Sprintf("0%d", chunk.amount))
115 | }
116 | }
117 | }
118 |
119 | return strings.Join(parts, ":")
120 | }
121 |
122 | func EnforcePtr(obj interface{}) (reflect.Value, error) {
123 | v := reflect.ValueOf(obj)
124 | if v.Kind() != reflect.Ptr {
125 | if v.Kind() == reflect.Invalid {
126 | return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind")
127 | }
128 | return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type())
129 | }
130 | if v.IsNil() {
131 | return reflect.Value{}, fmt.Errorf("expected pointer, but got nil")
132 | }
133 | return v.Elem(), nil
134 | }
135 |
136 | func ToBoolPointer(v bool) *bool {
137 | return &v
138 | }
139 |
140 | func ToStringPointer(v string) *string {
141 | return &v
142 | }
143 |
144 | func ToIntPointer(v int) *int {
145 | return &v
146 | }
147 |
148 | func ToInt64Pointer(v int64) *int64 {
149 | return &v
150 | }
151 |
152 | func ToDurationPointer(v time.Duration) *time.Duration {
153 | return &v
154 | }
155 |
--------------------------------------------------------------------------------
/pkg/util/filesystem/filesystem.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package filesystem
18 |
19 | import (
20 | "os"
21 | "path/filepath"
22 | )
23 |
24 | func WalkMatch(root, pattern string) ([]string, error) {
25 | var matches []string
26 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
27 | if err != nil {
28 | return err
29 | }
30 | if info.IsDir() {
31 | return nil
32 | }
33 | if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
34 | return err
35 | } else if matched {
36 | matches = append(matches, path)
37 | }
38 | return nil
39 | })
40 | if err != nil {
41 | return nil, err
42 | }
43 | return matches, nil
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/util/net/net.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package net
18 |
19 | import (
20 | "errors"
21 | "fmt"
22 | "net"
23 | "strconv"
24 | "strings"
25 | )
26 |
27 | // HostPort format addr and port suitable for dial
28 | func HostPort(addr string, port interface{}) string {
29 | host := addr
30 | if strings.Count(addr, ":") > 0 {
31 | host = fmt.Sprintf("[%s]", addr)
32 | }
33 | // when port is blank or 0, host is a queue name
34 | if v, ok := port.(string); ok && v == "" {
35 | return host
36 | } else if v, ok := port.(int); ok && v == 0 && net.ParseIP(host) == nil {
37 | return host
38 | }
39 |
40 | return fmt.Sprintf("%s:%v", host, port)
41 | }
42 |
43 | // Listen takes addr:portmin-portmax and binds to the first available port
44 | // Example: Listen("localhost:5000-6000", fn)
45 | func Listen(addr string, fn func(string) (net.Listener, error)) (net.Listener, error) {
46 |
47 | if strings.Count(addr, ":") == 1 && strings.Count(addr, "-") == 0 {
48 | return fn(addr)
49 | }
50 |
51 | // host:port || host:min-max
52 | host, ports, err := net.SplitHostPort(addr)
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | // try to extract port range
58 | prange := strings.Split(ports, "-")
59 |
60 | // single port
61 | if len(prange) < 2 {
62 | return fn(addr)
63 | }
64 |
65 | // we have a port range
66 |
67 | // extract min port
68 | min, err := strconv.Atoi(prange[0])
69 | if err != nil {
70 | return nil, errors.New("unable to extract port range")
71 | }
72 |
73 | // extract max port
74 | max, err := strconv.Atoi(prange[1])
75 | if err != nil {
76 | return nil, errors.New("unable to extract port range")
77 | }
78 |
79 | // range the ports
80 | for port := min; port <= max; port++ {
81 | // try bind to host:port
82 | ln, err := fn(HostPort(host, port))
83 | if err == nil {
84 | return ln, nil
85 | }
86 |
87 | // hit max port
88 | if port == max {
89 | return nil, err
90 | }
91 | }
92 |
93 | // why are we here?
94 | return nil, fmt.Errorf("unable to bind to %s", addr)
95 | }
96 |
--------------------------------------------------------------------------------
/pkg/util/parser/parser.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package parser
18 |
19 | import "strings"
20 |
21 | func ParseNameAndVendor(s string) (string, string) {
22 | i := strings.LastIndex(s, "/")
23 | if i == -1 {
24 | return s, ""
25 | }
26 | return s[i+1:], s[:i+1]
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/util/strings/strings.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package strings
18 |
19 | import (
20 | "golang.org/x/text/cases"
21 | "golang.org/x/text/language"
22 | "strings"
23 | )
24 |
25 | var uppercaseAcronym = map[string]string{
26 | "ID": "id",
27 | }
28 |
29 | // ToCamelCase Converts a string to CamelCase
30 | func toCamelCase(s string, initCase bool) string {
31 | s = strings.TrimSpace(s)
32 | if s == "" {
33 | return s
34 | }
35 | if a, ok := uppercaseAcronym[s]; ok {
36 | s = a
37 | }
38 |
39 | n := strings.Builder{}
40 | n.Grow(len(s))
41 | capNext := initCase
42 | for i, v := range []byte(s) {
43 | vIsCap := v >= 'A' && v <= 'Z'
44 | vIsLow := v >= 'a' && v <= 'z'
45 | if capNext {
46 | if vIsLow {
47 | v += 'A'
48 | v -= 'a'
49 | }
50 | } else if i == 0 {
51 | if vIsCap {
52 | v += 'a'
53 | v -= 'A'
54 | }
55 | }
56 | if vIsCap || vIsLow {
57 | n.WriteByte(v)
58 | capNext = false
59 | } else if vIsNum := v >= '0' && v <= '9'; vIsNum {
60 | n.WriteByte(v)
61 | capNext = true
62 | } else {
63 | capNext = v == '_' || v == ' ' || v == '-' || v == '.'
64 | }
65 | }
66 | return n.String()
67 | }
68 |
69 | // ToCamel converts a string to CamelCase
70 | func ToCamel(s string) string {
71 | return toCamelCase(s, true)
72 | }
73 |
74 | // ToLowerCamel converts a string to lowerCamelCase
75 | func ToLowerCamel(s string) string {
76 | return toCamelCase(s, false)
77 | }
78 |
79 | func Title(s string) string {
80 | return cases.Title(language.English).String(s)
81 | }
82 |
83 | func ToUpper(s string) string {
84 | return strings.ToUpper(s)
85 | }
86 |
87 | func ToLower(s string) string {
88 | return strings.ToLower(s)
89 | }
90 |
91 | func GitParse(s string) string {
92 | list := strings.Split(s, "/")
93 | return strings.Join(list[:2], "/")
94 | }
95 |
96 | func DeHyphenFunc(s string) string {
97 | return strings.ReplaceAll(s, "-", "")
98 | }
99 |
100 | func LowerHyphenFunc(s string) string {
101 | return strings.ReplaceAll(s, "-", "_")
102 | }
103 |
104 | func ToHyphen(s string) string {
105 | return strings.ReplaceAll(s, "-", "_")
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/util/tls/tls.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tls
18 |
19 | import (
20 | "bytes"
21 | "crypto/ecdsa"
22 | "crypto/elliptic"
23 | "crypto/rand"
24 | "crypto/tls"
25 | "crypto/x509"
26 | "crypto/x509/pkix"
27 | "encoding/pem"
28 | "math/big"
29 | "net"
30 | "time"
31 | )
32 |
33 | func Certificate(host ...string) (tls.Certificate, error) {
34 | priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
35 | if err != nil {
36 | return tls.Certificate{}, err
37 | }
38 |
39 | notBefore := time.Now()
40 | notAfter := notBefore.Add(time.Hour * 24 * 365)
41 |
42 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
43 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
44 | if err != nil {
45 | return tls.Certificate{}, err
46 | }
47 |
48 | template := x509.Certificate{
49 | SerialNumber: serialNumber,
50 | Subject: pkix.Name{
51 | Organization: []string{"Acme Co"},
52 | },
53 | NotBefore: notBefore,
54 | NotAfter: notAfter,
55 |
56 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
57 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
58 | BasicConstraintsValid: true,
59 | }
60 |
61 | for _, h := range host {
62 | if ip := net.ParseIP(h); ip != nil {
63 | template.IPAddresses = append(template.IPAddresses, ip)
64 | } else {
65 | template.DNSNames = append(template.DNSNames, h)
66 | }
67 | }
68 |
69 | template.IsCA = true
70 | template.KeyUsage |= x509.KeyUsageCertSign
71 |
72 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
73 | if err != nil {
74 | return tls.Certificate{}, err
75 | }
76 |
77 | // create public key
78 | certOut := bytes.NewBuffer(nil)
79 |
80 | err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
81 | if err != nil {
82 | return tls.Certificate{}, err
83 | }
84 |
85 | // create private key
86 | keyOut := bytes.NewBuffer(nil)
87 | b, err := x509.MarshalECPrivateKey(priv)
88 | if err != nil {
89 | return tls.Certificate{}, err
90 | }
91 | err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
92 | if err != nil {
93 | return tls.Certificate{}, err
94 | }
95 |
96 | return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes())
97 | }
98 |
--------------------------------------------------------------------------------
/pkg/util/types/types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "reflect"
4 |
5 | func Type(myvar interface{}) string {
6 | if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr {
7 | return "*" + t.Elem().PkgPath() + "." + t.Elem().Name()
8 | } else {
9 | return t.PkgPath() + "." + t.Name()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/generator/generator.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package generator
18 |
19 | import (
20 | "flag"
21 |
22 | "github.com/lastbackend/toolkit/protoc-gen-toolkit/descriptor"
23 | "github.com/lastbackend/toolkit/protoc-gen-toolkit/gentoolkit"
24 | "google.golang.org/protobuf/compiler/protogen"
25 | "google.golang.org/protobuf/types/pluginpb"
26 | )
27 |
28 | type Generator struct {
29 | files []*descriptor.File
30 | }
31 |
32 | func Init() *Generator {
33 | g := new(Generator)
34 | g.files = make([]*descriptor.File, 0)
35 | return g
36 | }
37 |
38 | func (g *Generator) Run() error {
39 | var flagSet flag.FlagSet
40 |
41 | protogen.Options{
42 | ParamFunc: flagSet.Set,
43 | }.Run(func(gen *protogen.Plugin) (err error) {
44 |
45 | gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
46 |
47 | desc := descriptor.NewDescriptor()
48 |
49 | if err := desc.LoadFromPlugin(gen); err != nil {
50 | return err
51 | }
52 |
53 | if err := g.LoadFiles(gen, desc); err != nil {
54 | return err
55 | }
56 |
57 | // Generate generates a *.pb.toolkit.go file containing Toolkit service definitions.
58 | contentFiles, err := gentoolkit.New(desc).Generate(g.files)
59 | if err != nil {
60 | return err
61 | }
62 |
63 | // Write generated files to disk
64 | for _, f := range contentFiles {
65 | genFile := gen.NewGeneratedFile(f.GetName(), protogen.GoImportPath(f.GoPkg.Path))
66 | if _, err := genFile.Write([]byte(f.GetContent())); err != nil {
67 | return err
68 | }
69 | }
70 |
71 | return nil
72 | })
73 |
74 | return nil
75 | }
76 |
77 | func (g *Generator) LoadFiles(gen *protogen.Plugin, desc *descriptor.Descriptor) (err error) {
78 | g.files = make([]*descriptor.File, 0)
79 | for _, f := range gen.Request.FileToGenerate {
80 | file, err := desc.FindFile(f)
81 | if err != nil {
82 | return err
83 | }
84 | g.files = append(g.files, file)
85 | }
86 | return err
87 | }
88 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/gentoolkit/templates/header.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // HeaderTpl is the header template used for new services.
20 | var HeaderTpl = `// Code generated by protoc-gen-toolkit. DO NOT EDIT.
21 | // source: {{ .GetName }}
22 |
23 | package {{ .GoPkg.Name }}
24 |
25 | import (
26 | {{ range $i := .Imports }}{{ if $i.Standard }}{{ $i | printf "%s\n" }}{{ end }}{{ end }}
27 |
28 | {{ range $i := .Imports }}{{ if not $i.Standard }}{{ $i | printf "%s\n" }}{{ end }}{{ end }}
29 | )
30 | `
31 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/gentoolkit/templates/message.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // MessageTpl is the message template used for new services.
20 | var MessageTpl = `// Code generated by protoc-gen-toolkit. DO NOT EDIT.
21 | // source: {{ .GetName }}
22 |
23 | package {{ .GoPkg.Name }}
24 |
25 | // {{ .Message }}
26 | `
27 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/gentoolkit/templates/plugin.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // PluginDefineTpl is the plugin define template used for new services.
20 | var PluginDefineTpl = `
21 | {{- range $type, $plugins := . }}
22 | {{- range $index, $plugin := $plugins }}
23 | type {{ $plugin.Prefix | ToCamel }}Plugin interface {
24 | {{ $type }}.Plugin
25 | }
26 | {{ end }}
27 | {{- end }}
28 | `
29 |
30 | // PluginInitializeTpl is the plugin initialize template used for new services.
31 | var PluginInitializeTpl = `
32 | {{- range $type, $plugins := . }}
33 | {{- range $index, $plugin := $plugins }}
34 | plugin_{{ $plugin.Prefix | ToLower }} := {{ $type }}.NewPlugin(app.runtime, &{{ $type }}.Options{Name: "{{ $plugin.Prefix | ToLower }}"})
35 | {{- end }}
36 | {{- end -}}
37 | `
38 |
39 | // PluginRegisterTpl is the plugin register template used for new services.
40 | var PluginRegisterTpl = `
41 | {{- range $type, $plugins := . }}
42 | {{- range $index, $plugin := $plugins }}
43 | app.runtime.Plugin().Provide(func() {{ $plugin.Prefix | ToCamel }}Plugin { return plugin_{{ $plugin.Prefix | ToLower }} })
44 | {{- end }}
45 | {{- end -}}
46 | `
47 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/gentoolkit/templates/test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package templates
18 |
19 | // TestTpl is the test template used for new services.
20 | var TestTpl = `// Suppress "imported and not used" errors
21 | var _ context.Context
22 | var _ emptypb.Empty
23 | var _ empty.Empty
24 |
25 | {{ range $svc := .Services }}
26 | // Mock server API for Api service
27 | type {{ $svc.GetName }}Stubs struct {
28 | {{ range $m := $svc.Methods }}
29 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }}
30 | {{ $m.GetName }} []{{ $m.GetName }}Stub
31 | {{ else }}
32 | {{ end }}
33 | {{ end }}
34 | }
35 |
36 | func New{{ $svc.GetName }}Stubs() *{{ $svc.GetName }}Stubs {
37 | stubs:= new({{ $svc.GetName }}Stubs)
38 | {{ range $m := $svc.Methods }}
39 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }}
40 | stubs.{{ $m.GetName }} = make([]{{ $m.GetName }}Stub,0)
41 | {{ end }}
42 | {{ end }}
43 | return stubs
44 | }
45 |
46 | func With{{ $svc.GetName }}Stubs(stubs *{{ $svc.GetName }}Stubs) servicepb.{{ $svc.GetName }}RPCClient{
47 |
48 | rpc_mock := new(service_mocks.{{ $svc.GetName }}RPCClient)
49 |
50 | {{ range $m := $svc.Methods }}
51 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }}
52 | for _, st := range stubs.{{ $m.GetName }} {
53 | resp := st.Response
54 | err := st.Error
55 | rpc_mock.On("{{ $m.GetName }}", st.Context, st.Request).Return(
56 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) *{{ $m.ResponseType.GoType $m.Service.File.GoPkg.Path }} {
57 | return resp
58 | },
59 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) error {
60 | return err
61 | },
62 | )
63 | rpc_mock.On("{{ $m.GetName }}", st.Context, st.Request, st.CallOptions).Return(
64 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) *{{ $m.ResponseType.GoType $m.Service.File.GoPkg.Path }} {
65 | return resp
66 | },
67 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) error {
68 | return err
69 | },
70 | )
71 | }
72 | {{ end }}
73 | {{ end }}
74 |
75 | return rpc_mock
76 | }
77 | {{ end }}
78 |
79 | {{ range $svc := .Services }}
80 | {{ range $m := $svc.Methods }}
81 | type {{ $m.GetName }}Stub struct {
82 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }}
83 | Context context.Context
84 | Request *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}
85 | Response *{{ $m.ResponseType.GoType $m.Service.File.GoPkg.Path }}
86 | CallOptions []client.GRPCCallOption
87 | Error error
88 | {{ end }}
89 | }
90 | {{ end }}
91 | {{ end }}
92 | `
93 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/gentoolkit/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package gentoolkit
18 |
19 | func camel(s string) string {
20 | if s == "" {
21 | return ""
22 | }
23 | t := make([]byte, 0, 32)
24 | i := 0
25 | if s[0] == '_' {
26 | // Need a capital letter; drop the '_'.
27 | t = append(t, 'X')
28 | i++
29 | }
30 | // Invariant: if the next letter is lower case, it must be converted
31 | // to upper case.
32 | // That is, we process a word at a time, where words are marked by _ or
33 | // upper case letter. Digits are treated as words.
34 | for ; i < len(s); i++ {
35 | c := s[i]
36 | if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
37 | continue // Skip the underscore in s.
38 | }
39 | if isASCIIDigit(c) {
40 | t = append(t, c)
41 | continue
42 | }
43 | // Assume we have a letter now - if not, it's a bogus identifier.
44 | // The next word is a sequence of characters that must start upper case.
45 | if isASCIILower(c) {
46 | c ^= ' ' // Make it a capital letter.
47 | }
48 | t = append(t, c) // Guaranteed not lower case.
49 | // Accept lower case sequence that follows.
50 | for i+1 < len(s) && isASCIILower(s[i+1]) {
51 | i++
52 | t = append(t, s[i])
53 | }
54 | }
55 | return string(t)
56 | }
57 |
58 | // And now lots of helper functions.
59 |
60 | // Is c an ASCII lower-case letter?
61 | func isASCIILower(c byte) bool {
62 | return 'a' <= c && c <= 'z'
63 | }
64 |
65 | // Is c an ASCII digit?
66 | func isASCIIDigit(c byte) bool {
67 | return '0' <= c && c <= '9'
68 | }
69 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "github.com/lastbackend/toolkit/protoc-gen-toolkit/generator"
21 |
22 | "os"
23 | )
24 |
25 | func main() {
26 | g := generator.Init()
27 | if err := g.Run(); err != nil {
28 | os.Exit(1)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/protoc-gen-toolkit/toolkit/options/annotations.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package toolkit;
4 |
5 | option go_package = "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options;annotations";
6 |
7 | import "google/protobuf/descriptor.proto";
8 |
9 | message Plugin {
10 | string plugin = 1;
11 | string prefix = 2;
12 | }
13 |
14 | message Service {
15 | string service = 1;
16 | string package = 2;
17 | }
18 |
19 | extend google.protobuf.FileOptions {
20 | repeated Plugin plugins = 50001;
21 | repeated Service services = 50002;
22 | TestSpec tests_spec = 50004;
23 | }
24 |
25 | message Runtime {
26 | enum Server {
27 | GRPC = 0;
28 | HTTP = 1;
29 | WEBSOCKET_PROXY = 2;
30 | WEBSOCKET = 3;
31 | }
32 | repeated Plugin plugins = 1;
33 | repeated Server servers = 2;
34 | }
35 |
36 | extend google.protobuf.ServiceOptions {
37 | Runtime runtime = 70001;
38 | Server server = 70002;
39 | }
40 |
41 | message TestSpec {
42 | MockeryTestsSpec mockery = 1;
43 | }
44 |
45 | message MockeryTestsSpec {
46 | string package = 1;
47 | }
48 |
49 | extend google.protobuf.MethodOptions {
50 | Route route = 60001;
51 | }
52 |
53 | message Server {
54 | repeated string middlewares = 1;
55 | }
56 |
57 | message Route {
58 | repeated string middlewares = 1;
59 | repeated string exclude_global_middlewares = 2;
60 | oneof server {
61 | HttpProxy http_proxy = 3;
62 | WsProxy websocket_proxy = 4;
63 | bool websocket = 5;
64 | }
65 | }
66 |
67 | message HttpProxy {
68 | string service = 1;
69 | string method = 2;
70 | }
71 |
72 | message WsProxy {
73 | string service = 1;
74 | string method = 2;
75 | }
76 |
--------------------------------------------------------------------------------
/toolkit.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright [2014] - [2023] The Last.Backend authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package toolkit
18 |
--------------------------------------------------------------------------------