├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature-request---enhancement.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── tests.yaml │ └── website.yml ├── .gitignore ├── .golangci.yaml ├── .richstyle.yaml ├── LICENSE ├── README.md ├── auth ├── auth.go ├── jwt │ ├── jwt.go │ └── token │ │ ├── jwt.go │ │ ├── jwt_test.go │ │ ├── options.go │ │ ├── test │ │ ├── sample_key │ │ ├── sample_key 2 │ │ └── sample_key.pub │ │ └── token.go ├── noop.go ├── options.go ├── rules.go └── rules_test.go ├── broker ├── broker.go ├── http.go ├── http_test.go ├── memory.go ├── memory_test.go ├── nats │ ├── context.go │ ├── nats.go │ ├── nats_test.go │ └── options.go ├── options.go └── rabbitmq │ ├── auth.go │ ├── channel.go │ ├── connection.go │ ├── connection_test.go │ ├── context.go │ ├── options.go │ ├── rabbitmq.go │ └── rabbitmq_test.go ├── cache ├── cache.go ├── memory.go ├── memory_test.go ├── options.go ├── options_test.go └── redis │ ├── options.go │ ├── options_test.go │ ├── redis.go │ └── redis_test.go ├── client ├── backoff.go ├── backoff_test.go ├── cache.go ├── cache_test.go ├── client.go ├── common_test.go ├── context.go ├── grpc │ ├── codec.go │ ├── error.go │ ├── grpc.go │ ├── grpc_pool.go │ ├── grpc_pool_test.go │ ├── grpc_test.go │ ├── message.go │ ├── options.go │ ├── request.go │ ├── request_test.go │ ├── response.go │ └── stream.go ├── options.go ├── options_test.go ├── retry.go ├── rpc_client.go ├── rpc_client_test.go ├── rpc_codec.go ├── rpc_message.go ├── rpc_request.go ├── rpc_request_test.go ├── rpc_response.go ├── rpc_stream.go └── wrapper.go ├── cmd ├── cmd.go └── options.go ├── codec ├── bytes │ ├── bytes.go │ └── marshaler.go ├── codec.go ├── grpc │ ├── grpc.go │ └── util.go ├── json │ ├── json.go │ └── marshaler.go ├── jsonrpc │ ├── client.go │ ├── jsonrpc.go │ └── server.go ├── proto │ ├── marshaler.go │ ├── message.go │ └── proto.go ├── protorpc │ ├── envelope.pb.go │ ├── envelope.pb.micro.go │ ├── envelope.proto │ ├── netstring.go │ └── protorpc.go └── text │ └── text.go ├── config ├── README.md ├── config.go ├── default.go ├── default_test.go ├── encoder │ ├── encoder.go │ └── json │ │ └── json.go ├── loader │ ├── loader.go │ └── memory │ │ ├── memory.go │ │ └── options.go ├── options.go ├── reader │ ├── json │ │ ├── json.go │ │ ├── json_test.go │ │ ├── values.go │ │ └── values_test.go │ ├── options.go │ ├── preprocessor.go │ ├── preprocessor_test.go │ └── reader.go ├── secrets │ ├── box │ │ ├── box.go │ │ └── box_test.go │ ├── secretbox │ │ ├── secretbox.go │ │ └── secretbox_test.go │ └── secrets.go ├── source │ ├── changeset.go │ ├── cli │ │ ├── README.md │ │ ├── cli.go │ │ ├── cli_test.go │ │ ├── options.go │ │ └── util.go │ ├── env │ │ ├── README.md │ │ ├── env.go │ │ ├── env_test.go │ │ ├── options.go │ │ └── watcher.go │ ├── file │ │ ├── README.md │ │ ├── file.go │ │ ├── file_test.go │ │ ├── format.go │ │ ├── format_test.go │ │ ├── options.go │ │ ├── watcher.go │ │ ├── watcher_linux.go │ │ └── watcher_test.go │ ├── flag │ │ ├── README.md │ │ ├── flag.go │ │ ├── flag_test.go │ │ └── options.go │ ├── memory │ │ ├── README.md │ │ ├── memory.go │ │ ├── options.go │ │ └── watcher.go │ ├── nats │ │ ├── README.md │ │ ├── nats.go │ │ ├── options.go │ │ └── watcher.go │ ├── noop.go │ ├── options.go │ └── source.go └── value.go ├── debug ├── handler │ └── debug.go ├── log │ ├── log.go │ ├── memory │ │ ├── memory.go │ │ ├── memory_test.go │ │ └── stream.go │ ├── noop │ │ └── noop.go │ ├── options.go │ └── os.go ├── profile │ ├── http │ │ └── http.go │ ├── pprof │ │ └── pprof.go │ └── profile.go ├── proto │ ├── debug.pb.go │ ├── debug.pb.micro.go │ └── debug.proto ├── stats │ ├── default.go │ └── stats.go └── trace │ ├── default.go │ ├── noop.go │ ├── options.go │ └── trace.go ├── errors ├── errors.go ├── errors.pb.go ├── errors.pb.micro.go ├── errors.proto └── errors_test.go ├── event.go ├── go.mod ├── go.sum ├── internal ├── README.md └── website │ ├── .gitignore │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── _config.yml │ ├── _layouts │ └── default.html │ ├── docs │ ├── architecture.md │ ├── broker.md │ ├── client-server.md │ ├── getting-started.md │ ├── index.md │ ├── registry.md │ ├── store.md │ └── transport.md │ ├── images │ └── logo.png │ └── index.html ├── logger ├── context.go ├── default.go ├── helper.go ├── level.go ├── logger.go ├── logger_test.go └── options.go ├── logo.png ├── metadata ├── metadata.go └── metadata_test.go ├── micro.go ├── options.go ├── profile └── profile.go ├── registry ├── cache │ ├── README.md │ ├── cache.go │ └── options.go ├── consul │ ├── consul.go │ ├── encoding.go │ ├── encoding_test.go │ ├── options.go │ ├── registry_test.go │ ├── watcher.go │ └── watcher_test.go ├── etcd │ ├── etcd.go │ ├── options.go │ └── watcher.go ├── mdns_registry.go ├── mdns_test.go ├── memory.go ├── memory_test.go ├── memory_util.go ├── memory_watcher.go ├── nats │ ├── nats.go │ ├── nats_assert_test.go │ ├── nats_environment_test.go │ ├── nats_options.go │ ├── nats_registry.go │ ├── nats_test.go │ ├── nats_util.go │ └── nats_watcher.go ├── options.go ├── options_test.go ├── registry.go └── watcher.go ├── selector ├── common_test.go ├── default.go ├── default_test.go ├── filter.go ├── filter_test.go ├── options.go ├── selector.go ├── strategy.go └── strategy_test.go ├── server ├── context.go ├── extractor.go ├── extractor_test.go ├── grpc │ ├── codec.go │ ├── context.go │ ├── error.go │ ├── extractor.go │ ├── extractor_test.go │ ├── grpc.go │ ├── handler.go │ ├── options.go │ ├── request.go │ ├── response.go │ ├── server.go │ ├── stream.go │ ├── subscriber.go │ └── util.go ├── handler.go ├── mock │ ├── mock.go │ ├── mock_handler.go │ ├── mock_subscriber.go │ └── mock_test.go ├── options.go ├── proto │ ├── server.pb.go │ ├── server.pb.micro.go │ └── server.proto ├── rpc_codec.go ├── rpc_codec_test.go ├── rpc_event.go ├── rpc_events.go ├── rpc_handler.go ├── rpc_helper.go ├── rpc_request.go ├── rpc_response.go ├── rpc_router.go ├── rpc_server.go ├── rpc_stream.go ├── rpc_stream_test.go ├── rpc_util.go ├── server.go ├── subscriber.go └── wrapper.go ├── service ├── options.go └── service.go ├── store ├── file.go ├── file_test.go ├── memory.go ├── mysql │ ├── mysql.go │ └── mysql_test.go ├── nats-js-kv │ ├── README.md │ ├── context.go │ ├── helpers_test.go │ ├── keys.go │ ├── nats.go │ ├── nats_test.go │ ├── options.go │ └── test_data.go ├── noop.go ├── options.go ├── postgres │ ├── README.md │ ├── metadata.go │ ├── pgx │ │ ├── README.md │ │ ├── db.go │ │ ├── metadata.go │ │ ├── pgx.go │ │ ├── pgx_test.go │ │ ├── queries.go │ │ └── templates.go │ ├── postgres.go │ └── postgres_test.go └── store.go ├── test ├── benchmark.go └── service.go ├── transport ├── context.go ├── grpc │ ├── grpc.go │ ├── grpc_test.go │ ├── handler.go │ ├── proto │ │ ├── transport.pb.go │ │ ├── transport.pb.micro.go │ │ ├── transport.proto │ │ └── transport_grpc.pb.go │ └── socket.go ├── headers │ └── headers.go ├── http_client.go ├── http_client_test.go ├── http_listener.go ├── http_proxy.go ├── http_socket.go ├── http_transport.go ├── http_transport_test.go ├── memory.go ├── memory_test.go ├── nats │ ├── nats.go │ ├── nats_test.go │ └── options.go ├── options.go └── transport.go ├── util ├── addr │ ├── addr.go │ └── addr_test.go ├── backoff │ └── backoff.go ├── buf │ └── buf.go ├── grpc │ ├── grpc.go │ └── grpc_test.go ├── http │ ├── http.go │ ├── http_test.go │ ├── options.go │ └── roundtripper.go ├── jitter │ └── jitter.go ├── mdns │ ├── .gitignore │ ├── client.go │ ├── dns_sd.go │ ├── dns_sd_test.go │ ├── server.go │ ├── server_test.go │ ├── zone.go │ └── zone_test.go ├── net │ ├── net.go │ └── net_test.go ├── pool │ ├── default.go │ ├── default_test.go │ ├── options.go │ └── pool.go ├── registry │ ├── util.go │ └── util_test.go ├── ring │ ├── buffer.go │ └── buffer_test.go ├── signal │ └── signal.go ├── socket │ ├── pool.go │ └── socket.go ├── test │ └── test.go ├── tls │ └── tls.go └── wrapper │ ├── wrapper.go │ └── wrapper_test.go ├── web ├── options.go ├── service.go ├── service_test.go ├── web.go └── web_test.go └── wrapper └── trace └── opentelemetry ├── README.md ├── opentelemetry.go ├── options.go └── wrapper.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: For reporting bugs in go-micro 4 | title: "[BUG]" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | 1. What are you trying to do? 12 | 2. What did you expect to happen? 13 | 3. What happens instead? 14 | 15 | ## How to reproduce the bug 16 | 17 | If possible, please include a minimal code snippet here. 18 | 19 | ## Environment 20 | 21 | Go Version: please paste `go version` output here 22 | 23 | ```go 24 | please paste `go env` output here 25 | ``` 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request---enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request / Enhancement 3 | about: If you have a need not served by go-micro 4 | title: "[FEATURE]" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about go-micro 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | Before asking, please check if your question has already been answered: 10 | 11 | 1. Check the documentation - https://micro.mu/docs/ 12 | 2. Check the examples and plugins - https://github.com/micro/examples & https://github.com/micro/go-plugins 13 | 3. Search existing issues 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request template 2 | 3 | Please, go through these steps before clicking submit on this PR. 4 | 5 | 1. Make sure this PR targets the `develop` branch. We follow the git-flow branching model. 6 | 2. Give a descriptive title to your PR. 7 | 3. Provide a description of your changes. 8 | 4. Make sure you have some relevant tests. 9 | 5. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable). 10 | 11 | ## PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING 12 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | on: 3 | push: 4 | branches: 5 | - "**" 6 | pull_request: 7 | types: 8 | - opened 9 | - reopened 10 | branches: 11 | - "**" 12 | jobs: 13 | unittests: 14 | name: Unit Tests 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Go 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: 1.24 22 | check-latest: true 23 | cache: true 24 | - name: Get dependencies 25 | run: | 26 | go install github.com/kyoh86/richgo@latest 27 | go get -v -t -d ./... 28 | - name: Run tests 29 | id: tests 30 | run: richgo test -v -race -cover ./... 31 | env: 32 | IN_TRAVIS_CI: yes 33 | RICHGO_FORCE_COLOR: 1 34 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v5 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./internal/website 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Develop tools 2 | /.vscode/ 3 | /.idea/ 4 | /.trunk 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Folders 14 | _obj 15 | _test 16 | _build 17 | 18 | # Architecture specific extensions/prefixes 19 | *.[568vq] 20 | [568vq].out 21 | 22 | *.cgo1.go 23 | *.cgo2.c 24 | _cgo_defun.c 25 | _cgo_gotypes.go 26 | _cgo_export.* 27 | 28 | # Test binary, build with `go test -c` 29 | *.test 30 | 31 | # Output of the go coverage tool, specifically when used with LiteIDE 32 | *.out 33 | 34 | # vim temp files 35 | *~ 36 | *.swp 37 | *.swo 38 | 39 | # go work files 40 | go.work 41 | go.work.sum 42 | -------------------------------------------------------------------------------- /.richstyle.yaml: -------------------------------------------------------------------------------- 1 | labelType: long 2 | coverThreshold: 70 3 | buildStyle: 4 | bold: true 5 | foreground: yellow 6 | startStyle: 7 | foreground: lightBlack 8 | passStyle: 9 | foreground: green 10 | failStyle: 11 | bold: true 12 | foreground: "#821515" 13 | skipStyle: 14 | foreground: lightBlack 15 | passPackageStyle: 16 | foreground: green 17 | hide: false 18 | failPackageStyle: 19 | bold: true 20 | foreground: "#821515" 21 | coveredStyle: 22 | foreground: green 23 | uncoveredStyle: 24 | bold: true 25 | foreground: yellow 26 | fileStyle: 27 | foreground: cyan 28 | lineStyle: 29 | foreground: magenta 30 | -------------------------------------------------------------------------------- /auth/jwt/token/options.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "time" 5 | 6 | "go-micro.dev/v5/store" 7 | ) 8 | 9 | type Options struct { 10 | // Store to persist the tokens 11 | Store store.Store 12 | // PublicKey base64 encoded, used by JWT 13 | PublicKey string 14 | // PrivateKey base64 encoded, used by JWT 15 | PrivateKey string 16 | } 17 | 18 | type Option func(o *Options) 19 | 20 | // WithStore sets the token providers store. 21 | func WithStore(s store.Store) Option { 22 | return func(o *Options) { 23 | o.Store = s 24 | } 25 | } 26 | 27 | // WithPublicKey sets the JWT public key. 28 | func WithPublicKey(key string) Option { 29 | return func(o *Options) { 30 | o.PublicKey = key 31 | } 32 | } 33 | 34 | // WithPrivateKey sets the JWT private key. 35 | func WithPrivateKey(key string) Option { 36 | return func(o *Options) { 37 | o.PrivateKey = key 38 | } 39 | } 40 | 41 | func NewOptions(opts ...Option) Options { 42 | var options Options 43 | for _, o := range opts { 44 | o(&options) 45 | } 46 | // set default store 47 | if options.Store == nil { 48 | options.Store = store.DefaultStore 49 | } 50 | return options 51 | } 52 | 53 | type GenerateOptions struct { 54 | // Expiry for the token 55 | Expiry time.Duration 56 | } 57 | 58 | type GenerateOption func(o *GenerateOptions) 59 | 60 | // WithExpiry for the generated account's token expires. 61 | func WithExpiry(d time.Duration) GenerateOption { 62 | return func(o *GenerateOptions) { 63 | o.Expiry = d 64 | } 65 | } 66 | 67 | // NewGenerateOptions from a slice of options. 68 | func NewGenerateOptions(opts ...GenerateOption) GenerateOptions { 69 | var options GenerateOptions 70 | for _, o := range opts { 71 | o(&options) 72 | } 73 | // set default Expiry of token 74 | if options.Expiry == 0 { 75 | options.Expiry = time.Minute * 15 76 | } 77 | return options 78 | } 79 | -------------------------------------------------------------------------------- /auth/jwt/token/test/sample_key.pub: -------------------------------------------------------------------------------- 1 | LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE4U2JKUDVYYkVpZFJtNWIyc05wTApHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkL1JsOS8xcE1WN01pTzNMSHd0aEhDMkJSWXFyKzF3RmRvWkNHCkJZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUwQkgvbFhRTXF5RXFGNU1JMnplakM0ek16cjE1T04rZ0U0Sm4KaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtL21VZElUL0xhRjdrUXh5WUs1VktuK05nT1d6TWx6S0FBcENuNwpUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsbzlqRGpsWTVvQk9jemZxZU5XSEs1R1hCN1F3cExOaDk0NlB6ClpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDV3bEVxOGhOaG1obTVOTmUvTytHZ2pCRE5TZlVoMDYrcTRuZ20KYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1b1J0UWhnaVk5MTBwUGY5YmF1WFdxd1VDVWE0cXNIempLUjBMLwpOMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVcmdMclBiRUI5ZWdjR2szOCticEtzM1o2UnI1K3RuRDFCSVBJCkZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVVUR0R4VXg4b2o4VkllUm5XRHE2TWMxaUpwOFV5Y2lCSVRSdHcKNGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMGxhUXpBdVAzYWlXWElNcDJzYzhTYytCbCtMalhtQm94QnJhQgpIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YZ0ZLU3NJbkhEckhWT3lXUEJlM2ZhZEVjNzdiK25iL2V4T3pyCjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= -------------------------------------------------------------------------------- /auth/jwt/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "go-micro.dev/v5/auth" 8 | ) 9 | 10 | var ( 11 | // ErrNotFound is returned when a token cannot be found. 12 | ErrNotFound = errors.New("token not found") 13 | // ErrEncodingToken is returned when the service encounters an error during encoding. 14 | ErrEncodingToken = errors.New("error encoding the token") 15 | // ErrInvalidToken is returned when the token provided is not valid. 16 | ErrInvalidToken = errors.New("invalid token provided") 17 | ) 18 | 19 | // Provider generates and inspects tokens. 20 | type Provider interface { 21 | Generate(account *auth.Account, opts ...GenerateOption) (*Token, error) 22 | Inspect(token string) (*auth.Account, error) 23 | String() string 24 | } 25 | 26 | type Token struct { 27 | // The actual token 28 | Token string `json:"token"` 29 | // Time of token creation 30 | Created time.Time `json:"created"` 31 | // Time of token expiry 32 | Expiry time.Time `json:"expiry"` 33 | } 34 | -------------------------------------------------------------------------------- /broker/memory_test.go: -------------------------------------------------------------------------------- 1 | package broker_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "go-micro.dev/v5/broker" 8 | ) 9 | 10 | func TestMemoryBroker(t *testing.T) { 11 | b := broker.NewMemoryBroker() 12 | 13 | if err := b.Connect(); err != nil { 14 | t.Fatalf("Unexpected connect error %v", err) 15 | } 16 | 17 | topic := "test" 18 | count := 10 19 | 20 | fn := func(p broker.Event) error { 21 | return nil 22 | } 23 | 24 | sub, err := b.Subscribe(topic, fn) 25 | if err != nil { 26 | t.Fatalf("Unexpected error subscribing %v", err) 27 | } 28 | 29 | for i := 0; i < count; i++ { 30 | message := &broker.Message{ 31 | Header: map[string]string{ 32 | "foo": "bar", 33 | "id": fmt.Sprintf("%d", i), 34 | }, 35 | Body: []byte(`hello world`), 36 | } 37 | 38 | if err := b.Publish(topic, message); err != nil { 39 | t.Fatalf("Unexpected error publishing %d", i) 40 | } 41 | } 42 | 43 | if err := sub.Unsubscribe(); err != nil { 44 | t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err) 45 | } 46 | 47 | if err := b.Disconnect(); err != nil { 48 | t.Fatalf("Unexpected connect error %v", err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /broker/nats/context.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/broker" 7 | ) 8 | 9 | // setBrokerOption returns a function to setup a context with given value. 10 | func setBrokerOption(k, v interface{}) broker.Option { 11 | return func(o *broker.Options) { 12 | if o.Context == nil { 13 | o.Context = context.Background() 14 | } 15 | o.Context = context.WithValue(o.Context, k, v) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /broker/nats/options.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | natsp "github.com/nats-io/nats.go" 5 | "go-micro.dev/v5/broker" 6 | ) 7 | 8 | type optionsKey struct{} 9 | type drainConnectionKey struct{} 10 | 11 | // Options accepts nats.Options. 12 | func Options(opts natsp.Options) broker.Option { 13 | return setBrokerOption(optionsKey{}, opts) 14 | } 15 | 16 | // DrainConnection will drain subscription on close. 17 | func DrainConnection() broker.Option { 18 | return setBrokerOption(drainConnectionKey{}, struct{}{}) 19 | } 20 | -------------------------------------------------------------------------------- /broker/rabbitmq/auth.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | type ExternalAuthentication struct { 4 | } 5 | 6 | func (auth *ExternalAuthentication) Mechanism() string { 7 | return "EXTERNAL" 8 | } 9 | 10 | func (auth *ExternalAuthentication) Response() string { 11 | return "" 12 | } 13 | -------------------------------------------------------------------------------- /broker/rabbitmq/context.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/broker" 7 | "go-micro.dev/v5/server" 8 | ) 9 | 10 | // setSubscribeOption returns a function to setup a context with given value. 11 | func setSubscribeOption(k, v interface{}) broker.SubscribeOption { 12 | return func(o *broker.SubscribeOptions) { 13 | if o.Context == nil { 14 | o.Context = context.Background() 15 | } 16 | o.Context = context.WithValue(o.Context, k, v) 17 | } 18 | } 19 | 20 | // setBrokerOption returns a function to setup a context with given value. 21 | func setBrokerOption(k, v interface{}) broker.Option { 22 | return func(o *broker.Options) { 23 | if o.Context == nil { 24 | o.Context = context.Background() 25 | } 26 | o.Context = context.WithValue(o.Context, k, v) 27 | } 28 | } 29 | 30 | // setBrokerOption returns a function to setup a context with given value. 31 | func setServerSubscriberOption(k, v interface{}) server.SubscriberOption { 32 | return func(o *server.SubscriberOptions) { 33 | if o.Context == nil { 34 | o.Context = context.Background() 35 | } 36 | o.Context = context.WithValue(o.Context, k, v) 37 | } 38 | } 39 | 40 | // setPublishOption returns a function to setup a context with given value. 41 | func setPublishOption(k, v interface{}) broker.PublishOption { 42 | return func(o *broker.PublishOptions) { 43 | if o.Context == nil { 44 | o.Context = context.Background() 45 | } 46 | o.Context = context.WithValue(o.Context, k, v) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | ) 8 | 9 | var ( 10 | // DefaultCache is the default cache. 11 | DefaultCache Cache = NewCache() 12 | // DefaultExpiration is the default duration for items stored in 13 | // the cache to expire. 14 | DefaultExpiration time.Duration = 0 15 | 16 | // ErrItemExpired is returned in Cache.Get when the item found in the cache 17 | // has expired. 18 | ErrItemExpired error = errors.New("item has expired") 19 | // ErrKeyNotFound is returned in Cache.Get and Cache.Delete when the 20 | // provided key could not be found in cache. 21 | ErrKeyNotFound error = errors.New("key not found in cache") 22 | ) 23 | 24 | // Cache is the interface that wraps the cache. 25 | type Cache interface { 26 | // Get gets a cached value by key. 27 | Get(ctx context.Context, key string) (interface{}, time.Time, error) 28 | // Put stores a key-value pair into cache. 29 | Put(ctx context.Context, key string, val interface{}, d time.Duration) error 30 | // Delete removes a key from cache. 31 | Delete(ctx context.Context, key string) error 32 | // String returns the name of the implementation. 33 | String() string 34 | } 35 | 36 | // Item represents an item stored in the cache. 37 | type Item struct { 38 | Value interface{} 39 | Expiration int64 40 | } 41 | 42 | // Expired returns true if the item has expired. 43 | func (i *Item) Expired() bool { 44 | if i.Expiration == 0 { 45 | return false 46 | } 47 | 48 | return time.Now().UnixNano() > i.Expiration 49 | } 50 | 51 | // NewCache returns a new cache. 52 | func NewCache(opts ...Option) Cache { 53 | options := NewOptions(opts...) 54 | items := make(map[string]Item) 55 | 56 | if len(options.Items) > 0 { 57 | items = options.Items 58 | } 59 | 60 | return &memCache{ 61 | opts: options, 62 | items: items, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cache/memory.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type memCache struct { 10 | opts Options 11 | 12 | items map[string]Item 13 | sync.RWMutex 14 | } 15 | 16 | func (c *memCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) { 17 | c.RWMutex.RLock() 18 | defer c.RWMutex.RUnlock() 19 | 20 | item, found := c.items[key] 21 | if !found { 22 | return nil, time.Time{}, ErrKeyNotFound 23 | } 24 | if item.Expired() { 25 | return nil, time.Time{}, ErrItemExpired 26 | } 27 | 28 | return item.Value, time.Unix(0, item.Expiration), nil 29 | } 30 | 31 | func (c *memCache) Put(ctx context.Context, key string, val interface{}, d time.Duration) error { 32 | var e int64 33 | if d == DefaultExpiration { 34 | d = c.opts.Expiration 35 | } 36 | if d > 0 { 37 | e = time.Now().Add(d).UnixNano() 38 | } 39 | 40 | c.RWMutex.Lock() 41 | defer c.RWMutex.Unlock() 42 | 43 | c.items[key] = Item{ 44 | Value: val, 45 | Expiration: e, 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (c *memCache) Delete(ctx context.Context, key string) error { 52 | c.RWMutex.Lock() 53 | defer c.RWMutex.Unlock() 54 | 55 | _, found := c.items[key] 56 | if !found { 57 | return ErrKeyNotFound 58 | } 59 | 60 | delete(c.items, key) 61 | return nil 62 | } 63 | 64 | func (m *memCache) String() string { 65 | return "memory" 66 | } 67 | -------------------------------------------------------------------------------- /cache/options.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "go-micro.dev/v5/logger" 8 | ) 9 | 10 | // Options represents the options for the cache. 11 | type Options struct { 12 | // Context should contain all implementation specific options, using context.WithValue. 13 | Context context.Context 14 | // Logger is the be used logger 15 | Logger logger.Logger 16 | Items map[string]Item 17 | // Address represents the address or other connection information of the cache service. 18 | Address string 19 | Expiration time.Duration 20 | } 21 | 22 | // Option manipulates the Options passed. 23 | type Option func(o *Options) 24 | 25 | // Expiration sets the duration for items stored in the cache to expire. 26 | func Expiration(d time.Duration) Option { 27 | return func(o *Options) { 28 | o.Expiration = d 29 | } 30 | } 31 | 32 | // Items initializes the cache with preconfigured items. 33 | func Items(i map[string]Item) Option { 34 | return func(o *Options) { 35 | o.Items = i 36 | } 37 | } 38 | 39 | // WithAddress sets the cache service address or connection information. 40 | func WithAddress(addr string) Option { 41 | return func(o *Options) { 42 | o.Address = addr 43 | } 44 | } 45 | 46 | // WithContext sets the cache context, for any extra configuration. 47 | func WithContext(c context.Context) Option { 48 | return func(o *Options) { 49 | o.Context = c 50 | } 51 | } 52 | 53 | // WithLogger sets underline logger. 54 | func WithLogger(l logger.Logger) Option { 55 | return func(o *Options) { 56 | o.Logger = l 57 | } 58 | } 59 | 60 | // NewOptions returns a new options struct. 61 | func NewOptions(opts ...Option) Options { 62 | options := Options{ 63 | Expiration: DefaultExpiration, 64 | Items: make(map[string]Item), 65 | Logger: logger.DefaultLogger, 66 | } 67 | 68 | for _, o := range opts { 69 | o(&options) 70 | } 71 | 72 | return options 73 | } 74 | -------------------------------------------------------------------------------- /cache/options_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestOptions(t *testing.T) { 9 | testData := map[string]struct { 10 | set bool 11 | expiration time.Duration 12 | items map[string]Item 13 | }{ 14 | "DefaultOptions": {false, DefaultExpiration, map[string]Item{}}, 15 | "ModifiedOptions": {true, time.Second, map[string]Item{"test": {"hello go-micro", 0}}}, 16 | } 17 | 18 | for k, d := range testData { 19 | t.Run(k, func(t *testing.T) { 20 | var opts Options 21 | 22 | if d.set { 23 | opts = NewOptions( 24 | Expiration(d.expiration), 25 | Items(d.items), 26 | ) 27 | } else { 28 | opts = NewOptions() 29 | } 30 | 31 | // test options 32 | for _, o := range []Options{opts} { 33 | if o.Expiration != d.expiration { 34 | t.Fatalf("Expected expiration '%v', got '%v'", d.expiration, o.Expiration) 35 | } 36 | 37 | if o.Items["test"] != d.items["test"] { 38 | t.Fatalf("Expected items %#v, got %#v", d.items, o.Items) 39 | } 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cache/redis/options.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | 6 | rclient "github.com/go-redis/redis/v8" 7 | "go-micro.dev/v5/cache" 8 | ) 9 | 10 | type redisOptionsContextKey struct{} 11 | 12 | // WithRedisOptions sets advanced options for redis. 13 | func WithRedisOptions(options rclient.UniversalOptions) cache.Option { 14 | return func(o *cache.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | 19 | o.Context = context.WithValue(o.Context, redisOptionsContextKey{}, options) 20 | } 21 | } 22 | 23 | func newUniversalClient(options cache.Options) rclient.UniversalClient { 24 | if options.Context == nil { 25 | options.Context = context.Background() 26 | } 27 | 28 | opts, ok := options.Context.Value(redisOptionsContextKey{}).(rclient.UniversalOptions) 29 | if !ok { 30 | addr := "redis://127.0.0.1:6379" 31 | if len(options.Address) > 0 { 32 | addr = options.Address 33 | } 34 | 35 | redisOptions, err := rclient.ParseURL(addr) 36 | if err != nil { 37 | redisOptions = &rclient.Options{Addr: addr} 38 | } 39 | 40 | return rclient.NewClient(redisOptions) 41 | } 42 | 43 | if len(opts.Addrs) == 0 && len(options.Address) > 0 { 44 | opts.Addrs = []string{options.Address} 45 | } 46 | 47 | return rclient.NewUniversalClient(&opts) 48 | } 49 | -------------------------------------------------------------------------------- /cache/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | rclient "github.com/go-redis/redis/v8" 8 | "go-micro.dev/v5/cache" 9 | ) 10 | 11 | // NewRedisCache returns a new redis cache. 12 | func NewRedisCache(opts ...cache.Option) cache.Cache { 13 | options := cache.NewOptions(opts...) 14 | return &redisCache{ 15 | opts: options, 16 | client: newUniversalClient(options), 17 | } 18 | } 19 | 20 | type redisCache struct { 21 | opts cache.Options 22 | client rclient.UniversalClient 23 | } 24 | 25 | func (c *redisCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) { 26 | val, err := c.client.Get(ctx, key).Bytes() 27 | if err != nil && err == rclient.Nil { 28 | return nil, time.Time{}, cache.ErrKeyNotFound 29 | } else if err != nil { 30 | return nil, time.Time{}, err 31 | } 32 | 33 | dur, err := c.client.TTL(ctx, key).Result() 34 | if err != nil { 35 | return nil, time.Time{}, err 36 | } 37 | if dur == -1 { 38 | return val, time.Unix(1<<63-1, 0), nil 39 | } 40 | if dur == -2 { 41 | return val, time.Time{}, cache.ErrItemExpired 42 | } 43 | 44 | return val, time.Now().Add(dur), nil 45 | } 46 | 47 | func (c *redisCache) Put(ctx context.Context, key string, val interface{}, dur time.Duration) error { 48 | return c.client.Set(ctx, key, val, dur).Err() 49 | } 50 | 51 | func (c *redisCache) Delete(ctx context.Context, key string) error { 52 | return c.client.Del(ctx, key).Err() 53 | } 54 | 55 | func (m *redisCache) String() string { 56 | return "redis" 57 | } 58 | -------------------------------------------------------------------------------- /client/backoff.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "go-micro.dev/v5/util/backoff" 8 | ) 9 | 10 | type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) 11 | 12 | func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { 13 | return backoff.Do(attempts), nil 14 | } 15 | -------------------------------------------------------------------------------- /client/backoff_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestBackoff(t *testing.T) { 10 | results := []time.Duration{ 11 | 0 * time.Second, 12 | 100 * time.Millisecond, 13 | 600 * time.Millisecond, 14 | 1900 * time.Millisecond, 15 | 4300 * time.Millisecond, 16 | 7900 * time.Millisecond, 17 | } 18 | 19 | c := NewClient() 20 | 21 | for i := 0; i < 5; i++ { 22 | d, err := exponentialBackoff(context.TODO(), c.NewRequest("test", "test", nil), i) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | if d != results[i] { 28 | t.Fatalf("Expected equal than %v, got %v", results[i], d) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/cache.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "hash/fnv" 8 | "time" 9 | 10 | cache "github.com/patrickmn/go-cache" 11 | 12 | "go-micro.dev/v5/metadata" 13 | "go-micro.dev/v5/transport/headers" 14 | ) 15 | 16 | // NewCache returns an initialized cache. 17 | func NewCache() *Cache { 18 | return &Cache{ 19 | cache: cache.New(cache.NoExpiration, 30*time.Second), 20 | } 21 | } 22 | 23 | // Cache for responses. 24 | type Cache struct { 25 | cache *cache.Cache 26 | } 27 | 28 | // Get a response from the cache. 29 | func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) { 30 | return c.cache.Get(key(ctx, req)) 31 | } 32 | 33 | // Set a response in the cache. 34 | func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) { 35 | c.cache.Set(key(ctx, req), rsp, expiry) 36 | } 37 | 38 | // List the key value pairs in the cache. 39 | func (c *Cache) List() map[string]string { 40 | items := c.cache.Items() 41 | 42 | rsp := make(map[string]string, len(items)) 43 | 44 | for k, v := range items { 45 | bytes, _ := json.Marshal(v.Object) 46 | rsp[k] = string(bytes) 47 | } 48 | 49 | return rsp 50 | } 51 | 52 | // key returns a hash for the context and request. 53 | func key(ctx context.Context, req *Request) string { 54 | ns, _ := metadata.Get(ctx, headers.Namespace) 55 | 56 | bytes, _ := json.Marshal(map[string]interface{}{ 57 | "namespace": ns, 58 | "request": map[string]interface{}{ 59 | "service": (*req).Service(), 60 | "endpoint": (*req).Endpoint(), 61 | "method": (*req).Method(), 62 | "body": (*req).Body(), 63 | }, 64 | }) 65 | 66 | h := fnv.New64() 67 | h.Write(bytes) 68 | 69 | return fmt.Sprintf("%x", h.Sum(nil)) 70 | } 71 | -------------------------------------------------------------------------------- /client/common_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | ) 6 | 7 | var ( 8 | // mock data. 9 | testData = map[string][]*registry.Service{ 10 | "foo": { 11 | { 12 | Name: "foo", 13 | Version: "1.0.0", 14 | Nodes: []*registry.Node{ 15 | { 16 | Id: "foo-1.0.0-123", 17 | Address: "localhost:9999", 18 | Metadata: map[string]string{ 19 | "protocol": "mucp", 20 | }, 21 | }, 22 | { 23 | Id: "foo-1.0.0-321", 24 | Address: "localhost:9999", 25 | Metadata: map[string]string{ 26 | "protocol": "mucp", 27 | }, 28 | }, 29 | }, 30 | }, 31 | { 32 | Name: "foo", 33 | Version: "1.0.1", 34 | Nodes: []*registry.Node{ 35 | { 36 | Id: "foo-1.0.1-321", 37 | Address: "localhost:6666", 38 | Metadata: map[string]string{ 39 | "protocol": "mucp", 40 | }, 41 | }, 42 | }, 43 | }, 44 | { 45 | Name: "foo", 46 | Version: "1.0.3", 47 | Nodes: []*registry.Node{ 48 | { 49 | Id: "foo-1.0.3-345", 50 | Address: "localhost:8888", 51 | Metadata: map[string]string{ 52 | "protocol": "mucp", 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | } 59 | ) 60 | -------------------------------------------------------------------------------- /client/context.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type clientKey struct{} 8 | 9 | func FromContext(ctx context.Context) (Client, bool) { 10 | c, ok := ctx.Value(clientKey{}).(Client) 11 | return c, ok 12 | } 13 | 14 | func NewContext(ctx context.Context, c Client) context.Context { 15 | return context.WithValue(ctx, clientKey{}, c) 16 | } 17 | -------------------------------------------------------------------------------- /client/grpc/error.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-micro.dev/v5/errors" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func microError(err error) error { 12 | // no error 13 | switch err { 14 | case nil: 15 | return nil 16 | } 17 | 18 | if verr, ok := err.(*errors.Error); ok { 19 | return verr 20 | } 21 | 22 | // grpc error 23 | s, ok := status.FromError(err) 24 | if !ok { 25 | return err 26 | } 27 | 28 | // return first error from details 29 | if details := s.Details(); len(details) > 0 { 30 | return microError(details[0].(error)) 31 | } 32 | 33 | // try to decode micro *errors.Error 34 | if e := errors.Parse(s.Message()); e.Code > 0 { 35 | return e // actually a micro error 36 | } 37 | 38 | // fallback 39 | return errors.New("go.micro.client", s.Message(), microStatusFromGrpcCode(s.Code())) 40 | } 41 | 42 | func microStatusFromGrpcCode(code codes.Code) int32 { 43 | switch code { 44 | case codes.OK: 45 | return http.StatusOK 46 | case codes.InvalidArgument: 47 | return http.StatusBadRequest 48 | case codes.DeadlineExceeded: 49 | return http.StatusRequestTimeout 50 | case codes.NotFound: 51 | return http.StatusNotFound 52 | case codes.AlreadyExists: 53 | return http.StatusConflict 54 | case codes.PermissionDenied: 55 | return http.StatusForbidden 56 | case codes.Unauthenticated: 57 | return http.StatusUnauthorized 58 | case codes.FailedPrecondition: 59 | return http.StatusPreconditionFailed 60 | case codes.Unimplemented: 61 | return http.StatusNotImplemented 62 | case codes.Internal: 63 | return http.StatusInternalServerError 64 | case codes.Unavailable: 65 | return http.StatusServiceUnavailable 66 | } 67 | 68 | return http.StatusInternalServerError 69 | } 70 | -------------------------------------------------------------------------------- /client/grpc/grpc_pool_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | pb "google.golang.org/grpc/examples/helloworld/helloworld" 11 | ) 12 | 13 | func testPool(t *testing.T, size int, ttl time.Duration, idle int, ms int) { 14 | // setup server 15 | l, err := net.Listen("tcp", ":0") 16 | if err != nil { 17 | t.Errorf("failed to listen: %v", err) 18 | } 19 | defer l.Close() 20 | 21 | s := grpc.NewServer() 22 | pb.RegisterGreeterServer(s, &greeterServer{}) 23 | 24 | go s.Serve(l) 25 | defer s.Stop() 26 | 27 | // zero pool 28 | p := newPool(size, ttl, idle, ms) 29 | 30 | for i := 0; i < 10; i++ { 31 | // get a conn 32 | cc, err := p.getConn(context.TODO(), l.Addr().String(), grpc.WithInsecure()) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | rsp := pb.HelloReply{} 38 | 39 | err = cc.Invoke(context.TODO(), "/helloworld.Greeter/SayHello", &pb.HelloRequest{Name: "John"}, &rsp) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | if rsp.Message != "Hello John" { 45 | t.Errorf("Got unexpected response %v", rsp.Message) 46 | } 47 | 48 | // release the conn 49 | p.release(l.Addr().String(), cc, nil) 50 | 51 | p.Lock() 52 | if i := p.conns[l.Addr().String()].count; i > size { 53 | p.Unlock() 54 | t.Errorf("pool size %d is greater than expected %d", i, size) 55 | } 56 | p.Unlock() 57 | } 58 | } 59 | 60 | func TestGRPCPool(t *testing.T) { 61 | testPool(t, 0, time.Minute, 10, 2) 62 | testPool(t, 2, time.Minute, 10, 1) 63 | } 64 | -------------------------------------------------------------------------------- /client/grpc/message.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "go-micro.dev/v5/client" 5 | ) 6 | 7 | type grpcEvent struct { 8 | topic string 9 | contentType string 10 | payload interface{} 11 | } 12 | 13 | func newGRPCEvent(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { 14 | var options client.MessageOptions 15 | for _, o := range opts { 16 | o(&options) 17 | } 18 | 19 | if len(options.ContentType) > 0 { 20 | contentType = options.ContentType 21 | } 22 | 23 | return &grpcEvent{ 24 | payload: payload, 25 | topic: topic, 26 | contentType: contentType, 27 | } 28 | } 29 | 30 | func (g *grpcEvent) ContentType() string { 31 | return g.contentType 32 | } 33 | 34 | func (g *grpcEvent) Topic() string { 35 | return g.topic 36 | } 37 | 38 | func (g *grpcEvent) Payload() interface{} { 39 | return g.payload 40 | } 41 | -------------------------------------------------------------------------------- /client/grpc/request_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMethodToGRPC(t *testing.T) { 8 | testData := []struct { 9 | service string 10 | method string 11 | expect string 12 | }{ 13 | { 14 | "helloworld", 15 | "Greeter.SayHello", 16 | "/helloworld.Greeter/SayHello", 17 | }, 18 | { 19 | "helloworld", 20 | "/helloworld.Greeter/SayHello", 21 | "/helloworld.Greeter/SayHello", 22 | }, 23 | { 24 | "", 25 | "/helloworld.Greeter/SayHello", 26 | "/helloworld.Greeter/SayHello", 27 | }, 28 | { 29 | "", 30 | "Greeter.SayHello", 31 | "/Greeter/SayHello", 32 | }, 33 | } 34 | 35 | for _, d := range testData { 36 | method := methodToGRPC(d.service, d.method) 37 | if method != d.expect { 38 | t.Fatalf("expected %s got %s", d.expect, method) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/grpc/response.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "strings" 5 | 6 | "go-micro.dev/v5/codec" 7 | "go-micro.dev/v5/codec/bytes" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/encoding" 10 | ) 11 | 12 | type response struct { 13 | conn *grpc.ClientConn 14 | stream grpc.ClientStream 15 | codec encoding.Codec 16 | gcodec codec.Codec 17 | } 18 | 19 | // Read the response. 20 | func (r *response) Codec() codec.Reader { 21 | return r.gcodec 22 | } 23 | 24 | // read the header. 25 | func (r *response) Header() map[string]string { 26 | md, err := r.stream.Header() 27 | if err != nil { 28 | return map[string]string{} 29 | } 30 | hdr := make(map[string]string, len(md)) 31 | for k, v := range md { 32 | hdr[k] = strings.Join(v, ",") 33 | } 34 | return hdr 35 | } 36 | 37 | // Read the undecoded response. 38 | func (r *response) Read() ([]byte, error) { 39 | f := &bytes.Frame{} 40 | if err := r.gcodec.ReadBody(f); err != nil { 41 | return nil, err 42 | } 43 | return f.Data, nil 44 | } 45 | -------------------------------------------------------------------------------- /client/grpc/stream.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "sync" 7 | 8 | "go-micro.dev/v5/client" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // Implements the streamer interface. 13 | type grpcStream struct { 14 | sync.RWMutex 15 | closed bool 16 | err error 17 | stream grpc.ClientStream 18 | request client.Request 19 | response client.Response 20 | context context.Context 21 | cancel func() 22 | release func(error) 23 | } 24 | 25 | func (g *grpcStream) Context() context.Context { 26 | return g.context 27 | } 28 | 29 | func (g *grpcStream) Request() client.Request { 30 | return g.request 31 | } 32 | 33 | func (g *grpcStream) Response() client.Response { 34 | return g.response 35 | } 36 | 37 | func (g *grpcStream) Send(msg interface{}) error { 38 | if err := g.stream.SendMsg(msg); err != nil { 39 | g.setError(err) 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | func (g *grpcStream) Recv(msg interface{}) (err error) { 46 | if err = g.stream.RecvMsg(msg); err != nil { 47 | if err != io.EOF { 48 | g.setError(err) 49 | } 50 | return err 51 | } 52 | return 53 | } 54 | 55 | func (g *grpcStream) Error() error { 56 | g.RLock() 57 | defer g.RUnlock() 58 | return g.err 59 | } 60 | 61 | func (g *grpcStream) setError(e error) { 62 | g.Lock() 63 | g.err = e 64 | g.Unlock() 65 | } 66 | 67 | func (g *grpcStream) CloseSend() error { 68 | return g.stream.CloseSend() 69 | } 70 | 71 | func (g *grpcStream) Close() error { 72 | g.Lock() 73 | defer g.Unlock() 74 | 75 | if g.closed { 76 | return nil 77 | } 78 | // cancel the context 79 | g.cancel() 80 | g.closed = true 81 | // release back to pool 82 | g.release(g.err) 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /client/retry.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/errors" 7 | ) 8 | 9 | // note that returning either false or a non-nil error will result in the call not being retried. 10 | type RetryFunc func(ctx context.Context, req Request, retryCount int, err error) (bool, error) 11 | 12 | // RetryAlways always retry on error. 13 | func RetryAlways(ctx context.Context, req Request, retryCount int, err error) (bool, error) { 14 | return true, nil 15 | } 16 | 17 | // RetryOnError retries a request on a 500 or timeout error. 18 | func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) { 19 | if err == nil { 20 | return false, nil 21 | } 22 | 23 | e := errors.Parse(err.Error()) 24 | if e == nil { 25 | return false, nil 26 | } 27 | 28 | switch e.Code { 29 | // Retry on timeout, not on 500 internal server error, as that is a business 30 | // logic error that should be handled by the user. 31 | case 408: 32 | return true, nil 33 | default: 34 | return false, nil 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/rpc_message.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type message struct { 4 | payload interface{} 5 | topic string 6 | contentType string 7 | } 8 | 9 | func newMessage(topic string, payload interface{}, contentType string, opts ...MessageOption) Message { 10 | var options MessageOptions 11 | for _, o := range opts { 12 | o(&options) 13 | } 14 | 15 | if len(options.ContentType) > 0 { 16 | contentType = options.ContentType 17 | } 18 | 19 | return &message{ 20 | payload: payload, 21 | topic: topic, 22 | contentType: contentType, 23 | } 24 | } 25 | 26 | func (m *message) ContentType() string { 27 | return m.contentType 28 | } 29 | 30 | func (m *message) Topic() string { 31 | return m.topic 32 | } 33 | 34 | func (m *message) Payload() interface{} { 35 | return m.payload 36 | } 37 | -------------------------------------------------------------------------------- /client/rpc_request.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | ) 6 | 7 | type rpcRequest struct { 8 | opts RequestOptions 9 | codec codec.Codec 10 | body interface{} 11 | service string 12 | method string 13 | endpoint string 14 | contentType string 15 | } 16 | 17 | func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request { 18 | var opts RequestOptions 19 | 20 | for _, o := range reqOpts { 21 | o(&opts) 22 | } 23 | 24 | // set the content-type specified 25 | if len(opts.ContentType) > 0 { 26 | contentType = opts.ContentType 27 | } 28 | 29 | return &rpcRequest{ 30 | service: service, 31 | method: endpoint, 32 | endpoint: endpoint, 33 | body: request, 34 | contentType: contentType, 35 | opts: opts, 36 | } 37 | } 38 | 39 | func (r *rpcRequest) ContentType() string { 40 | return r.contentType 41 | } 42 | 43 | func (r *rpcRequest) Service() string { 44 | return r.service 45 | } 46 | 47 | func (r *rpcRequest) Method() string { 48 | return r.method 49 | } 50 | 51 | func (r *rpcRequest) Endpoint() string { 52 | return r.endpoint 53 | } 54 | 55 | func (r *rpcRequest) Body() interface{} { 56 | return r.body 57 | } 58 | 59 | func (r *rpcRequest) Codec() codec.Writer { 60 | return r.codec 61 | } 62 | 63 | func (r *rpcRequest) Stream() bool { 64 | return r.opts.Stream 65 | } 66 | -------------------------------------------------------------------------------- /client/rpc_request_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRequestOptions(t *testing.T) { 8 | r := newRequest("service", "endpoint", nil, "application/json") 9 | if r.Service() != "service" { 10 | t.Fatalf("expected 'service' got %s", r.Service()) 11 | } 12 | if r.Endpoint() != "endpoint" { 13 | t.Fatalf("expected 'endpoint' got %s", r.Endpoint()) 14 | } 15 | if r.ContentType() != "application/json" { 16 | t.Fatalf("expected 'endpoint' got %s", r.ContentType()) 17 | } 18 | 19 | r2 := newRequest("service", "endpoint", nil, "application/json", WithContentType("application/protobuf")) 20 | if r2.ContentType() != "application/protobuf" { 21 | t.Fatalf("expected 'endpoint' got %s", r2.ContentType()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/rpc_response.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | "go-micro.dev/v5/transport" 6 | ) 7 | 8 | type rpcResponse struct { 9 | socket transport.Socket 10 | codec codec.Codec 11 | header map[string]string 12 | body []byte 13 | } 14 | 15 | func (r *rpcResponse) Codec() codec.Reader { 16 | return r.codec 17 | } 18 | 19 | func (r *rpcResponse) Header() map[string]string { 20 | return r.header 21 | } 22 | 23 | func (r *rpcResponse) Read() ([]byte, error) { 24 | var msg transport.Message 25 | 26 | if err := r.socket.Recv(&msg); err != nil { 27 | return nil, err 28 | } 29 | 30 | // set internals 31 | r.header = msg.Header 32 | r.body = msg.Body 33 | 34 | return msg.Body, nil 35 | } 36 | -------------------------------------------------------------------------------- /client/wrapper.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/registry" 7 | ) 8 | 9 | // CallFunc represents the individual call func. 10 | type CallFunc func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error 11 | 12 | // CallWrapper is a low level wrapper for the CallFunc. 13 | type CallWrapper func(CallFunc) CallFunc 14 | 15 | // Wrapper wraps a client and returns a client. 16 | type Wrapper func(Client) Client 17 | 18 | // StreamWrapper wraps a Stream and returns the equivalent. 19 | type StreamWrapper func(Stream) Stream 20 | -------------------------------------------------------------------------------- /codec/bytes/bytes.go: -------------------------------------------------------------------------------- 1 | // Package bytes provides a bytes codec which does not encode or decode anything 2 | package bytes 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | type Codec struct { 12 | Conn io.ReadWriteCloser 13 | } 14 | 15 | // Frame gives us the ability to define raw data to send over the pipes. 16 | type Frame struct { 17 | Data []byte 18 | } 19 | 20 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 21 | return nil 22 | } 23 | 24 | func (c *Codec) ReadBody(b interface{}) error { 25 | // read bytes 26 | buf, err := io.ReadAll(c.Conn) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | switch v := b.(type) { 32 | case *[]byte: 33 | *v = buf 34 | case *Frame: 35 | v.Data = buf 36 | default: 37 | return fmt.Errorf("failed to read body: %v is not type of *[]byte", b) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 44 | var v []byte 45 | switch vb := b.(type) { 46 | case *Frame: 47 | v = vb.Data 48 | case *[]byte: 49 | v = *vb 50 | case []byte: 51 | v = vb 52 | default: 53 | return fmt.Errorf("failed to write: %v is not type of *[]byte or []byte", b) 54 | } 55 | _, err := c.Conn.Write(v) 56 | return err 57 | } 58 | 59 | func (c *Codec) Close() error { 60 | return c.Conn.Close() 61 | } 62 | 63 | func (c *Codec) String() string { 64 | return "bytes" 65 | } 66 | 67 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 68 | return &Codec{ 69 | Conn: c, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /codec/bytes/marshaler.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | ) 6 | 7 | type Marshaler struct{} 8 | 9 | type Message struct { 10 | Header map[string]string 11 | Body []byte 12 | } 13 | 14 | func (n Marshaler) Marshal(v interface{}) ([]byte, error) { 15 | switch ve := v.(type) { 16 | case *[]byte: 17 | return *ve, nil 18 | case []byte: 19 | return ve, nil 20 | case *Message: 21 | return ve.Body, nil 22 | } 23 | return nil, codec.ErrInvalidMessage 24 | } 25 | 26 | func (n Marshaler) Unmarshal(d []byte, v interface{}) error { 27 | switch ve := v.(type) { 28 | case *[]byte: 29 | *ve = d 30 | return nil 31 | case *Message: 32 | ve.Body = d 33 | return nil 34 | } 35 | return codec.ErrInvalidMessage 36 | } 37 | 38 | func (n Marshaler) String() string { 39 | return "bytes" 40 | } 41 | -------------------------------------------------------------------------------- /codec/codec.go: -------------------------------------------------------------------------------- 1 | // Package codec is an interface for encoding messages 2 | package codec 3 | 4 | import ( 5 | "errors" 6 | "io" 7 | ) 8 | 9 | const ( 10 | Error MessageType = iota 11 | Request 12 | Response 13 | Event 14 | ) 15 | 16 | var ( 17 | ErrInvalidMessage = errors.New("invalid message") 18 | ) 19 | 20 | type MessageType int 21 | 22 | // Takes in a connection/buffer and returns a new Codec. 23 | type NewCodec func(io.ReadWriteCloser) Codec 24 | 25 | // Codec encodes/decodes various types of messages used within go-micro. 26 | // ReadHeader and ReadBody are called in pairs to read requests/responses 27 | // from the connection. Close is called when finished with the 28 | // connection. ReadBody may be called with a nil argument to force the 29 | // body to be read and discarded. 30 | type Codec interface { 31 | Reader 32 | Writer 33 | Close() error 34 | String() string 35 | } 36 | 37 | type Reader interface { 38 | ReadHeader(*Message, MessageType) error 39 | ReadBody(interface{}) error 40 | } 41 | 42 | type Writer interface { 43 | Write(*Message, interface{}) error 44 | } 45 | 46 | // Marshaler is a simple encoding interface used for the broker/transport 47 | // where headers are not supported by the underlying implementation. 48 | type Marshaler interface { 49 | Marshal(interface{}) ([]byte, error) 50 | Unmarshal([]byte, interface{}) error 51 | String() string 52 | } 53 | 54 | // Message represents detailed information about 55 | // the communication, likely followed by the body. 56 | // In the case of an error, body may be nil. 57 | type Message struct { 58 | 59 | // The values read from the socket 60 | Header map[string]string 61 | Id string 62 | Target string 63 | Method string 64 | Endpoint string 65 | Error string 66 | 67 | Body []byte 68 | Type MessageType 69 | } 70 | -------------------------------------------------------------------------------- /codec/grpc/util.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | var ( 10 | MaxMessageSize = 1024 * 1024 * 4 // 4Mb 11 | maxInt = int(^uint(0) >> 1) 12 | ) 13 | 14 | func decode(r io.Reader) (uint8, []byte, error) { 15 | header := make([]byte, 5) 16 | 17 | // read the header 18 | if _, err := r.Read(header); err != nil { 19 | return uint8(0), nil, err 20 | } 21 | 22 | // get encoding format e.g compressed 23 | cf := uint8(header[0]) 24 | 25 | // get message length 26 | length := binary.BigEndian.Uint32(header[1:]) 27 | 28 | // no encoding format 29 | if length == 0 { 30 | return cf, nil, nil 31 | } 32 | 33 | // 34 | if int64(length) > int64(maxInt) { 35 | return cf, nil, fmt.Errorf("grpc: received message larger than max length allowed on current machine (%d vs. %d)", length, maxInt) 36 | } 37 | if int(length) > MaxMessageSize { 38 | return cf, nil, fmt.Errorf("grpc: received message larger than max (%d vs. %d)", length, MaxMessageSize) 39 | } 40 | 41 | msg := make([]byte, int(length)) 42 | 43 | if _, err := r.Read(msg); err != nil { 44 | if err == io.EOF { 45 | err = io.ErrUnexpectedEOF 46 | } 47 | return cf, nil, err 48 | } 49 | 50 | return cf, msg, nil 51 | } 52 | 53 | func encode(cf uint8, buf []byte, w io.Writer) error { 54 | header := make([]byte, 5) 55 | 56 | // set compression 57 | header[0] = byte(cf) 58 | 59 | // write length as header 60 | binary.BigEndian.PutUint32(header[1:], uint32(len(buf))) 61 | 62 | // read the header 63 | if _, err := w.Write(header); err != nil { 64 | return err 65 | } 66 | 67 | // write the buffer 68 | _, err := w.Write(buf) 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /codec/json/json.go: -------------------------------------------------------------------------------- 1 | // Package json provides a json codec 2 | package json 3 | 4 | import ( 5 | "encoding/json" 6 | "io" 7 | 8 | "github.com/golang/protobuf/jsonpb" 9 | "github.com/golang/protobuf/proto" 10 | "go-micro.dev/v5/codec" 11 | ) 12 | 13 | type Codec struct { 14 | Conn io.ReadWriteCloser 15 | Encoder *json.Encoder 16 | Decoder *json.Decoder 17 | } 18 | 19 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 20 | return nil 21 | } 22 | 23 | func (c *Codec) ReadBody(b interface{}) error { 24 | if b == nil { 25 | return nil 26 | } 27 | if pb, ok := b.(proto.Message); ok { 28 | return jsonpb.UnmarshalNext(c.Decoder, pb) 29 | } 30 | return c.Decoder.Decode(b) 31 | } 32 | 33 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 34 | if b == nil { 35 | return nil 36 | } 37 | return c.Encoder.Encode(b) 38 | } 39 | 40 | func (c *Codec) Close() error { 41 | return c.Conn.Close() 42 | } 43 | 44 | func (c *Codec) String() string { 45 | return "json" 46 | } 47 | 48 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 49 | return &Codec{ 50 | Conn: c, 51 | Decoder: json.NewDecoder(c), 52 | Encoder: json.NewEncoder(c), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /codec/json/marshaler.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/golang/protobuf/jsonpb" 8 | "github.com/golang/protobuf/proto" 9 | "github.com/oxtoacart/bpool" 10 | ) 11 | 12 | var jsonpbMarshaler = &jsonpb.Marshaler{} 13 | 14 | // create buffer pool with 16 instances each preallocated with 256 bytes. 15 | var bufferPool = bpool.NewSizedBufferPool(16, 256) 16 | 17 | type Marshaler struct{} 18 | 19 | func (j Marshaler) Marshal(v interface{}) ([]byte, error) { 20 | if pb, ok := v.(proto.Message); ok { 21 | buf := bufferPool.Get() 22 | defer bufferPool.Put(buf) 23 | if err := jsonpbMarshaler.Marshal(buf, pb); err != nil { 24 | return nil, err 25 | } 26 | return buf.Bytes(), nil 27 | } 28 | return json.Marshal(v) 29 | } 30 | 31 | func (j Marshaler) Unmarshal(d []byte, v interface{}) error { 32 | if pb, ok := v.(proto.Message); ok { 33 | return jsonpb.Unmarshal(bytes.NewReader(d), pb) 34 | } 35 | return json.Unmarshal(d, v) 36 | } 37 | 38 | func (j Marshaler) String() string { 39 | return "json" 40 | } 41 | -------------------------------------------------------------------------------- /codec/proto/marshaler.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/golang/protobuf/proto" 7 | "github.com/oxtoacart/bpool" 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | // create buffer pool with 16 instances each preallocated with 256 bytes. 12 | var bufferPool = bpool.NewSizedBufferPool(16, 256) 13 | 14 | type Marshaler struct{} 15 | 16 | func (Marshaler) Marshal(v interface{}) ([]byte, error) { 17 | pb, ok := v.(proto.Message) 18 | if !ok { 19 | return nil, codec.ErrInvalidMessage 20 | } 21 | 22 | // looks not good, but allows to reuse underlining bytes 23 | buf := bufferPool.Get() 24 | pbuf := proto.NewBuffer(buf.Bytes()) 25 | defer func() { 26 | bufferPool.Put(bytes.NewBuffer(pbuf.Bytes())) 27 | }() 28 | 29 | if err := pbuf.Marshal(pb); err != nil { 30 | return nil, err 31 | } 32 | 33 | return pbuf.Bytes(), nil 34 | } 35 | 36 | func (Marshaler) Unmarshal(data []byte, v interface{}) error { 37 | pb, ok := v.(proto.Message) 38 | if !ok { 39 | return codec.ErrInvalidMessage 40 | } 41 | 42 | return proto.Unmarshal(data, pb) 43 | } 44 | 45 | func (Marshaler) String() string { 46 | return "proto" 47 | } 48 | -------------------------------------------------------------------------------- /codec/proto/message.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | type Message struct { 4 | Data []byte 5 | } 6 | 7 | func (m *Message) MarshalJSON() ([]byte, error) { 8 | return m.Data, nil 9 | } 10 | 11 | func (m *Message) UnmarshalJSON(data []byte) error { 12 | m.Data = data 13 | return nil 14 | } 15 | 16 | func (m *Message) ProtoMessage() {} 17 | 18 | func (m *Message) Reset() { 19 | *m = Message{} 20 | } 21 | 22 | func (m *Message) String() string { 23 | return string(m.Data) 24 | } 25 | 26 | func (m *Message) Marshal() ([]byte, error) { 27 | return m.Data, nil 28 | } 29 | 30 | func (m *Message) Unmarshal(data []byte) error { 31 | m.Data = data 32 | return nil 33 | } 34 | 35 | func NewMessage(data []byte) *Message { 36 | return &Message{data} 37 | } 38 | -------------------------------------------------------------------------------- /codec/proto/proto.go: -------------------------------------------------------------------------------- 1 | // Package proto provides a proto codec 2 | package proto 3 | 4 | import ( 5 | "io" 6 | 7 | "github.com/golang/protobuf/proto" 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | type Codec struct { 12 | Conn io.ReadWriteCloser 13 | } 14 | 15 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 16 | return nil 17 | } 18 | 19 | func (c *Codec) ReadBody(b interface{}) error { 20 | if b == nil { 21 | return nil 22 | } 23 | buf, err := io.ReadAll(c.Conn) 24 | if err != nil { 25 | return err 26 | } 27 | m, ok := b.(proto.Message) 28 | if !ok { 29 | return codec.ErrInvalidMessage 30 | } 31 | return proto.Unmarshal(buf, m) 32 | } 33 | 34 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 35 | if b == nil { 36 | // Nothing to write 37 | return nil 38 | } 39 | p, ok := b.(proto.Message) 40 | if !ok { 41 | return codec.ErrInvalidMessage 42 | } 43 | buf, err := proto.Marshal(p) 44 | if err != nil { 45 | return err 46 | } 47 | _, err = c.Conn.Write(buf) 48 | return err 49 | } 50 | 51 | func (c *Codec) Close() error { 52 | return c.Conn.Close() 53 | } 54 | 55 | func (c *Codec) String() string { 56 | return "proto" 57 | } 58 | 59 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 60 | return &Codec{ 61 | Conn: c, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /codec/protorpc/envelope.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: codec/protorpc/envelope.proto 3 | 4 | package protorpc 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | -------------------------------------------------------------------------------- /codec/protorpc/envelope.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package protorpc; 4 | 5 | message Request { 6 | string service_method = 1; 7 | fixed64 seq = 2; 8 | } 9 | 10 | message Response { 11 | string service_method = 1; 12 | fixed64 seq = 2; 13 | string error = 3; 14 | } 15 | -------------------------------------------------------------------------------- /codec/protorpc/netstring.go: -------------------------------------------------------------------------------- 1 | package protorpc 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // WriteNetString writes data to a big-endian netstring on a Writer. 9 | // Size is always a 32-bit unsigned int. 10 | func WriteNetString(w io.Writer, data []byte) (written int, err error) { 11 | size := make([]byte, 4) 12 | binary.BigEndian.PutUint32(size, uint32(len(data))) 13 | if written, err = w.Write(size); err != nil { 14 | return 15 | } 16 | return w.Write(data) 17 | } 18 | 19 | // ReadNetString reads data from a big-endian netstring. 20 | func ReadNetString(r io.Reader) (data []byte, err error) { 21 | sizeBuf := make([]byte, 4) 22 | _, err = r.Read(sizeBuf) 23 | if err != nil { 24 | return nil, err 25 | } 26 | size := binary.BigEndian.Uint32(sizeBuf) 27 | if size == 0 { 28 | return nil, nil 29 | } 30 | data = make([]byte, size) 31 | _, err = r.Read(data) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /codec/text/text.go: -------------------------------------------------------------------------------- 1 | // Package text reads any text/* content-type 2 | package text 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | type Codec struct { 12 | Conn io.ReadWriteCloser 13 | } 14 | 15 | // Frame gives us the ability to define raw data to send over the pipes. 16 | type Frame struct { 17 | Data []byte 18 | } 19 | 20 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 21 | return nil 22 | } 23 | 24 | func (c *Codec) ReadBody(b interface{}) error { 25 | // read bytes 26 | buf, err := io.ReadAll(c.Conn) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | switch v := b.(type) { 32 | case *string: 33 | *v = string(buf) 34 | case *[]byte: 35 | *v = buf 36 | case *Frame: 37 | v.Data = buf 38 | default: 39 | return fmt.Errorf("failed to read body: %v is not type of *[]byte", b) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 46 | var v []byte 47 | switch ve := b.(type) { 48 | case *Frame: 49 | v = ve.Data 50 | case *[]byte: 51 | v = *ve 52 | case *string: 53 | v = []byte(*ve) 54 | case string: 55 | v = []byte(ve) 56 | case []byte: 57 | v = ve 58 | default: 59 | return fmt.Errorf("failed to write: %v is not type of *[]byte or []byte", b) 60 | } 61 | _, err := c.Conn.Write(v) 62 | return err 63 | } 64 | 65 | func (c *Codec) Close() error { 66 | return c.Conn.Close() 67 | } 68 | 69 | func (c *Codec) String() string { 70 | return "text" 71 | } 72 | 73 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 74 | return &Codec{ 75 | Conn: c, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | # Config [![GoDoc](https://godoc.org/github.com/micro/go-micro/config?status.svg)](https://godoc.org/github.com/micro/go-micro/config) 2 | 3 | Config is a pluggable dynamic config package 4 | 5 | Most config in applications are statically configured or include complex logic to load from multiple sources. 6 | Go Config makes this easy, pluggable and mergeable. You'll never have to deal with config in the same way again. 7 | 8 | ## Features 9 | 10 | - **Dynamic Loading** - Load configuration from multiple source as and when needed. Go Config manages watching config sources 11 | in the background and automatically merges and updates an in memory view. 12 | 13 | - **Pluggable Sources** - Choose from any number of sources to load and merge config. The backend source is abstracted away into 14 | a standard format consumed internally and decoded via encoders. Sources can be env vars, flags, file, etcd, k8s configmap, etc. 15 | 16 | - **Mergeable Config** - If you specify multiple sources of config, regardless of format, they will be merged and presented in 17 | a single view. This massively simplifies priority order loading and changes based on environment. 18 | 19 | - **Observe Changes** - Optionally watch the config for changes to specific values. Hot reload your app using Go Config's watcher. 20 | You don't have to handle ad-hoc hup reloading or whatever else, just keep reading the config and watch for changes if you need 21 | to be notified. 22 | 23 | - **Sane Defaults** - In case config loads badly or is completely wiped away for some unknown reason, you can specify fallback 24 | values when accessing any config values directly. This ensures you'll always be reading some sane default in the event of a problem. 25 | -------------------------------------------------------------------------------- /config/encoder/encoder.go: -------------------------------------------------------------------------------- 1 | // Package encoder handles source encoding formats 2 | package encoder 3 | 4 | type Encoder interface { 5 | Encode(interface{}) ([]byte, error) 6 | Decode([]byte, interface{}) error 7 | String() string 8 | } 9 | -------------------------------------------------------------------------------- /config/encoder/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "go-micro.dev/v5/config/encoder" 7 | ) 8 | 9 | type jsonEncoder struct{} 10 | 11 | func (j jsonEncoder) Encode(v interface{}) ([]byte, error) { 12 | return json.Marshal(v) 13 | } 14 | 15 | func (j jsonEncoder) Decode(d []byte, v interface{}) error { 16 | return json.Unmarshal(d, v) 17 | } 18 | 19 | func (j jsonEncoder) String() string { 20 | return "json" 21 | } 22 | 23 | func NewEncoder() encoder.Encoder { 24 | return jsonEncoder{} 25 | } 26 | -------------------------------------------------------------------------------- /config/loader/loader.go: -------------------------------------------------------------------------------- 1 | // Package loader manages loading from multiple sources 2 | package loader 3 | 4 | import ( 5 | "context" 6 | 7 | "go-micro.dev/v5/config/reader" 8 | "go-micro.dev/v5/config/source" 9 | ) 10 | 11 | // Loader manages loading sources. 12 | type Loader interface { 13 | // Stop the loader 14 | Close() error 15 | // Load the sources 16 | Load(...source.Source) error 17 | // A Snapshot of loaded config 18 | Snapshot() (*Snapshot, error) 19 | // Force sync of sources 20 | Sync() error 21 | // Watch for changes 22 | Watch(...string) (Watcher, error) 23 | // Name of loader 24 | String() string 25 | } 26 | 27 | // Watcher lets you watch sources and returns a merged ChangeSet. 28 | type Watcher interface { 29 | // First call to next may return the current Snapshot 30 | // If you are watching a path then only the data from 31 | // that path is returned. 32 | Next() (*Snapshot, error) 33 | // Stop watching for changes 34 | Stop() error 35 | } 36 | 37 | // Snapshot is a merged ChangeSet. 38 | type Snapshot struct { 39 | // The merged ChangeSet 40 | ChangeSet *source.ChangeSet 41 | // Deterministic and comparable version of the snapshot 42 | Version string 43 | } 44 | 45 | // Options contains all options for a config loader. 46 | type Options struct { 47 | Reader reader.Reader 48 | 49 | // for alternative data 50 | Context context.Context 51 | 52 | Source []source.Source 53 | 54 | WithWatcherDisabled bool 55 | } 56 | 57 | // Option is a helper for a single option. 58 | type Option func(o *Options) 59 | 60 | // Copy snapshot. 61 | func Copy(s *Snapshot) *Snapshot { 62 | cs := *(s.ChangeSet) 63 | 64 | return &Snapshot{ 65 | ChangeSet: &cs, 66 | Version: s.Version, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/loader/memory/options.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "go-micro.dev/v5/config/loader" 5 | "go-micro.dev/v5/config/reader" 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | // WithSource appends a source to list of sources. 10 | func WithSource(s source.Source) loader.Option { 11 | return func(o *loader.Options) { 12 | o.Source = append(o.Source, s) 13 | } 14 | } 15 | 16 | // WithReader sets the config reader. 17 | func WithReader(r reader.Reader) loader.Option { 18 | return func(o *loader.Options) { 19 | o.Reader = r 20 | } 21 | } 22 | 23 | func WithWatcherDisabled() loader.Option { 24 | return func(o *loader.Options) { 25 | o.WithWatcherDisabled = true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/options.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "go-micro.dev/v5/config/loader" 5 | "go-micro.dev/v5/config/reader" 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | // WithLoader sets the loader for manager config. 10 | func WithLoader(l loader.Loader) Option { 11 | return func(o *Options) { 12 | o.Loader = l 13 | } 14 | } 15 | 16 | // WithSource appends a source to list of sources. 17 | func WithSource(s source.Source) Option { 18 | return func(o *Options) { 19 | o.Source = append(o.Source, s) 20 | } 21 | } 22 | 23 | // WithReader sets the config reader. 24 | func WithReader(r reader.Reader) Option { 25 | return func(o *Options) { 26 | o.Reader = r 27 | } 28 | } 29 | 30 | func WithWatcherDisabled() Option { 31 | return func(o *Options) { 32 | o.WithWatcherDisabled = true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /config/reader/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/imdario/mergo" 8 | "go-micro.dev/v5/config/encoder" 9 | "go-micro.dev/v5/config/encoder/json" 10 | "go-micro.dev/v5/config/reader" 11 | "go-micro.dev/v5/config/source" 12 | ) 13 | 14 | type jsonReader struct { 15 | opts reader.Options 16 | json encoder.Encoder 17 | } 18 | 19 | func (j *jsonReader) Merge(changes ...*source.ChangeSet) (*source.ChangeSet, error) { 20 | var merged map[string]interface{} 21 | 22 | for _, m := range changes { 23 | if m == nil { 24 | continue 25 | } 26 | 27 | if len(m.Data) == 0 { 28 | continue 29 | } 30 | 31 | codec, ok := j.opts.Encoding[m.Format] 32 | if !ok { 33 | // fallback 34 | codec = j.json 35 | } 36 | 37 | var data map[string]interface{} 38 | if err := codec.Decode(m.Data, &data); err != nil { 39 | return nil, err 40 | } 41 | if err := mergo.Map(&merged, data, mergo.WithOverride); err != nil { 42 | return nil, err 43 | } 44 | } 45 | 46 | b, err := j.json.Encode(merged) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | cs := &source.ChangeSet{ 52 | Timestamp: time.Now(), 53 | Data: b, 54 | Source: "json", 55 | Format: j.json.String(), 56 | } 57 | cs.Checksum = cs.Sum() 58 | 59 | return cs, nil 60 | } 61 | 62 | func (j *jsonReader) Values(ch *source.ChangeSet) (reader.Values, error) { 63 | if ch == nil { 64 | return nil, errors.New("changeset is nil") 65 | } 66 | if ch.Format != "json" { 67 | return nil, errors.New("unsupported format") 68 | } 69 | return newValues(ch) 70 | } 71 | 72 | func (j *jsonReader) String() string { 73 | return "json" 74 | } 75 | 76 | // NewReader creates a json reader. 77 | func NewReader(opts ...reader.Option) reader.Reader { 78 | options := reader.NewOptions(opts...) 79 | return &jsonReader{ 80 | json: json.NewEncoder(), 81 | opts: options, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /config/reader/json/json_test.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "testing" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | func TestReader(t *testing.T) { 10 | data := []byte(`{"foo": "bar", "baz": {"bar": "cat"}}`) 11 | 12 | testData := []struct { 13 | path []string 14 | value string 15 | }{ 16 | { 17 | []string{"foo"}, 18 | "bar", 19 | }, 20 | { 21 | []string{"baz", "bar"}, 22 | "cat", 23 | }, 24 | } 25 | 26 | r := NewReader() 27 | 28 | c, err := r.Merge(&source.ChangeSet{Data: data}, &source.ChangeSet{}) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | values, err := r.Values(c) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | for _, test := range testData { 39 | if v, err := values.Get(test.path...); err != nil { 40 | t.Fatal(err) 41 | } else if v.String("") != test.value { 42 | t.Fatalf("Expected %s got %s for path %v", test.value, v, test.path) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /config/reader/options.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "go-micro.dev/v5/config/encoder" 5 | "go-micro.dev/v5/config/encoder/json" 6 | ) 7 | 8 | type Options struct { 9 | Encoding map[string]encoder.Encoder 10 | } 11 | 12 | type Option func(o *Options) 13 | 14 | func NewOptions(opts ...Option) Options { 15 | options := Options{ 16 | Encoding: map[string]encoder.Encoder{ 17 | "json": json.NewEncoder(), 18 | }, 19 | } 20 | for _, o := range opts { 21 | o(&options) 22 | } 23 | return options 24 | } 25 | 26 | func WithEncoder(e encoder.Encoder) Option { 27 | return func(o *Options) { 28 | if o.Encoding == nil { 29 | o.Encoding = make(map[string]encoder.Encoder) 30 | } 31 | o.Encoding[e.String()] = e 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /config/reader/preprocessor.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "os" 5 | "regexp" 6 | ) 7 | 8 | func ReplaceEnvVars(raw []byte) ([]byte, error) { 9 | re := regexp.MustCompile(`\$\{([A-Za-z0-9_]+)\}`) 10 | if re.Match(raw) { 11 | dataS := string(raw) 12 | res := re.ReplaceAllStringFunc(dataS, replaceEnvVars) 13 | return []byte(res), nil 14 | } else { 15 | return raw, nil 16 | } 17 | } 18 | 19 | func replaceEnvVars(element string) string { 20 | v := element[2 : len(element)-1] 21 | el := os.Getenv(v) 22 | return el 23 | } 24 | -------------------------------------------------------------------------------- /config/reader/reader.go: -------------------------------------------------------------------------------- 1 | // Package reader parses change sets and provides config values 2 | package reader 3 | 4 | import ( 5 | "time" 6 | 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | // Reader is an interface for merging changesets. 11 | type Reader interface { 12 | Merge(...*source.ChangeSet) (*source.ChangeSet, error) 13 | Values(*source.ChangeSet) (Values, error) 14 | String() string 15 | } 16 | 17 | // Values is returned by the reader. 18 | type Values interface { 19 | Bytes() []byte 20 | Get(path ...string) (Value, error) 21 | Set(val interface{}, path ...string) 22 | Del(path ...string) 23 | Map() map[string]interface{} 24 | Scan(v interface{}) error 25 | } 26 | 27 | // Value represents a value of any type. 28 | type Value interface { 29 | Bool(def bool) bool 30 | Int(def int) int 31 | String(def string) string 32 | Float64(def float64) float64 33 | Duration(def time.Duration) time.Duration 34 | StringSlice(def []string) []string 35 | StringMap(def map[string]string) map[string]string 36 | Scan(val interface{}) error 37 | Bytes() []byte 38 | } 39 | -------------------------------------------------------------------------------- /config/secrets/secretbox/secretbox_test.go: -------------------------------------------------------------------------------- 1 | package secretbox 2 | 3 | import ( 4 | "encoding/base64" 5 | "reflect" 6 | "testing" 7 | 8 | "go-micro.dev/v5/config/secrets" 9 | ) 10 | 11 | func TestSecretBox(t *testing.T) { 12 | secretKey, err := base64.StdEncoding.DecodeString("4jbVgq8FsAV7vy+n8WqEZrl7BUtNqh3fYT5RXzXOPFY=") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | s := NewSecrets() 18 | 19 | if err := s.Init(); err == nil { 20 | t.Error("Secretbox accepted an empty secret key") 21 | } 22 | if err := s.Init(secrets.Key([]byte("invalid"))); err == nil { 23 | t.Error("Secretbox accepted a secret key that is invalid") 24 | } 25 | 26 | if err := s.Init(secrets.Key(secretKey)); err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | o := s.Options() 31 | if !reflect.DeepEqual(o.Key, secretKey) { 32 | t.Error("Init() didn't set secret key correctly") 33 | } 34 | if s.String() != "nacl-secretbox" { 35 | t.Error(s.String() + " should be nacl-secretbox") 36 | } 37 | 38 | // Try 10 times to get different nonces 39 | for i := 0; i < 10; i++ { 40 | message := []byte(`Can you hear me, Major Tom?`) 41 | 42 | encrypted, err := s.Encrypt(message) 43 | if err != nil { 44 | t.Errorf("Failed to encrypt message (%s)", err) 45 | } 46 | 47 | decrypted, err := s.Decrypt(encrypted) 48 | if err != nil { 49 | t.Errorf("Failed to decrypt encrypted message (%s)", err) 50 | } 51 | 52 | if !reflect.DeepEqual(message, decrypted) { 53 | t.Errorf("Decrypted Message dod not match encrypted message") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/source/changeset.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | ) 7 | 8 | // Sum returns the md5 checksum of the ChangeSet data. 9 | func (c *ChangeSet) Sum() string { 10 | h := md5.New() 11 | h.Write(c.Data) 12 | return fmt.Sprintf("%x", h.Sum(nil)) 13 | } 14 | -------------------------------------------------------------------------------- /config/source/cli/README.md: -------------------------------------------------------------------------------- 1 | # cli Source 2 | 3 | The cli source reads config from parsed flags via a cli.Context. 4 | 5 | ## Format 6 | 7 | We expect the use of the `urfave/cli` package. Upper case flags will be lower cased. Dashes will be used as delimiters for nesting. 8 | 9 | ### Example 10 | 11 | ```go 12 | micro.Flags( 13 | cli.StringFlag{ 14 | Name: "database-address", 15 | Value: "127.0.0.1", 16 | Usage: "the db address", 17 | }, 18 | cli.IntFlag{ 19 | Name: "database-port", 20 | Value: 3306, 21 | Usage: "the db port", 22 | }, 23 | ) 24 | ``` 25 | 26 | Becomes 27 | 28 | ```json 29 | { 30 | "database": { 31 | "address": "127.0.0.1", 32 | "port": 3306 33 | } 34 | } 35 | ``` 36 | 37 | ## New and Load Source 38 | 39 | Because a cli.Context is needed to retrieve the flags and their values, it is recommended to build your source from within a cli.Action. 40 | 41 | ```go 42 | 43 | func main() { 44 | // New Service 45 | service := micro.NewService( 46 | micro.Name("example"), 47 | micro.Flags( 48 | cli.StringFlag{ 49 | Name: "database-address", 50 | Value: "127.0.0.1", 51 | Usage: "the db address", 52 | }, 53 | ), 54 | ) 55 | 56 | var clisrc source.Source 57 | 58 | service.Init( 59 | micro.Action(func(c *cli.Context) { 60 | clisrc = cli.NewSource( 61 | cli.Context(c), 62 | ) 63 | // Alternatively, just setup your config right here 64 | }), 65 | ) 66 | 67 | // ... Load and use that source ... 68 | conf := config.NewConfig() 69 | conf.Load(clisrc) 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /config/source/cli/options.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/urfave/cli/v2" 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | type contextKey struct{} 11 | 12 | // Context sets the cli context. 13 | func Context(c *cli.Context) source.Option { 14 | return func(o *source.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | o.Context = context.WithValue(o.Context, contextKey{}, c) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/source/cli/util.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "strings" 7 | 8 | "github.com/urfave/cli/v2" 9 | ) 10 | 11 | func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { 12 | switch ff.Value.(type) { 13 | case *cli.StringSlice: 14 | default: 15 | set.Set(name, ff.Value.String()) 16 | } 17 | } 18 | 19 | func normalizeFlags(flags []cli.Flag, set *flag.FlagSet) error { 20 | visited := make(map[string]bool) 21 | set.Visit(func(f *flag.Flag) { 22 | visited[f.Name] = true 23 | }) 24 | for _, f := range flags { 25 | parts := f.Names() 26 | if len(parts) == 1 { 27 | continue 28 | } 29 | var ff *flag.Flag 30 | for _, name := range parts { 31 | name = strings.Trim(name, " ") 32 | if visited[name] { 33 | if ff != nil { 34 | return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) 35 | } 36 | ff = set.Lookup(name) 37 | } 38 | } 39 | if ff == nil { 40 | continue 41 | } 42 | for _, name := range parts { 43 | name = strings.Trim(name, " ") 44 | if !visited[name] { 45 | copyFlag(name, ff, set) 46 | } 47 | } 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /config/source/env/README.md: -------------------------------------------------------------------------------- 1 | # Env Source 2 | 3 | The env source reads config from environment variables 4 | 5 | ## Format 6 | 7 | We expect environment variables to be in the standard format of FOO=bar 8 | 9 | Keys are converted to lowercase and split on underscore. 10 | 11 | ### Format example 12 | 13 | ```bash 14 | DATABASE_ADDRESS=127.0.0.1 15 | DATABASE_PORT=3306 16 | ``` 17 | 18 | Becomes 19 | 20 | ```json 21 | { 22 | "database": { 23 | "address": "127.0.0.1", 24 | "port": 3306 25 | } 26 | } 27 | ``` 28 | 29 | ## Prefixes 30 | 31 | Environment variables can be namespaced so we only have access to a subset. Two options are available: 32 | 33 | ```go 34 | WithPrefix(p ...string) 35 | WithStrippedPrefix(p ...string) 36 | ``` 37 | 38 | The former will preserve the prefix and make it a top level key in the config. The latter eliminates the prefix, reducing the nesting by one. 39 | 40 | ### Prefixes example 41 | 42 | Given ENVs of: 43 | 44 | ```bash 45 | APP_DATABASE_ADDRESS=127.0.0.1 46 | APP_DATABASE_PORT=3306 47 | VAULT_ADDR=vault:1337 48 | ``` 49 | 50 | and a source initialized as follows: 51 | 52 | ```go 53 | src := env.NewSource( 54 | env.WithPrefix("VAULT"), 55 | env.WithStrippedPrefix("APP"), 56 | ) 57 | ``` 58 | 59 | The resulting config will be: 60 | 61 | ```json 62 | { 63 | "database": { 64 | "address": "127.0.0.1", 65 | "port": 3306 66 | }, 67 | "vault": { 68 | "addr": "vault:1337" 69 | } 70 | } 71 | ``` 72 | 73 | ## New Source 74 | 75 | Specify source with data 76 | 77 | ```go 78 | src := env.NewSource( 79 | // optionally specify prefix 80 | env.WithPrefix("MICRO"), 81 | ) 82 | ``` 83 | 84 | ## Load Source 85 | 86 | Load the source into config 87 | 88 | ```go 89 | // Create new config 90 | conf := config.NewConfig() 91 | 92 | // Load env source 93 | conf.Load(src) 94 | ``` 95 | -------------------------------------------------------------------------------- /config/source/env/options.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | type strippedPrefixKey struct{} 11 | type prefixKey struct{} 12 | 13 | // WithStrippedPrefix sets the environment variable prefixes to scope to. 14 | // These prefixes will be removed from the actual config entries. 15 | func WithStrippedPrefix(p ...string) source.Option { 16 | return func(o *source.Options) { 17 | if o.Context == nil { 18 | o.Context = context.Background() 19 | } 20 | 21 | o.Context = context.WithValue(o.Context, strippedPrefixKey{}, appendUnderscore(p)) 22 | } 23 | } 24 | 25 | // WithPrefix sets the environment variable prefixes to scope to. 26 | // These prefixes will not be removed. Each prefix will be considered a top level config entry. 27 | func WithPrefix(p ...string) source.Option { 28 | return func(o *source.Options) { 29 | if o.Context == nil { 30 | o.Context = context.Background() 31 | } 32 | o.Context = context.WithValue(o.Context, prefixKey{}, appendUnderscore(p)) 33 | } 34 | } 35 | 36 | func appendUnderscore(prefixes []string) []string { 37 | //nolint:prealloc 38 | var result []string 39 | for _, p := range prefixes { 40 | if !strings.HasSuffix(p, "_") { 41 | result = append(result, p+"_") 42 | continue 43 | } 44 | 45 | result = append(result, p) 46 | } 47 | 48 | return result 49 | } 50 | -------------------------------------------------------------------------------- /config/source/env/watcher.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "go-micro.dev/v5/config/source" 5 | ) 6 | 7 | type watcher struct { 8 | exit chan struct{} 9 | } 10 | 11 | func (w *watcher) Next() (*source.ChangeSet, error) { 12 | <-w.exit 13 | 14 | return nil, source.ErrWatcherStopped 15 | } 16 | 17 | func (w *watcher) Stop() error { 18 | close(w.exit) 19 | return nil 20 | } 21 | 22 | func newWatcher() (source.Watcher, error) { 23 | return &watcher{exit: make(chan struct{})}, nil 24 | } 25 | -------------------------------------------------------------------------------- /config/source/file/README.md: -------------------------------------------------------------------------------- 1 | # File Source 2 | 3 | The file source reads config from a file. 4 | 5 | It uses the File extension to determine the Format e.g `config.yaml` has the yaml format. 6 | It does not make use of encoders or interpet the file data. If a file extension is not present 7 | the source Format will default to the Encoder in options. 8 | 9 | ## Example 10 | 11 | A config file format in json 12 | 13 | ```json 14 | { 15 | "hosts": { 16 | "database": { 17 | "address": "10.0.0.1", 18 | "port": 3306 19 | }, 20 | "cache": { 21 | "address": "10.0.0.2", 22 | "port": 6379 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | ## New Source 29 | 30 | Specify file source with path to file. Path is optional and will default to `config.json` 31 | 32 | ```go 33 | fileSource := file.NewSource( 34 | file.WithPath("/tmp/config.json"), 35 | ) 36 | ``` 37 | 38 | ## File Format 39 | 40 | To load different file formats e.g yaml, toml, xml simply specify them with their extension 41 | 42 | ```go 43 | fileSource := file.NewSource( 44 | file.WithPath("/tmp/config.yaml"), 45 | ) 46 | ``` 47 | 48 | If you want to specify a file without extension, ensure you set the encoder to the same format 49 | 50 | ```go 51 | e := toml.NewEncoder() 52 | 53 | fileSource := file.NewSource( 54 | file.WithPath("/tmp/config"), 55 | source.WithEncoder(e), 56 | ) 57 | ``` 58 | 59 | ## Load Source 60 | 61 | Load the source into config 62 | 63 | ```go 64 | // Create new config 65 | conf := config.NewConfig() 66 | 67 | // Load file source 68 | conf.Load(fileSource) 69 | ``` 70 | -------------------------------------------------------------------------------- /config/source/file/file.go: -------------------------------------------------------------------------------- 1 | // Package file is a file source. Expected format is json 2 | package file 3 | 4 | import ( 5 | "io" 6 | "io/fs" 7 | "os" 8 | 9 | "go-micro.dev/v5/config/source" 10 | ) 11 | 12 | type file struct { 13 | opts source.Options 14 | fs fs.FS 15 | path string 16 | } 17 | 18 | var ( 19 | DefaultPath = "config.json" 20 | ) 21 | 22 | func (f *file) Read() (*source.ChangeSet, error) { 23 | var fh fs.File 24 | var err error 25 | 26 | if f.fs != nil { 27 | fh, err = f.fs.Open(f.path) 28 | } else { 29 | fh, err = os.Open(f.path) 30 | } 31 | 32 | if err != nil { 33 | return nil, err 34 | } 35 | defer fh.Close() 36 | b, err := io.ReadAll(fh) 37 | if err != nil { 38 | return nil, err 39 | } 40 | info, err := fh.Stat() 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | cs := &source.ChangeSet{ 46 | Format: format(f.path, f.opts.Encoder), 47 | Source: f.String(), 48 | Timestamp: info.ModTime(), 49 | Data: b, 50 | } 51 | cs.Checksum = cs.Sum() 52 | 53 | return cs, nil 54 | } 55 | 56 | func (f *file) String() string { 57 | return "file" 58 | } 59 | 60 | func (f *file) Watch() (source.Watcher, error) { 61 | // do not watch if fs.FS instance is provided 62 | if f.fs != nil { 63 | return source.NewNoopWatcher() 64 | } 65 | 66 | if _, err := os.Stat(f.path); err != nil { 67 | return nil, err 68 | } 69 | return newWatcher(f) 70 | } 71 | 72 | func (f *file) Write(cs *source.ChangeSet) error { 73 | return nil 74 | } 75 | 76 | func NewSource(opts ...source.Option) source.Source { 77 | options := source.NewOptions(opts...) 78 | 79 | fs, _ := options.Context.Value(fsKey{}).(fs.FS) 80 | 81 | path := DefaultPath 82 | f, ok := options.Context.Value(filePathKey{}).(string) 83 | if ok { 84 | path = f 85 | } 86 | return &file{opts: options, fs: fs, path: path} 87 | } 88 | -------------------------------------------------------------------------------- /config/source/file/format.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "strings" 5 | 6 | "go-micro.dev/v5/config/encoder" 7 | ) 8 | 9 | func format(p string, e encoder.Encoder) string { 10 | parts := strings.Split(p, ".") 11 | if len(parts) > 1 { 12 | return parts[len(parts)-1] 13 | } 14 | return e.String() 15 | } 16 | -------------------------------------------------------------------------------- /config/source/file/format_test.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "testing" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | func TestFormat(t *testing.T) { 10 | opts := source.NewOptions() 11 | e := opts.Encoder 12 | 13 | testCases := []struct { 14 | p string 15 | f string 16 | }{ 17 | {"/foo/bar.json", "json"}, 18 | {"/foo/bar.yaml", "yaml"}, 19 | {"/foo/bar.xml", "xml"}, 20 | {"/foo/bar.conf.ini", "ini"}, 21 | {"conf", e.String()}, 22 | } 23 | 24 | for _, d := range testCases { 25 | f := format(d.p, e) 26 | if f != d.f { 27 | t.Fatalf("%s: expected %s got %s", d.p, d.f, f) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /config/source/file/options.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "context" 5 | "io/fs" 6 | 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | type filePathKey struct{} 11 | type fsKey struct{} 12 | 13 | // WithPath sets the path to file. 14 | func WithPath(p string) source.Option { 15 | return func(o *source.Options) { 16 | if o.Context == nil { 17 | o.Context = context.Background() 18 | } 19 | o.Context = context.WithValue(o.Context, filePathKey{}, p) 20 | } 21 | } 22 | 23 | // WithFS sets the underlying filesystem to lookup file from (default os.FS). 24 | func WithFS(fs fs.FS) source.Option { 25 | return func(o *source.Options) { 26 | if o.Context == nil { 27 | o.Context = context.Background() 28 | } 29 | o.Context = context.WithValue(o.Context, fsKey{}, fs) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/source/file/watcher.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package file 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "go-micro.dev/v5/config/source" 11 | ) 12 | 13 | type watcher struct { 14 | f *file 15 | 16 | fw *fsnotify.Watcher 17 | } 18 | 19 | func newWatcher(f *file) (source.Watcher, error) { 20 | fw, err := fsnotify.NewWatcher() 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | fw.Add(f.path) 26 | 27 | return &watcher{ 28 | f: f, 29 | fw: fw, 30 | }, nil 31 | } 32 | 33 | func (w *watcher) Next() (*source.ChangeSet, error) { 34 | // try get the event 35 | select { 36 | case event, ok := <-w.fw.Events: 37 | // check if channel was closed (i.e. Watcher.Close() was called). 38 | if !ok { 39 | return nil, source.ErrWatcherStopped 40 | } 41 | 42 | if event.Has(fsnotify.Rename) { 43 | // check existence of file, and add watch again 44 | _, err := os.Stat(event.Name) 45 | if err == nil || os.IsExist(err) { 46 | w.fw.Add(event.Name) 47 | } 48 | } 49 | 50 | c, err := w.f.Read() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return c, nil 56 | case err, ok := <-w.fw.Errors: 57 | // check if channel was closed (i.e. Watcher.Close() was called). 58 | if !ok { 59 | return nil, source.ErrWatcherStopped 60 | } 61 | 62 | return nil, err 63 | } 64 | } 65 | 66 | func (w *watcher) Stop() error { 67 | return w.fw.Close() 68 | } 69 | -------------------------------------------------------------------------------- /config/source/file/watcher_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package file 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "go-micro.dev/v5/config/source" 11 | ) 12 | 13 | type watcher struct { 14 | f *file 15 | 16 | fw *fsnotify.Watcher 17 | } 18 | 19 | func newWatcher(f *file) (source.Watcher, error) { 20 | fw, err := fsnotify.NewWatcher() 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | fw.Add(f.path) 26 | 27 | return &watcher{ 28 | f: f, 29 | fw: fw, 30 | }, nil 31 | } 32 | 33 | func (w *watcher) Next() (*source.ChangeSet, error) { 34 | // try get the event 35 | select { 36 | case event, ok := <-w.fw.Events: 37 | // check if channel was closed (i.e. Watcher.Close() was called). 38 | if !ok { 39 | return nil, source.ErrWatcherStopped 40 | } 41 | 42 | if event.Has(fsnotify.Rename) { 43 | // check existence of file, and add watch again 44 | _, err := os.Stat(event.Name) 45 | if err == nil || os.IsExist(err) { 46 | w.fw.Add(event.Name) 47 | } 48 | } 49 | 50 | c, err := w.f.Read() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | // add path again for the event bug of fsnotify 56 | w.fw.Add(w.f.path) 57 | 58 | return c, nil 59 | case err, ok := <-w.fw.Errors: 60 | // check if channel was closed (i.e. Watcher.Close() was called). 61 | if !ok { 62 | return nil, source.ErrWatcherStopped 63 | } 64 | 65 | return nil, err 66 | } 67 | } 68 | 69 | func (w *watcher) Stop() error { 70 | return w.fw.Close() 71 | } 72 | -------------------------------------------------------------------------------- /config/source/flag/README.md: -------------------------------------------------------------------------------- 1 | # Flag Source 2 | 3 | The flag source reads config from flags 4 | 5 | ## Format 6 | 7 | We expect the use of the `flag` package. Upper case flags will be lower cased. Dashes will be used as delimiters. 8 | 9 | ### Example 10 | 11 | ```go 12 | dbAddress := flag.String("database_address", "127.0.0.1", "the db address") 13 | dbPort := flag.Int("database_port", 3306, "the db port) 14 | ``` 15 | 16 | Becomes 17 | 18 | ```json 19 | { 20 | "database": { 21 | "address": "127.0.0.1", 22 | "port": 3306 23 | } 24 | } 25 | ``` 26 | 27 | ## New Source 28 | 29 | ```go 30 | flagSource := flag.NewSource( 31 | // optionally enable reading of unset flags and their default 32 | // values into config, defaults to false 33 | IncludeUnset(true) 34 | ) 35 | ``` 36 | 37 | ## Load Source 38 | 39 | Load the source into config 40 | 41 | ```go 42 | // Create new config 43 | conf := config.NewConfig() 44 | 45 | // Load flag source 46 | conf.Load(flagSource) 47 | ``` 48 | -------------------------------------------------------------------------------- /config/source/flag/flag_test.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "testing" 7 | ) 8 | 9 | var ( 10 | dbuser = flag.String("database-user", "default", "db user") 11 | dbhost = flag.String("database-host", "", "db host") 12 | dbpw = flag.String("database-password", "", "db pw") 13 | ) 14 | 15 | func initTestFlags() { 16 | flag.Set("database-host", "localhost") 17 | flag.Set("database-password", "some-password") 18 | flag.Parse() 19 | } 20 | 21 | func TestFlagsrc_Read(t *testing.T) { 22 | initTestFlags() 23 | source := NewSource() 24 | c, err := source.Read() 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | 29 | var actual map[string]interface{} 30 | if err := json.Unmarshal(c.Data, &actual); err != nil { 31 | t.Error(err) 32 | } 33 | 34 | actualDB := actual["database"].(map[string]interface{}) 35 | if actualDB["host"] != *dbhost { 36 | t.Errorf("expected %v got %v", *dbhost, actualDB["host"]) 37 | } 38 | 39 | if actualDB["password"] != *dbpw { 40 | t.Errorf("expected %v got %v", *dbpw, actualDB["password"]) 41 | } 42 | 43 | // unset flags should not be loaded 44 | if actualDB["user"] != nil { 45 | t.Errorf("expected %v got %v", nil, actualDB["user"]) 46 | } 47 | } 48 | 49 | func TestFlagsrc_ReadAll(t *testing.T) { 50 | initTestFlags() 51 | source := NewSource(IncludeUnset(true)) 52 | c, err := source.Read() 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | 57 | var actual map[string]interface{} 58 | if err := json.Unmarshal(c.Data, &actual); err != nil { 59 | t.Error(err) 60 | } 61 | 62 | actualDB := actual["database"].(map[string]interface{}) 63 | 64 | // unset flag defaults should be loaded 65 | if actualDB["user"] != *dbuser { 66 | t.Errorf("expected %v got %v", *dbuser, actualDB["user"]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/source/flag/options.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | type includeUnsetKey struct{} 10 | 11 | // IncludeUnset toggles the loading of unset flags and their respective default values. 12 | // Default behavior is to ignore any unset flags. 13 | func IncludeUnset(b bool) source.Option { 14 | return func(o *source.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | o.Context = context.WithValue(o.Context, includeUnsetKey{}, true) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/source/memory/README.md: -------------------------------------------------------------------------------- 1 | # Memory Source 2 | 3 | The memory source provides in-memory data as a source 4 | 5 | ## Memory Format 6 | 7 | The expected data format is json 8 | 9 | ```json 10 | data := []byte(`{ 11 | "hosts": { 12 | "database": { 13 | "address": "10.0.0.1", 14 | "port": 3306 15 | }, 16 | "cache": { 17 | "address": "10.0.0.2", 18 | "port": 6379 19 | } 20 | } 21 | }`) 22 | ``` 23 | 24 | ## New Source 25 | 26 | Specify source with data 27 | 28 | ```go 29 | memorySource := memory.NewSource( 30 | memory.WithJSON(data), 31 | ) 32 | ``` 33 | 34 | ## Load Source 35 | 36 | Load the source into config 37 | 38 | ```go 39 | // Create new config 40 | conf := config.NewConfig() 41 | 42 | // Load memory source 43 | conf.Load(memorySource) 44 | ``` 45 | -------------------------------------------------------------------------------- /config/source/memory/options.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | type changeSetKey struct{} 10 | 11 | func withData(d []byte, f string) source.Option { 12 | return func(o *source.Options) { 13 | if o.Context == nil { 14 | o.Context = context.Background() 15 | } 16 | o.Context = context.WithValue(o.Context, changeSetKey{}, &source.ChangeSet{ 17 | Data: d, 18 | Format: f, 19 | }) 20 | } 21 | } 22 | 23 | // WithChangeSet allows a changeset to be set. 24 | func WithChangeSet(cs *source.ChangeSet) source.Option { 25 | return func(o *source.Options) { 26 | if o.Context == nil { 27 | o.Context = context.Background() 28 | } 29 | o.Context = context.WithValue(o.Context, changeSetKey{}, cs) 30 | } 31 | } 32 | 33 | // WithJSON allows the source data to be set to json. 34 | func WithJSON(d []byte) source.Option { 35 | return withData(d, "json") 36 | } 37 | 38 | // WithYAML allows the source data to be set to yaml. 39 | func WithYAML(d []byte) source.Option { 40 | return withData(d, "yaml") 41 | } 42 | -------------------------------------------------------------------------------- /config/source/memory/watcher.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "go-micro.dev/v5/config/source" 5 | ) 6 | 7 | type watcher struct { 8 | Updates chan *source.ChangeSet 9 | Source *memory 10 | Id string 11 | } 12 | 13 | func (w *watcher) Next() (*source.ChangeSet, error) { 14 | cs := <-w.Updates 15 | return cs, nil 16 | } 17 | 18 | func (w *watcher) Stop() error { 19 | w.Source.Lock() 20 | delete(w.Source.Watchers, w.Id) 21 | w.Source.Unlock() 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /config/source/nats/README.md: -------------------------------------------------------------------------------- 1 | # Nats Source 2 | 3 | The nats source reads config from nats key/values 4 | 5 | ## Nats Format 6 | 7 | The nats source expects keys under the default bucket `default` default key `micro_config` 8 | 9 | Values are expected to be json 10 | 11 | ``` 12 | nats kv put default micro_config '{"nats": {"address": "10.0.0.1", "port": 8488}}' 13 | ``` 14 | 15 | ``` 16 | conf.Get("nats") 17 | ``` 18 | 19 | ## New Source 20 | 21 | Specify source with data 22 | 23 | ```go 24 | natsSource := nats.NewSource( 25 | nats.WithUrl("127.0.0.1:4222"), 26 | nats.WithBucket("my_bucket"), 27 | nats.WithKey("my_key"), 28 | ) 29 | ``` 30 | 31 | ## Load Source 32 | 33 | Load the source into config 34 | 35 | ```go 36 | // Create new config 37 | conf := config.NewConfig() 38 | 39 | // Load nats source 40 | conf.Load(natsSource) 41 | ``` 42 | 43 | ## Watch 44 | 45 | ```go 46 | wh, _ := natsSource.Watch() 47 | 48 | for { 49 | v, err := watcher.Next() 50 | if err != nil { 51 | log.Fatalf("err %v", err) 52 | } 53 | 54 | log.Infof("data %v", string(v.Data)) 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /config/source/nats/options.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | natsgo "github.com/nats-io/nats.go" 8 | "go-micro.dev/v5/config/source" 9 | ) 10 | 11 | type ( 12 | urlKey struct{} 13 | bucketKey struct{} 14 | keyKey struct{} 15 | ) 16 | 17 | // WithUrl sets the nats url. 18 | func WithUrl(a ...string) source.Option { 19 | return func(o *source.Options) { 20 | if o.Context == nil { 21 | o.Context = context.Background() 22 | } 23 | o.Context = context.WithValue(o.Context, urlKey{}, a) 24 | } 25 | } 26 | 27 | // WithBucket sets the nats key. 28 | func WithBucket(a string) source.Option { 29 | return func(o *source.Options) { 30 | if o.Context == nil { 31 | o.Context = context.Background() 32 | } 33 | o.Context = context.WithValue(o.Context, bucketKey{}, a) 34 | } 35 | } 36 | 37 | // WithKey sets the nats key. 38 | func WithKey(a string) source.Option { 39 | return func(o *source.Options) { 40 | if o.Context == nil { 41 | o.Context = context.Background() 42 | } 43 | o.Context = context.WithValue(o.Context, keyKey{}, a) 44 | } 45 | } 46 | 47 | func Client(url string) (natsgo.JetStreamContext, error) { 48 | nc, err := natsgo.Connect(url) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return nc.JetStream(natsgo.MaxWait(10 * time.Second)) 54 | } 55 | -------------------------------------------------------------------------------- /config/source/nats/watcher.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "time" 5 | 6 | natsgo "github.com/nats-io/nats.go" 7 | "go-micro.dev/v5/config/encoder" 8 | "go-micro.dev/v5/config/source" 9 | ) 10 | 11 | type watcher struct { 12 | e encoder.Encoder 13 | name string 14 | bucket string 15 | key string 16 | 17 | ch chan *source.ChangeSet 18 | exit chan bool 19 | } 20 | 21 | func newWatcher(kv natsgo.KeyValue, bucket, key, name string, e encoder.Encoder) (source.Watcher, error) { 22 | w := &watcher{ 23 | e: e, 24 | name: name, 25 | bucket: bucket, 26 | key: key, 27 | ch: make(chan *source.ChangeSet), 28 | exit: make(chan bool), 29 | } 30 | 31 | wh, _ := kv.Watch(key) 32 | 33 | go func() { 34 | for { 35 | select { 36 | case v := <-wh.Updates(): 37 | if v != nil { 38 | w.handle(v.Value()) 39 | } 40 | case <-w.exit: 41 | _ = wh.Stop() 42 | return 43 | } 44 | } 45 | }() 46 | return w, nil 47 | } 48 | 49 | func (w *watcher) handle(data []byte) { 50 | cs := &source.ChangeSet{ 51 | Timestamp: time.Now(), 52 | Format: w.e.String(), 53 | Source: w.name, 54 | Data: data, 55 | } 56 | cs.Checksum = cs.Sum() 57 | 58 | w.ch <- cs 59 | } 60 | 61 | func (w *watcher) Next() (*source.ChangeSet, error) { 62 | select { 63 | case cs := <-w.ch: 64 | return cs, nil 65 | case <-w.exit: 66 | return nil, source.ErrWatcherStopped 67 | } 68 | } 69 | 70 | func (w *watcher) Stop() error { 71 | select { 72 | case <-w.exit: 73 | return nil 74 | default: 75 | close(w.exit) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /config/source/noop.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type noopWatcher struct { 8 | exit chan struct{} 9 | } 10 | 11 | func (w *noopWatcher) Next() (*ChangeSet, error) { 12 | <-w.exit 13 | 14 | return nil, errors.New("noopWatcher stopped") 15 | } 16 | 17 | func (w *noopWatcher) Stop() error { 18 | close(w.exit) 19 | return nil 20 | } 21 | 22 | // NewNoopWatcher returns a watcher that blocks on Next() until Stop() is called. 23 | func NewNoopWatcher() (Watcher, error) { 24 | return &noopWatcher{exit: make(chan struct{})}, nil 25 | } 26 | -------------------------------------------------------------------------------- /config/source/options.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/client" 7 | "go-micro.dev/v5/config/encoder" 8 | "go-micro.dev/v5/config/encoder/json" 9 | ) 10 | 11 | type Options struct { 12 | // Encoder 13 | Encoder encoder.Encoder 14 | 15 | // for alternative data 16 | Context context.Context 17 | 18 | // Client to use for RPC 19 | Client client.Client 20 | } 21 | 22 | type Option func(o *Options) 23 | 24 | func NewOptions(opts ...Option) Options { 25 | options := Options{ 26 | Encoder: json.NewEncoder(), 27 | Context: context.Background(), 28 | Client: client.DefaultClient, 29 | } 30 | 31 | for _, o := range opts { 32 | o(&options) 33 | } 34 | 35 | return options 36 | } 37 | 38 | // WithEncoder sets the source encoder. 39 | func WithEncoder(e encoder.Encoder) Option { 40 | return func(o *Options) { 41 | o.Encoder = e 42 | } 43 | } 44 | 45 | // WithClient sets the source client. 46 | func WithClient(c client.Client) Option { 47 | return func(o *Options) { 48 | o.Client = c 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config/source/source.go: -------------------------------------------------------------------------------- 1 | // Package source is the interface for sources 2 | package source 3 | 4 | import ( 5 | "errors" 6 | "time" 7 | ) 8 | 9 | var ( 10 | // ErrWatcherStopped is returned when source watcher has been stopped. 11 | ErrWatcherStopped = errors.New("watcher stopped") 12 | ) 13 | 14 | // Source is the source from which config is loaded. 15 | type Source interface { 16 | Read() (*ChangeSet, error) 17 | Write(*ChangeSet) error 18 | Watch() (Watcher, error) 19 | String() string 20 | } 21 | 22 | // ChangeSet represents a set of changes from a source. 23 | type ChangeSet struct { 24 | Timestamp time.Time 25 | Checksum string 26 | Format string 27 | Source string 28 | Data []byte 29 | } 30 | 31 | // Watcher watches a source for changes. 32 | type Watcher interface { 33 | Next() (*ChangeSet, error) 34 | Stop() error 35 | } 36 | -------------------------------------------------------------------------------- /config/value.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "go-micro.dev/v5/config/reader" 7 | ) 8 | 9 | type value struct{} 10 | 11 | func newValue() reader.Value { 12 | return new(value) 13 | } 14 | 15 | func (v *value) Bool(def bool) bool { 16 | return false 17 | } 18 | 19 | func (v *value) Int(def int) int { 20 | return 0 21 | } 22 | 23 | func (v *value) String(def string) string { 24 | return "" 25 | } 26 | 27 | func (v *value) Float64(def float64) float64 { 28 | return 0.0 29 | } 30 | 31 | func (v *value) Duration(def time.Duration) time.Duration { 32 | return time.Duration(0) 33 | } 34 | 35 | func (v *value) StringSlice(def []string) []string { 36 | return nil 37 | } 38 | 39 | func (v *value) StringMap(def map[string]string) map[string]string { 40 | return map[string]string{} 41 | } 42 | 43 | func (v *value) Scan(val interface{}) error { 44 | return nil 45 | } 46 | 47 | func (v *value) Bytes() []byte { 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /debug/log/log.go: -------------------------------------------------------------------------------- 1 | // Package log provides debug logging 2 | package log 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | var ( 11 | // Default buffer size if any. 12 | DefaultSize = 1024 13 | // DefaultLog logger. 14 | DefaultLog = NewLog() 15 | // Default formatter. 16 | DefaultFormat = TextFormat 17 | ) 18 | 19 | // Log is debug log interface for reading and writing logs. 20 | type Log interface { 21 | // Read reads log entries from the logger 22 | Read(...ReadOption) ([]Record, error) 23 | // Write writes records to log 24 | Write(Record) error 25 | // Stream log records 26 | Stream() (Stream, error) 27 | } 28 | 29 | // Record is log record entry. 30 | type Record struct { 31 | // Timestamp of logged event 32 | Timestamp time.Time `json:"timestamp"` 33 | // Metadata to enrich log record 34 | Metadata map[string]string `json:"metadata"` 35 | // Value contains log entry 36 | Message interface{} `json:"message"` 37 | } 38 | 39 | // Stream returns a log stream. 40 | type Stream interface { 41 | Chan() <-chan Record 42 | Stop() error 43 | } 44 | 45 | // Format is a function which formats the output. 46 | type FormatFunc func(Record) string 47 | 48 | // TextFormat returns text format. 49 | func TextFormat(r Record) string { 50 | t := r.Timestamp.Format("2006-01-02 15:04:05") 51 | return fmt.Sprintf("%s %v", t, r.Message) 52 | } 53 | 54 | // JSONFormat is a json Format func. 55 | func JSONFormat(r Record) string { 56 | b, _ := json.Marshal(r) 57 | return string(b) 58 | } 59 | -------------------------------------------------------------------------------- /debug/log/memory/memory_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "go-micro.dev/v5/debug/log" 8 | ) 9 | 10 | func TestLogger(t *testing.T) { 11 | // set size to some value 12 | size := 100 13 | // override the global logger 14 | lg := NewLog(log.Size(size)) 15 | // make sure we have the right size of the logger ring buffer 16 | if lg.(*memoryLog).Size() != size { 17 | t.Errorf("expected buffer size: %d, got: %d", size, lg.(*memoryLog).Size()) 18 | } 19 | 20 | // Log some cruft 21 | lg.Write(log.Record{Message: "foobar"}) 22 | lg.Write(log.Record{Message: "foo bar"}) 23 | 24 | // Check if the logs are stored in the logger ring buffer 25 | expected := []string{"foobar", "foo bar"} 26 | entries, _ := lg.Read(log.Count(len(expected))) 27 | for i, entry := range entries { 28 | if !reflect.DeepEqual(entry.Message, expected[i]) { 29 | t.Errorf("expected %s, got %s", expected[i], entry.Message) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /debug/log/memory/stream.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "go-micro.dev/v5/debug/log" 5 | ) 6 | 7 | type logStream struct { 8 | stream <-chan log.Record 9 | stop chan bool 10 | } 11 | 12 | func (l *logStream) Chan() <-chan log.Record { 13 | return l.stream 14 | } 15 | 16 | func (l *logStream) Stop() error { 17 | select { 18 | case <-l.stop: 19 | return nil 20 | default: 21 | close(l.stop) 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /debug/log/noop/noop.go: -------------------------------------------------------------------------------- 1 | package noop 2 | 3 | import ( 4 | "go-micro.dev/v5/debug/log" 5 | ) 6 | 7 | type noop struct{} 8 | 9 | func (n *noop) Read(...log.ReadOption) ([]log.Record, error) { 10 | return nil, nil 11 | } 12 | 13 | func (n *noop) Write(log.Record) error { 14 | return nil 15 | } 16 | 17 | func (n *noop) Stream() (log.Stream, error) { 18 | return nil, nil 19 | } 20 | 21 | func NewLog(opts ...log.Option) log.Log { 22 | return new(noop) 23 | } 24 | -------------------------------------------------------------------------------- /debug/log/options.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "time" 4 | 5 | // Option used by the logger. 6 | type Option func(*Options) 7 | 8 | // Options are logger options. 9 | type Options struct { 10 | // Format specifies the output format 11 | Format FormatFunc 12 | // Name of the log 13 | Name string 14 | // Size is the size of ring buffer 15 | Size int 16 | } 17 | 18 | // Name of the log. 19 | func Name(n string) Option { 20 | return func(o *Options) { 21 | o.Name = n 22 | } 23 | } 24 | 25 | // Size sets the size of the ring buffer. 26 | func Size(s int) Option { 27 | return func(o *Options) { 28 | o.Size = s 29 | } 30 | } 31 | 32 | func Format(f FormatFunc) Option { 33 | return func(o *Options) { 34 | o.Format = f 35 | } 36 | } 37 | 38 | // DefaultOptions returns default options. 39 | func DefaultOptions() Options { 40 | return Options{ 41 | Size: DefaultSize, 42 | } 43 | } 44 | 45 | // ReadOptions for querying the logs. 46 | type ReadOptions struct { 47 | // Since what time in past to return the logs 48 | Since time.Time 49 | // Count specifies number of logs to return 50 | Count int 51 | // Stream requests continuous log stream 52 | Stream bool 53 | } 54 | 55 | // ReadOption used for reading the logs. 56 | type ReadOption func(*ReadOptions) 57 | 58 | // Since sets the time since which to return the log records. 59 | func Since(s time.Time) ReadOption { 60 | return func(o *ReadOptions) { 61 | o.Since = s 62 | } 63 | } 64 | 65 | // Count sets the number of log records to return. 66 | func Count(c int) ReadOption { 67 | return func(o *ReadOptions) { 68 | o.Count = c 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /debug/log/os.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/google/uuid" 7 | "go-micro.dev/v5/util/ring" 8 | ) 9 | 10 | // Should stream from OS. 11 | type osLog struct { 12 | format FormatFunc 13 | buffer *ring.Buffer 14 | subs map[string]*osStream 15 | 16 | sync.RWMutex 17 | once sync.Once 18 | } 19 | 20 | type osStream struct { 21 | stream chan Record 22 | } 23 | 24 | // Read reads log entries from the logger. 25 | func (o *osLog) Read(...ReadOption) ([]Record, error) { 26 | var records []Record 27 | 28 | // read the last 100 records 29 | for _, v := range o.buffer.Get(100) { 30 | records = append(records, v.Value.(Record)) 31 | } 32 | 33 | return records, nil 34 | } 35 | 36 | // Write writes records to log. 37 | func (o *osLog) Write(r Record) error { 38 | o.buffer.Put(r) 39 | return nil 40 | } 41 | 42 | // Stream log records. 43 | func (o *osLog) Stream() (Stream, error) { 44 | o.Lock() 45 | defer o.Unlock() 46 | 47 | // create stream 48 | st := &osStream{ 49 | stream: make(chan Record, 128), 50 | } 51 | 52 | // save stream 53 | o.subs[uuid.New().String()] = st 54 | 55 | return st, nil 56 | } 57 | 58 | func (o *osStream) Chan() <-chan Record { 59 | return o.stream 60 | } 61 | 62 | func (o *osStream) Stop() error { 63 | return nil 64 | } 65 | 66 | func NewLog(opts ...Option) Log { 67 | options := Options{ 68 | Format: DefaultFormat, 69 | } 70 | for _, o := range opts { 71 | o(&options) 72 | } 73 | 74 | l := &osLog{ 75 | format: options.Format, 76 | buffer: ring.New(1024), 77 | subs: make(map[string]*osStream), 78 | } 79 | 80 | return l 81 | } 82 | -------------------------------------------------------------------------------- /debug/profile/http/http.go: -------------------------------------------------------------------------------- 1 | // Package http enables the http profiler 2 | package http 3 | 4 | import ( 5 | "context" 6 | "net/http" 7 | "net/http/pprof" 8 | "sync" 9 | 10 | "go-micro.dev/v5/debug/profile" 11 | ) 12 | 13 | type httpProfile struct { 14 | server *http.Server 15 | sync.Mutex 16 | running bool 17 | } 18 | 19 | var ( 20 | DefaultAddress = ":6060" 21 | ) 22 | 23 | // Start the profiler. 24 | func (h *httpProfile) Start() error { 25 | h.Lock() 26 | defer h.Unlock() 27 | 28 | if h.running { 29 | return nil 30 | } 31 | 32 | go func() { 33 | if err := h.server.ListenAndServe(); err != nil { 34 | h.Lock() 35 | h.running = false 36 | h.Unlock() 37 | } 38 | }() 39 | 40 | h.running = true 41 | 42 | return nil 43 | } 44 | 45 | // Stop the profiler. 46 | func (h *httpProfile) Stop() error { 47 | h.Lock() 48 | defer h.Unlock() 49 | 50 | if !h.running { 51 | return nil 52 | } 53 | 54 | h.running = false 55 | 56 | return h.server.Shutdown(context.TODO()) 57 | } 58 | 59 | func (h *httpProfile) String() string { 60 | return "http" 61 | } 62 | 63 | func NewProfile(opts ...profile.Option) profile.Profile { 64 | mux := http.NewServeMux() 65 | 66 | mux.HandleFunc("/debug/pprof/", pprof.Index) 67 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 68 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 69 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 70 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 71 | 72 | return &httpProfile{ 73 | server: &http.Server{ 74 | Addr: DefaultAddress, 75 | Handler: mux, 76 | }, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /debug/profile/profile.go: -------------------------------------------------------------------------------- 1 | // Package profile is for profilers 2 | package profile 3 | 4 | type Profile interface { 5 | // Start the profiler 6 | Start() error 7 | // Stop the profiler 8 | Stop() error 9 | // Name of the profiler 10 | String() string 11 | } 12 | 13 | var ( 14 | DefaultProfile Profile = new(noop) 15 | ) 16 | 17 | type noop struct{} 18 | 19 | func (p *noop) Start() error { 20 | return nil 21 | } 22 | 23 | func (p *noop) Stop() error { 24 | return nil 25 | } 26 | 27 | func (p *noop) String() string { 28 | return "noop" 29 | } 30 | 31 | type Options struct { 32 | // Name to use for the profile 33 | Name string 34 | } 35 | 36 | type Option func(o *Options) 37 | 38 | // Name of the profile. 39 | func Name(n string) Option { 40 | return func(o *Options) { 41 | o.Name = n 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /debug/stats/default.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | 8 | "go-micro.dev/v5/util/ring" 9 | ) 10 | 11 | type stats struct { 12 | // used to store past stats 13 | buffer *ring.Buffer 14 | 15 | sync.RWMutex 16 | started int64 17 | requests uint64 18 | errors uint64 19 | } 20 | 21 | func (s *stats) snapshot() *Stat { 22 | s.RLock() 23 | defer s.RUnlock() 24 | 25 | var mstat runtime.MemStats 26 | runtime.ReadMemStats(&mstat) 27 | 28 | now := time.Now().Unix() 29 | 30 | return &Stat{ 31 | Timestamp: now, 32 | Started: s.started, 33 | Uptime: now - s.started, 34 | Memory: mstat.Alloc, 35 | GC: mstat.PauseTotalNs, 36 | Threads: uint64(runtime.NumGoroutine()), 37 | Requests: s.requests, 38 | Errors: s.errors, 39 | } 40 | } 41 | 42 | func (s *stats) Read() ([]*Stat, error) { 43 | // TODO adjustable size and optional read values 44 | buf := s.buffer.Get(60) 45 | var stats []*Stat 46 | 47 | // get a value from the buffer if it exists 48 | for _, b := range buf { 49 | stat, ok := b.Value.(*Stat) 50 | if !ok { 51 | continue 52 | } 53 | stats = append(stats, stat) 54 | } 55 | 56 | // get a snapshot 57 | stats = append(stats, s.snapshot()) 58 | 59 | return stats, nil 60 | } 61 | 62 | func (s *stats) Write(stat *Stat) error { 63 | s.buffer.Put(stat) 64 | return nil 65 | } 66 | 67 | func (s *stats) Record(err error) error { 68 | s.Lock() 69 | defer s.Unlock() 70 | 71 | // increment the total request count 72 | s.requests++ 73 | 74 | // increment the error count 75 | if err != nil { 76 | s.errors++ 77 | } 78 | 79 | return nil 80 | } 81 | 82 | // NewStats returns a new in memory stats buffer 83 | // TODO add options. 84 | func NewStats() Stats { 85 | return &stats{ 86 | started: time.Now().Unix(), 87 | buffer: ring.New(60), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /debug/stats/stats.go: -------------------------------------------------------------------------------- 1 | // Package stats provides runtime stats 2 | package stats 3 | 4 | // Stats provides stats interface. 5 | type Stats interface { 6 | // Read stat snapshot 7 | Read() ([]*Stat, error) 8 | // Write a stat snapshot 9 | Write(*Stat) error 10 | // Record a request 11 | Record(error) error 12 | } 13 | 14 | // A runtime stat. 15 | type Stat struct { 16 | // Timestamp of recording 17 | Timestamp int64 18 | // Start time as unix timestamp 19 | Started int64 20 | // Uptime in seconds 21 | Uptime int64 22 | // Memory usage in bytes 23 | Memory uint64 24 | // Threads aka go routines 25 | Threads uint64 26 | // Garbage collection in nanoseconds 27 | GC uint64 28 | // Total requests 29 | Requests uint64 30 | // Total errors 31 | Errors uint64 32 | } 33 | 34 | var ( 35 | DefaultStats = NewStats() 36 | ) 37 | -------------------------------------------------------------------------------- /debug/trace/noop.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import "context" 4 | 5 | type noop struct{} 6 | 7 | func (n *noop) Init(...Option) error { 8 | return nil 9 | } 10 | 11 | func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) { 12 | return nil, nil 13 | } 14 | 15 | func (n *noop) Finish(*Span) error { 16 | return nil 17 | } 18 | 19 | func (n *noop) Read(...ReadOption) ([]*Span, error) { 20 | return nil, nil 21 | } 22 | -------------------------------------------------------------------------------- /debug/trace/options.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | type Options struct { 4 | // Size is the size of ring buffer 5 | Size int 6 | } 7 | 8 | type Option func(o *Options) 9 | 10 | type ReadOptions struct { 11 | // Trace id 12 | Trace string 13 | } 14 | 15 | type ReadOption func(o *ReadOptions) 16 | 17 | // Read the given trace. 18 | func ReadTrace(t string) ReadOption { 19 | return func(o *ReadOptions) { 20 | o.Trace = t 21 | } 22 | } 23 | 24 | const ( 25 | // DefaultSize of the buffer. 26 | DefaultSize = 64 27 | ) 28 | 29 | // DefaultOptions returns default options. 30 | func DefaultOptions() Options { 31 | return Options{ 32 | Size: DefaultSize, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /errors/errors.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: errors.proto 3 | 4 | package errors 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | -------------------------------------------------------------------------------- /errors/errors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package errors; 4 | 5 | message Error { 6 | string id = 1; 7 | int32 code = 2; 8 | string detail = 3; 9 | string status = 4; 10 | }; 11 | 12 | message MultiError { 13 | repeated Error errors = 1; 14 | } -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/client" 7 | ) 8 | 9 | type event struct { 10 | c client.Client 11 | topic string 12 | } 13 | 14 | func (e *event) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error { 15 | return e.c.Publish(ctx, e.c.NewMessage(e.topic, msg), opts...) 16 | } 17 | -------------------------------------------------------------------------------- /internal/README.md: -------------------------------------------------------------------------------- 1 | Internal related things 2 | -------------------------------------------------------------------------------- /internal/website/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | -------------------------------------------------------------------------------- /internal/website/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # gem "rails" 6 | gem 'github-pages', group: :jekyll_plugins 7 | -------------------------------------------------------------------------------- /internal/website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | The Go Micro website including docs 4 | -------------------------------------------------------------------------------- /internal/website/_config.yml: -------------------------------------------------------------------------------- 1 | title: Docs 2 | description: "A Go microservices framework" 3 | baseurl: "" # the subpath of your site, e.g. /blog 4 | url: "" # the base hostname & protocol for your site, e.g. http://example.com 5 | 6 | theme: jekyll-theme-primer 7 | -------------------------------------------------------------------------------- /internal/website/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title }} | {% endif %}Go Micro Documentation 7 | 17 | 18 | 19 |
20 | 21 | Go Micro Logo 22 | Go Micro 23 | 24 | 29 |
30 |
31 | {{ content }} 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /internal/website/docs/broker.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # Broker 6 | 7 | The broker provides pub/sub messaging for Go Micro services. 8 | 9 | ## Features 10 | - Publish messages to topics 11 | - Subscribe to topics 12 | - Multiple broker implementations 13 | 14 | ## Implementations 15 | Supported brokers include: 16 | - Memory (default) 17 | - NATS 18 | - RabbitMQ 19 | 20 | Configure the broker when creating your service as needed. 21 | 22 | ## Example Usage 23 | 24 | Here's how to use the broker in your Go Micro service: 25 | 26 | ```go 27 | package main 28 | 29 | import ( 30 | "go-micro.dev/v5" 31 | "go-micro.dev/v5/broker" 32 | "log" 33 | ) 34 | 35 | func main() { 36 | service := micro.NewService() 37 | service.Init() 38 | 39 | // Publish a message 40 | if err := broker.Publish("topic", &broker.Message{Body: []byte("hello world")}); err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | // Subscribe to a topic 45 | _, err := broker.Subscribe("topic", func(p broker.Event) error { 46 | log.Printf("Received message: %s", string(p.Message().Body)) 47 | return nil 48 | }) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // Run the service 54 | if err := service.Run(); err != nil { 55 | log.Fatal(err) 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /internal/website/docs/client-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # Client/Server 6 | 7 | Go Micro uses a client/server model for RPC communication between services. 8 | 9 | ## Client 10 | The client is used to make requests to other services. 11 | 12 | ## Server 13 | The server handles incoming requests. 14 | 15 | Both client and server are pluggable and support middleware wrappers for additional functionality. 16 | 17 | ## Example Usage 18 | 19 | Here's how to define a simple handler and register it with a Go Micro server: 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "context" 26 | "go-micro.dev/v5" 27 | "log" 28 | ) 29 | 30 | type Greeter struct{} 31 | 32 | func (g *Greeter) Hello(ctx context.Context, req *struct{}, rsp *struct{Msg string}) error { 33 | rsp.Msg = "Hello, world!" 34 | return nil 35 | } 36 | 37 | func main() { 38 | service := micro.NewService( 39 | micro.Name("greeter"), 40 | ) 41 | service.Init() 42 | micro.RegisterHandler(service.Server(), new(Greeter)) 43 | if err := service.Run(); err != nil { 44 | log.Fatal(err) 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /internal/website/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # Docs 6 | 7 | Documentation for the Go Micro framework 8 | 9 | ## Overview 10 | 11 | Go Micro is a framework for microservices development. 12 | It's built on a powerful pluggable architecture using 13 | Go interfaces. Go Micro defines the foundations for 14 | distributed systems development which includes 15 | service discovery, client/server rpc and pubsub. 16 | Additionally Go Micro contains other primitives 17 | such as auth, caching and storage. All of this 18 | is encapsulated in a high level service interface. 19 | 20 | ## Learn More 21 | 22 | To get started follow the getting started guide. 23 | Otherwise continue to read the docs for more information 24 | about the framework. 25 | 26 | ## Contents 27 | 28 | - [Getting Started](getting-started.md) 29 | - [Architecture](architecture.md) 30 | - [Registry](registry.md) 31 | - [Broker](broker.md) 32 | - [Client/Server](client-server.md) 33 | - [Transport](transport.md) 34 | - [Store](store.md) 35 | -------------------------------------------------------------------------------- /internal/website/docs/registry.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # Registry 6 | 7 | The registry is responsible for service discovery in Go Micro. It allows services to register themselves and discover other services. 8 | 9 | ## Features 10 | - Service registration and deregistration 11 | - Service lookup 12 | - Watch for changes 13 | 14 | ## Implementations 15 | Go Micro supports multiple registry backends, including: 16 | - MDNS (default) 17 | - Consul 18 | - Etcd 19 | - NATS 20 | 21 | You can configure the registry when initializing your service. 22 | 23 | ## Example Usage 24 | 25 | Here's how to use a custom registry (e.g., Consul) in your Go Micro service: 26 | 27 | ```go 28 | package main 29 | 30 | import ( 31 | "go-micro.dev/v5" 32 | "go-micro.dev/v5/registry/consul" 33 | ) 34 | 35 | func main() { 36 | reg := consul.NewRegistry() 37 | service := micro.NewService( 38 | micro.Registry(reg), 39 | ) 40 | service.Init() 41 | service.Run() 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /internal/website/docs/store.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # Store 6 | 7 | The store provides a pluggable interface for data storage in Go Micro. 8 | 9 | ## Features 10 | - Key-value storage 11 | - Multiple backend support 12 | 13 | ## Implementations 14 | Supported stores include: 15 | - Memory (default) 16 | - File 17 | - MySQL 18 | - Redis 19 | 20 | Configure the store as needed for your application. 21 | 22 | ## Example Usage 23 | 24 | Here's how to use the store in your Go Micro service: 25 | 26 | ```go 27 | package main 28 | 29 | import ( 30 | "go-micro.dev/v5" 31 | "go-micro.dev/v5/store" 32 | "log" 33 | ) 34 | 35 | func main() { 36 | service := micro.NewService() 37 | service.Init() 38 | 39 | // Write a record 40 | if err := store.Write(&store.Record{Key: "foo", Value: []byte("bar")}); err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | // Read a record 45 | recs, err := store.Read("foo") 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | log.Printf("Read value: %s", string(recs[0].Value)) 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /internal/website/docs/transport.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # Transport 6 | 7 | The transport layer is responsible for communication between services. 8 | 9 | ## Features 10 | - Pluggable transport implementations 11 | - Secure and efficient communication 12 | 13 | ## Implementations 14 | Supported transports include: 15 | - TCP (default) 16 | - gRPC 17 | 18 | You can specify the transport when initializing your service. 19 | 20 | ## Example Usage 21 | 22 | Here's how to use a custom transport (e.g., gRPC) in your Go Micro service: 23 | 24 | ```go 25 | package main 26 | 27 | import ( 28 | "go-micro.dev/v5" 29 | "go-micro.dev/v5/transport/grpc" 30 | ) 31 | 32 | func main() { 33 | t := grpc.NewTransport() 34 | service := micro.NewService( 35 | micro.Transport(t), 36 | ) 37 | service.Init() 38 | service.Run() 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /internal/website/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micro/go-micro/88f38eaef6cfa46fb7f5c0f13895368e52691768/internal/website/images/logo.png -------------------------------------------------------------------------------- /internal/website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Go Micro 10 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 |

A Go microservices framework

34 |
go get go-micro.dev/v5
35 |

36 | Community 37 | Docs 38 | Github 39 |

40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /logger/context.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "context" 4 | 5 | type loggerKey struct{} 6 | 7 | func FromContext(ctx context.Context) (Logger, bool) { 8 | l, ok := ctx.Value(loggerKey{}).(Logger) 9 | return l, ok 10 | } 11 | 12 | func NewContext(ctx context.Context, l Logger) context.Context { 13 | return context.WithValue(ctx, loggerKey{}, l) 14 | } 15 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | // Package log provides a log interface 2 | package logger 3 | 4 | var ( 5 | // Default logger. 6 | DefaultLogger Logger = NewLogger() 7 | 8 | // Default logger helper. 9 | DefaultHelper *Helper = NewHelper(DefaultLogger) 10 | ) 11 | 12 | // Logger is a generic logging interface. 13 | type Logger interface { 14 | // Init initializes options 15 | Init(options ...Option) error 16 | // The Logger options 17 | Options() Options 18 | // Fields set fields to always be logged 19 | Fields(fields map[string]interface{}) Logger 20 | // Log writes a log entry 21 | Log(level Level, v ...interface{}) 22 | // Logf writes a formatted log entry 23 | Logf(level Level, format string, v ...interface{}) 24 | // String returns the name of logger 25 | String() string 26 | } 27 | 28 | func Init(opts ...Option) error { 29 | return DefaultLogger.Init(opts...) 30 | } 31 | 32 | func Fields(fields map[string]interface{}) Logger { 33 | return DefaultLogger.Fields(fields) 34 | } 35 | 36 | func Log(level Level, v ...interface{}) { 37 | DefaultLogger.Log(level, v...) 38 | } 39 | 40 | func Logf(level Level, format string, v ...interface{}) { 41 | DefaultLogger.Logf(level, format, v...) 42 | } 43 | 44 | func String() string { 45 | return DefaultLogger.String() 46 | } 47 | 48 | func LoggerOrDefault(l Logger) Logger { 49 | if l == nil { 50 | return DefaultLogger 51 | } 52 | return l 53 | } 54 | -------------------------------------------------------------------------------- /logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestLogger(t *testing.T) { 9 | l := NewLogger(WithLevel(TraceLevel), WithCallerSkipCount(2)) 10 | 11 | h1 := NewHelper(l).WithFields(map[string]interface{}{"key1": "val1"}) 12 | h1.Log(TraceLevel, "simple log before trace_msg1") 13 | h1.Trace("trace_msg1") 14 | h1.Log(TraceLevel, "simple log after trace_msg1") 15 | h1.Warn("warn_msg1") 16 | 17 | h2 := NewHelper(l).WithFields(map[string]interface{}{"key2": "val2"}) 18 | h2.Logf(TraceLevel, "formatted log before trace_msg%s", "2") 19 | h2.Trace("trace_msg2") 20 | h2.Logf(TraceLevel, "formatted log after trace_msg%s", "2") 21 | h2.Warn("warn_msg2") 22 | 23 | l = NewLogger(WithLevel(TraceLevel), WithCallerSkipCount(1)) 24 | l.Fields(map[string]interface{}{"key3": "val4"}).Log(InfoLevel, "test_msg") 25 | } 26 | 27 | func TestExtract(t *testing.T) { 28 | l := NewLogger(WithLevel(TraceLevel), WithCallerSkipCount(2)).Fields(map[string]interface{}{"requestID": "req-1"}) 29 | 30 | ctx := NewContext(context.Background(), l) 31 | 32 | Info("info message without request ID") 33 | Extract(ctx).Info("info message with request ID") 34 | } 35 | -------------------------------------------------------------------------------- /logger/options.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | type Option func(*Options) 9 | 10 | type Options struct { 11 | // It's common to set this to a file, or leave it default which is `os.Stderr` 12 | Out io.Writer 13 | // Alternative options 14 | Context context.Context 15 | // fields to always be logged 16 | Fields map[string]interface{} 17 | // Caller skip frame count for file:line info 18 | CallerSkipCount int 19 | // The logging level the logger should log at. default is `InfoLevel` 20 | Level Level 21 | } 22 | 23 | // WithFields set default fields for the logger. 24 | func WithFields(fields map[string]interface{}) Option { 25 | return func(args *Options) { 26 | args.Fields = fields 27 | } 28 | } 29 | 30 | // WithLevel set default level for the logger. 31 | func WithLevel(level Level) Option { 32 | return func(args *Options) { 33 | args.Level = level 34 | } 35 | } 36 | 37 | // WithOutput set default output writer for the logger. 38 | func WithOutput(out io.Writer) Option { 39 | return func(args *Options) { 40 | args.Out = out 41 | } 42 | } 43 | 44 | // WithCallerSkipCount set frame count to skip. 45 | func WithCallerSkipCount(c int) Option { 46 | return func(args *Options) { 47 | args.CallerSkipCount = c 48 | } 49 | } 50 | 51 | func SetOption(k, v interface{}) Option { 52 | return func(o *Options) { 53 | if o.Context == nil { 54 | o.Context = context.Background() 55 | } 56 | o.Context = context.WithValue(o.Context, k, v) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micro/go-micro/88f38eaef6cfa46fb7f5c0f13895368e52691768/logo.png -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "go-micro.dev/v5/service" 5 | ) 6 | 7 | var Broker = service.Broker 8 | var Cache = service.Cache 9 | var Cmd = service.Cmd 10 | var Client = service.Client 11 | var Context = service.Context 12 | var Handle = service.Handle 13 | var HandleSignal = service.HandleSignal 14 | var Profile = service.Profile 15 | var Server = service.Server 16 | var Store = service.Store 17 | var Registry = service.Registry 18 | var Tracer = service.Tracer 19 | var Auth = service.Auth 20 | var Config = service.Config 21 | var Selector = service.Selector 22 | var Transport = service.Transport 23 | var Address = service.Address 24 | var Name = service.Name 25 | var Version = service.Version 26 | var Metadata = service.Metadata 27 | var Flags = service.Flags 28 | var Action = service.Action 29 | var RegisterTTL = service.RegisterTTL 30 | var RegisterInterval = service.RegisterInterval 31 | var WrapClient = service.WrapClient 32 | var WrapCall = service.WrapCall 33 | var WrapHandler = service.WrapHandler 34 | var WrapSubscriber = service.WrapSubscriber 35 | var AddListenOption = service.AddListenOption 36 | var BeforeStart = service.BeforeStart 37 | var BeforeStop = service.BeforeStop 38 | var AfterStart = service.AfterStart 39 | var AfterStop = service.AfterStop 40 | var Logger = service.Logger 41 | -------------------------------------------------------------------------------- /registry/cache/README.md: -------------------------------------------------------------------------------- 1 | # Registry Cache 2 | 3 | Cache is a library that provides a caching layer for the go-micro [registry](https://godoc.org/github.com/micro/go-micro/registry#Registry). 4 | 5 | If you're looking for caching in your microservices use the [selector](https://micro.mu/docs/fault-tolerance.html#caching-discovery). 6 | 7 | ## Interface 8 | 9 | ```go 10 | // Cache is the registry cache interface 11 | type Cache interface { 12 | // embed the registry interface 13 | registry.Registry 14 | // stop the cache watcher 15 | Stop() 16 | } 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```go 22 | import ( 23 | "github.com/micro/go-micro/registry" 24 | "github.com/micro/go-micro/registry/cache" 25 | ) 26 | 27 | r := registry.NewRegistry() 28 | cache := cache.New(r) 29 | 30 | services, _ := cache.GetService("my.service") 31 | ``` 32 | -------------------------------------------------------------------------------- /registry/cache/options.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "go-micro.dev/v5/logger" 7 | ) 8 | 9 | // WithTTL sets the cache TTL. 10 | func WithTTL(t time.Duration) Option { 11 | return func(o *Options) { 12 | o.TTL = t 13 | } 14 | } 15 | 16 | // WithLogger sets the underline logger. 17 | func WithLogger(l logger.Logger) Option { 18 | return func(o *Options) { 19 | o.Logger = l 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /registry/etcd/options.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/registry" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type authKey struct{} 11 | 12 | type logConfigKey struct{} 13 | 14 | type authCreds struct { 15 | Username string 16 | Password string 17 | } 18 | 19 | // Auth allows you to specify username/password. 20 | func Auth(username, password string) registry.Option { 21 | return func(o *registry.Options) { 22 | if o.Context == nil { 23 | o.Context = context.Background() 24 | } 25 | o.Context = context.WithValue(o.Context, authKey{}, &authCreds{Username: username, Password: password}) 26 | } 27 | } 28 | 29 | // LogConfig allows you to set etcd log config. 30 | func LogConfig(config *zap.Config) registry.Option { 31 | return func(o *registry.Options) { 32 | if o.Context == nil { 33 | o.Context = context.Background() 34 | } 35 | o.Context = context.WithValue(o.Context, logConfigKey{}, config) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /registry/memory_watcher.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type memWatcher struct { 8 | wo WatchOptions 9 | res chan *Result 10 | exit chan bool 11 | id string 12 | } 13 | 14 | func (m *memWatcher) Next() (*Result, error) { 15 | for { 16 | select { 17 | case r := <-m.res: 18 | if len(m.wo.Service) > 0 && m.wo.Service != r.Service.Name { 19 | continue 20 | } 21 | return r, nil 22 | case <-m.exit: 23 | return nil, errors.New("watcher stopped") 24 | } 25 | } 26 | } 27 | 28 | func (m *memWatcher) Stop() { 29 | select { 30 | case <-m.exit: 31 | return 32 | default: 33 | close(m.exit) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /registry/nats/nats_assert_test.go: -------------------------------------------------------------------------------- 1 | package nats_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func assertNoError(tb testing.TB, actual error) { 9 | if actual != nil { 10 | tb.Errorf("expected no error, got %v", actual) 11 | } 12 | } 13 | 14 | func assertEqual(tb testing.TB, expected, actual interface{}) { 15 | if !reflect.DeepEqual(expected, actual) { 16 | tb.Errorf("expected %v, got %v", expected, actual) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /registry/nats/nats_environment_test.go: -------------------------------------------------------------------------------- 1 | package nats_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | log "go-micro.dev/v5/logger" 8 | "go-micro.dev/v5/registry" 9 | "go-micro.dev/v5/registry/nats" 10 | ) 11 | 12 | type environment struct { 13 | registryOne registry.Registry 14 | registryTwo registry.Registry 15 | registryThree registry.Registry 16 | 17 | serviceOne registry.Service 18 | serviceTwo registry.Service 19 | 20 | nodeOne registry.Node 21 | nodeTwo registry.Node 22 | nodeThree registry.Node 23 | } 24 | 25 | var e environment 26 | 27 | func TestMain(m *testing.M) { 28 | natsURL := os.Getenv("NATS_URL") 29 | if natsURL == "" { 30 | log.Infof("NATS_URL is undefined - skipping tests") 31 | return 32 | } 33 | 34 | e.registryOne = nats.NewNatsRegistry(registry.Addrs(natsURL), nats.Quorum(1)) 35 | e.registryTwo = nats.NewNatsRegistry(registry.Addrs(natsURL), nats.Quorum(1)) 36 | e.registryThree = nats.NewNatsRegistry(registry.Addrs(natsURL), nats.Quorum(1)) 37 | 38 | e.serviceOne.Name = "one" 39 | e.serviceOne.Version = "default" 40 | e.serviceOne.Nodes = []*registry.Node{&e.nodeOne} 41 | 42 | e.serviceTwo.Name = "two" 43 | e.serviceTwo.Version = "default" 44 | e.serviceTwo.Nodes = []*registry.Node{&e.nodeOne, &e.nodeTwo} 45 | 46 | e.nodeOne.Id = "one" 47 | e.nodeTwo.Id = "two" 48 | e.nodeThree.Id = "three" 49 | 50 | if err := e.registryOne.Register(&e.serviceOne); err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | if err := e.registryOne.Register(&e.serviceTwo); err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | result := m.Run() 59 | 60 | if err := e.registryOne.Deregister(&e.serviceOne); err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | if err := e.registryOne.Deregister(&e.serviceTwo); err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | os.Exit(result) 69 | } 70 | -------------------------------------------------------------------------------- /registry/nats/nats_registry.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | var ( 4 | DefaultRegistry = NewNatsRegistry() 5 | ) 6 | -------------------------------------------------------------------------------- /registry/nats/nats_watcher.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/nats-io/nats.go" 8 | "go-micro.dev/v5/registry" 9 | ) 10 | 11 | type natsWatcher struct { 12 | sub *nats.Subscription 13 | wo registry.WatchOptions 14 | } 15 | 16 | func (n *natsWatcher) Next() (*registry.Result, error) { 17 | var result *registry.Result 18 | for { 19 | m, err := n.sub.NextMsg(time.Minute) 20 | if err != nil && err == nats.ErrTimeout { 21 | continue 22 | } else if err != nil { 23 | return nil, err 24 | } 25 | if err := json.Unmarshal(m.Data, &result); err != nil { 26 | return nil, err 27 | } 28 | if len(n.wo.Service) > 0 && result.Service.Name != n.wo.Service { 29 | continue 30 | } 31 | break 32 | } 33 | 34 | return result, nil 35 | } 36 | 37 | func (n *natsWatcher) Stop() { 38 | n.sub.Unsubscribe() 39 | } 40 | -------------------------------------------------------------------------------- /registry/watcher.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import "time" 4 | 5 | // Watcher is an interface that returns updates 6 | // about services within the registry. 7 | type Watcher interface { 8 | // Next is a blocking call 9 | Next() (*Result, error) 10 | Stop() 11 | } 12 | 13 | // Result is returned by a call to Next on 14 | // the watcher. Actions can be create, update, delete. 15 | type Result struct { 16 | Service *Service 17 | Action string 18 | } 19 | 20 | // EventType defines registry event type. 21 | type EventType int 22 | 23 | const ( 24 | // Create is emitted when a new service is registered. 25 | Create EventType = iota 26 | // Delete is emitted when an existing service is deregsitered. 27 | Delete 28 | // Update is emitted when an existing servicec is updated. 29 | Update 30 | ) 31 | 32 | // String returns human readable event type. 33 | func (t EventType) String() string { 34 | switch t { 35 | case Create: 36 | return "create" 37 | case Delete: 38 | return "delete" 39 | case Update: 40 | return "update" 41 | default: 42 | return "unknown" 43 | } 44 | } 45 | 46 | // Event is registry event. 47 | type Event struct { 48 | // Timestamp is event timestamp 49 | Timestamp time.Time 50 | // Service is registry service 51 | Service *Service 52 | // Id is registry id 53 | Id string 54 | // Type defines type of event 55 | Type EventType 56 | } 57 | -------------------------------------------------------------------------------- /selector/common_test.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | ) 6 | 7 | var ( 8 | // mock data. 9 | testData = map[string][]*registry.Service{ 10 | "foo": { 11 | { 12 | Name: "foo", 13 | Version: "1.0.0", 14 | Nodes: []*registry.Node{ 15 | { 16 | Id: "foo-1.0.0-123", 17 | Address: "localhost:9999", 18 | }, 19 | { 20 | Id: "foo-1.0.0-321", 21 | Address: "localhost:9999", 22 | }, 23 | }, 24 | }, 25 | { 26 | Name: "foo", 27 | Version: "1.0.1", 28 | Nodes: []*registry.Node{ 29 | { 30 | Id: "foo-1.0.1-321", 31 | Address: "localhost:6666", 32 | }, 33 | }, 34 | }, 35 | { 36 | Name: "foo", 37 | Version: "1.0.3", 38 | Nodes: []*registry.Node{ 39 | { 40 | Id: "foo-1.0.3-345", 41 | Address: "localhost:8888", 42 | }, 43 | }, 44 | }, 45 | }, 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /selector/default_test.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "go-micro.dev/v5/registry" 8 | ) 9 | 10 | func TestRegistrySelector(t *testing.T) { 11 | counts := map[string]int{} 12 | 13 | r := registry.NewMemoryRegistry(registry.Services(testData)) 14 | cache := NewSelector(Registry(r)) 15 | 16 | next, err := cache.Select("foo") 17 | if err != nil { 18 | t.Errorf("Unexpected error calling cache select: %v", err) 19 | } 20 | 21 | for i := 0; i < 100; i++ { 22 | node, err := next() 23 | if err != nil { 24 | t.Errorf("Expected node err, got err: %v", err) 25 | } 26 | counts[node.Id]++ 27 | } 28 | 29 | if len(os.Getenv("IN_TRAVIS_CI")) == 0 { 30 | t.Logf("Selector Counts %v", counts) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /selector/filter.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | ) 6 | 7 | // FilterEndpoint is an endpoint based Select Filter which will 8 | // only return services with the endpoint specified. 9 | func FilterEndpoint(name string) Filter { 10 | return func(old []*registry.Service) []*registry.Service { 11 | var services []*registry.Service 12 | 13 | for _, service := range old { 14 | for _, ep := range service.Endpoints { 15 | if ep.Name == name { 16 | services = append(services, service) 17 | break 18 | } 19 | } 20 | } 21 | 22 | return services 23 | } 24 | } 25 | 26 | // FilterLabel is a label based Select Filter which will 27 | // only return services with the label specified. 28 | func FilterLabel(key, val string) Filter { 29 | return func(old []*registry.Service) []*registry.Service { 30 | var services []*registry.Service 31 | 32 | for _, service := range old { 33 | serv := new(registry.Service) 34 | var nodes []*registry.Node 35 | 36 | for _, node := range service.Nodes { 37 | if node.Metadata == nil { 38 | continue 39 | } 40 | 41 | if node.Metadata[key] == val { 42 | nodes = append(nodes, node) 43 | } 44 | } 45 | 46 | // only add service if there's some nodes 47 | if len(nodes) > 0 { 48 | // copy 49 | *serv = *service 50 | serv.Nodes = nodes 51 | services = append(services, serv) 52 | } 53 | } 54 | 55 | return services 56 | } 57 | } 58 | 59 | // FilterVersion is a version based Select Filter which will 60 | // only return services with the version specified. 61 | func FilterVersion(version string) Filter { 62 | return func(old []*registry.Service) []*registry.Service { 63 | var services []*registry.Service 64 | 65 | for _, service := range old { 66 | if service.Version == version { 67 | services = append(services, service) 68 | } 69 | } 70 | 71 | return services 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /selector/options.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/logger" 7 | "go-micro.dev/v5/registry" 8 | ) 9 | 10 | type Options struct { 11 | Registry registry.Registry 12 | Strategy Strategy 13 | 14 | // Other options for implementations of the interface 15 | // can be stored in a context 16 | Context context.Context 17 | // Logger is the underline logger 18 | Logger logger.Logger 19 | } 20 | 21 | type SelectOptions struct { 22 | 23 | // Other options for implementations of the interface 24 | // can be stored in a context 25 | Context context.Context 26 | Strategy Strategy 27 | 28 | Filters []Filter 29 | } 30 | 31 | type Option func(*Options) 32 | 33 | // SelectOption used when making a select call. 34 | type SelectOption func(*SelectOptions) 35 | 36 | // Registry sets the registry used by the selector. 37 | func Registry(r registry.Registry) Option { 38 | return func(o *Options) { 39 | o.Registry = r 40 | } 41 | } 42 | 43 | // SetStrategy sets the default strategy for the selector. 44 | func SetStrategy(fn Strategy) Option { 45 | return func(o *Options) { 46 | o.Strategy = fn 47 | } 48 | } 49 | 50 | // WithFilter adds a filter function to the list of filters 51 | // used during the Select call. 52 | func WithFilter(fn ...Filter) SelectOption { 53 | return func(o *SelectOptions) { 54 | o.Filters = append(o.Filters, fn...) 55 | } 56 | } 57 | 58 | // Strategy sets the selector strategy. 59 | func WithStrategy(fn Strategy) SelectOption { 60 | return func(o *SelectOptions) { 61 | o.Strategy = fn 62 | } 63 | } 64 | 65 | // WithLogger sets the underline logger. 66 | func WithLogger(l logger.Logger) Option { 67 | return func(o *Options) { 68 | o.Logger = l 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /selector/selector.go: -------------------------------------------------------------------------------- 1 | // Package selector is a way to pick a list of service nodes 2 | package selector 3 | 4 | import ( 5 | "errors" 6 | 7 | "go-micro.dev/v5/registry" 8 | ) 9 | 10 | // Selector builds on the registry as a mechanism to pick nodes 11 | // and mark their status. This allows host pools and other things 12 | // to be built using various algorithms. 13 | type Selector interface { 14 | Init(opts ...Option) error 15 | Options() Options 16 | // Select returns a function which should return the next node 17 | Select(service string, opts ...SelectOption) (Next, error) 18 | // Mark sets the success/error against a node 19 | Mark(service string, node *registry.Node, err error) 20 | // Reset returns state back to zero for a service 21 | Reset(service string) 22 | // Close renders the selector unusable 23 | Close() error 24 | // Name of the selector 25 | String() string 26 | } 27 | 28 | // Next is a function that returns the next node 29 | // based on the selector's strategy. 30 | type Next func() (*registry.Node, error) 31 | 32 | // Filter is used to filter a service during the selection process. 33 | type Filter func([]*registry.Service) []*registry.Service 34 | 35 | // Strategy is a selection strategy e.g random, round robin. 36 | type Strategy func([]*registry.Service) Next 37 | 38 | var ( 39 | DefaultSelector = NewSelector() 40 | 41 | ErrNotFound = errors.New("not found") 42 | ErrNoneAvailable = errors.New("none available") 43 | ) 44 | -------------------------------------------------------------------------------- /selector/strategy.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "time" 7 | 8 | "go-micro.dev/v5/registry" 9 | ) 10 | 11 | func init() { 12 | rand.Seed(time.Now().UnixNano()) 13 | } 14 | 15 | // Random is a random strategy algorithm for node selection. 16 | func Random(services []*registry.Service) Next { 17 | nodes := make([]*registry.Node, 0, len(services)) 18 | 19 | for _, service := range services { 20 | nodes = append(nodes, service.Nodes...) 21 | } 22 | 23 | return func() (*registry.Node, error) { 24 | if len(nodes) == 0 { 25 | return nil, ErrNoneAvailable 26 | } 27 | 28 | i := rand.Int() % len(nodes) 29 | return nodes[i], nil 30 | } 31 | } 32 | 33 | // RoundRobin is a roundrobin strategy algorithm for node selection. 34 | func RoundRobin(services []*registry.Service) Next { 35 | nodes := make([]*registry.Node, 0, len(services)) 36 | 37 | for _, service := range services { 38 | nodes = append(nodes, service.Nodes...) 39 | } 40 | 41 | var i = rand.Int() 42 | var mtx sync.Mutex 43 | 44 | return func() (*registry.Node, error) { 45 | if len(nodes) == 0 { 46 | return nil, ErrNoneAvailable 47 | } 48 | 49 | mtx.Lock() 50 | node := nodes[i%len(nodes)] 51 | i++ 52 | mtx.Unlock() 53 | 54 | return node, nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /selector/strategy_test.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "go-micro.dev/v5/registry" 8 | ) 9 | 10 | func TestStrategies(t *testing.T) { 11 | testData := []*registry.Service{ 12 | { 13 | Name: "test1", 14 | Version: "latest", 15 | Nodes: []*registry.Node{ 16 | { 17 | Id: "test1-1", 18 | Address: "10.0.0.1:1001", 19 | }, 20 | { 21 | Id: "test1-2", 22 | Address: "10.0.0.2:1002", 23 | }, 24 | }, 25 | }, 26 | { 27 | Name: "test1", 28 | Version: "default", 29 | Nodes: []*registry.Node{ 30 | { 31 | Id: "test1-3", 32 | Address: "10.0.0.3:1003", 33 | }, 34 | { 35 | Id: "test1-4", 36 | Address: "10.0.0.4:1004", 37 | }, 38 | }, 39 | }, 40 | } 41 | 42 | for name, strategy := range map[string]Strategy{"random": Random, "roundrobin": RoundRobin} { 43 | next := strategy(testData) 44 | counts := make(map[string]int) 45 | 46 | for i := 0; i < 100; i++ { 47 | node, err := next() 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | counts[node.Id]++ 52 | } 53 | 54 | if len(os.Getenv("IN_TRAVIS_CI")) == 0 { 55 | t.Logf("%s: %+v\n", name, counts) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/context.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | type serverKey struct{} 9 | type wgKey struct{} 10 | 11 | func wait(ctx context.Context) *sync.WaitGroup { 12 | if ctx == nil { 13 | return nil 14 | } 15 | wg, ok := ctx.Value(wgKey{}).(*sync.WaitGroup) 16 | if !ok { 17 | return nil 18 | } 19 | return wg 20 | } 21 | 22 | func FromContext(ctx context.Context) (Server, bool) { 23 | c, ok := ctx.Value(serverKey{}).(Server) 24 | return c, ok 25 | } 26 | 27 | func NewContext(ctx context.Context, s Server) context.Context { 28 | return context.WithValue(ctx, serverKey{}, s) 29 | } 30 | -------------------------------------------------------------------------------- /server/extractor_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "go-micro.dev/v5/registry" 9 | ) 10 | 11 | type testHandler struct{} 12 | 13 | type testRequest struct{} 14 | 15 | type testResponse struct{} 16 | 17 | func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error { 18 | return nil 19 | } 20 | 21 | func TestExtractEndpoint(t *testing.T) { 22 | handler := &testHandler{} 23 | typ := reflect.TypeOf(handler) 24 | 25 | var endpoints []*registry.Endpoint 26 | 27 | for m := 0; m < typ.NumMethod(); m++ { 28 | if e := extractEndpoint(typ.Method(m)); e != nil { 29 | endpoints = append(endpoints, e) 30 | } 31 | } 32 | 33 | if i := len(endpoints); i != 1 { 34 | t.Errorf("Expected 1 endpoint, have %d", i) 35 | } 36 | 37 | if endpoints[0].Name != "Test" { 38 | t.Errorf("Expected handler Test, got %s", endpoints[0].Name) 39 | } 40 | 41 | if endpoints[0].Request == nil { 42 | t.Error("Expected non nil request") 43 | } 44 | 45 | if endpoints[0].Response == nil { 46 | t.Error("Expected non nil request") 47 | } 48 | 49 | if endpoints[0].Request.Name != "testRequest" { 50 | t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name) 51 | } 52 | 53 | if endpoints[0].Response.Name != "testResponse" { 54 | t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name) 55 | } 56 | 57 | if endpoints[0].Request.Type != "testRequest" { 58 | t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type) 59 | } 60 | 61 | if endpoints[0].Response.Type != "testResponse" { 62 | t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/grpc/context.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/server" 7 | ) 8 | 9 | func setServerOption(k, v interface{}) server.Option { 10 | return func(o *server.Options) { 11 | if o.Context == nil { 12 | o.Context = context.Background() 13 | } 14 | o.Context = context.WithValue(o.Context, k, v) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/grpc/error.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-micro.dev/v5/errors" 7 | "google.golang.org/grpc/codes" 8 | ) 9 | 10 | func microError(err *errors.Error) codes.Code { 11 | switch err { 12 | case nil: 13 | return codes.OK 14 | } 15 | 16 | switch err.Code { 17 | case http.StatusOK: 18 | return codes.OK 19 | case http.StatusBadRequest: 20 | return codes.InvalidArgument 21 | case http.StatusRequestTimeout: 22 | return codes.DeadlineExceeded 23 | case http.StatusNotFound: 24 | return codes.NotFound 25 | case http.StatusConflict: 26 | return codes.AlreadyExists 27 | case http.StatusForbidden: 28 | return codes.PermissionDenied 29 | case http.StatusUnauthorized: 30 | return codes.Unauthenticated 31 | case http.StatusPreconditionFailed: 32 | return codes.FailedPrecondition 33 | case http.StatusNotImplemented: 34 | return codes.Unimplemented 35 | case http.StatusInternalServerError: 36 | return codes.Internal 37 | case http.StatusServiceUnavailable: 38 | return codes.Unavailable 39 | } 40 | 41 | return codes.Unknown 42 | } 43 | -------------------------------------------------------------------------------- /server/grpc/extractor_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "go-micro.dev/v5/registry" 9 | ) 10 | 11 | type testHandler struct{} 12 | 13 | type testRequest struct{} 14 | 15 | type testResponse struct{} 16 | 17 | func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error { 18 | return nil 19 | } 20 | 21 | func TestExtractEndpoint(t *testing.T) { 22 | handler := &testHandler{} 23 | typ := reflect.TypeOf(handler) 24 | 25 | var endpoints []*registry.Endpoint 26 | 27 | for m := 0; m < typ.NumMethod(); m++ { 28 | if e := extractEndpoint(typ.Method(m)); e != nil { 29 | endpoints = append(endpoints, e) 30 | } 31 | } 32 | 33 | if i := len(endpoints); i != 1 { 34 | t.Errorf("Expected 1 endpoint, have %d", i) 35 | } 36 | 37 | if endpoints[0].Name != "Test" { 38 | t.Errorf("Expected handler Test, got %s", endpoints[0].Name) 39 | } 40 | 41 | if endpoints[0].Request == nil { 42 | t.Error("Expected non nil request") 43 | } 44 | 45 | if endpoints[0].Response == nil { 46 | t.Error("Expected non nil request") 47 | } 48 | 49 | if endpoints[0].Request.Name != "testRequest" { 50 | t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name) 51 | } 52 | 53 | if endpoints[0].Response.Name != "testResponse" { 54 | t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name) 55 | } 56 | 57 | if endpoints[0].Request.Type != "testRequest" { 58 | t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type) 59 | } 60 | 61 | if endpoints[0].Response.Type != "testResponse" { 62 | t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/grpc/handler.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "reflect" 5 | 6 | "go-micro.dev/v5/registry" 7 | "go-micro.dev/v5/server" 8 | ) 9 | 10 | type rpcHandler struct { 11 | name string 12 | handler interface{} 13 | endpoints []*registry.Endpoint 14 | opts server.HandlerOptions 15 | } 16 | 17 | func newRpcHandler(handler interface{}, opts ...server.HandlerOption) server.Handler { 18 | options := server.HandlerOptions{ 19 | Metadata: make(map[string]map[string]string), 20 | } 21 | 22 | for _, o := range opts { 23 | o(&options) 24 | } 25 | 26 | typ := reflect.TypeOf(handler) 27 | hdlr := reflect.ValueOf(handler) 28 | name := reflect.Indirect(hdlr).Type().Name() 29 | 30 | var endpoints []*registry.Endpoint 31 | 32 | for m := 0; m < typ.NumMethod(); m++ { 33 | if e := extractEndpoint(typ.Method(m)); e != nil { 34 | e.Name = name + "." + e.Name 35 | 36 | for k, v := range options.Metadata[e.Name] { 37 | e.Metadata[k] = v 38 | } 39 | 40 | endpoints = append(endpoints, e) 41 | } 42 | } 43 | 44 | return &rpcHandler{ 45 | name: name, 46 | handler: handler, 47 | endpoints: endpoints, 48 | opts: options, 49 | } 50 | } 51 | 52 | func (r *rpcHandler) Name() string { 53 | return r.name 54 | } 55 | 56 | func (r *rpcHandler) Handler() interface{} { 57 | return r.handler 58 | } 59 | 60 | func (r *rpcHandler) Endpoints() []*registry.Endpoint { 61 | return r.endpoints 62 | } 63 | 64 | func (r *rpcHandler) Options() server.HandlerOptions { 65 | return r.opts 66 | } 67 | -------------------------------------------------------------------------------- /server/grpc/request.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | "go-micro.dev/v5/codec/bytes" 6 | ) 7 | 8 | type rpcRequest struct { 9 | service string 10 | method string 11 | contentType string 12 | codec codec.Codec 13 | header map[string]string 14 | body []byte 15 | stream bool 16 | payload interface{} 17 | } 18 | 19 | type rpcMessage struct { 20 | topic string 21 | contentType string 22 | payload interface{} 23 | header map[string]string 24 | body []byte 25 | codec codec.Codec 26 | } 27 | 28 | func (r *rpcRequest) ContentType() string { 29 | return r.contentType 30 | } 31 | 32 | func (r *rpcRequest) Service() string { 33 | return r.service 34 | } 35 | 36 | func (r *rpcRequest) Method() string { 37 | return r.method 38 | } 39 | 40 | func (r *rpcRequest) Endpoint() string { 41 | return r.method 42 | } 43 | 44 | func (r *rpcRequest) Codec() codec.Reader { 45 | return r.codec 46 | } 47 | 48 | func (r *rpcRequest) Header() map[string]string { 49 | return r.header 50 | } 51 | 52 | func (r *rpcRequest) Read() ([]byte, error) { 53 | f := &bytes.Frame{} 54 | if err := r.codec.ReadBody(f); err != nil { 55 | return nil, err 56 | } 57 | return f.Data, nil 58 | } 59 | 60 | func (r *rpcRequest) Stream() bool { 61 | return r.stream 62 | } 63 | 64 | func (r *rpcRequest) Body() interface{} { 65 | return r.payload 66 | } 67 | 68 | func (r *rpcMessage) ContentType() string { 69 | return r.contentType 70 | } 71 | 72 | func (r *rpcMessage) Topic() string { 73 | return r.topic 74 | } 75 | 76 | func (r *rpcMessage) Payload() interface{} { 77 | return r.payload 78 | } 79 | 80 | func (r *rpcMessage) Header() map[string]string { 81 | return r.header 82 | } 83 | 84 | func (r *rpcMessage) Body() []byte { 85 | return r.body 86 | } 87 | 88 | func (r *rpcMessage) Codec() codec.Reader { 89 | return r.codec 90 | } 91 | -------------------------------------------------------------------------------- /server/grpc/response.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | ) 6 | 7 | type rpcResponse struct { 8 | header map[string]string 9 | codec codec.Codec 10 | } 11 | 12 | func (r *rpcResponse) Codec() codec.Writer { 13 | return r.codec 14 | } 15 | 16 | func (r *rpcResponse) WriteHeader(hdr map[string]string) { 17 | for k, v := range hdr { 18 | r.header[k] = v 19 | } 20 | } 21 | 22 | func (r *rpcResponse) Write(b []byte) error { 23 | return r.codec.Write(&codec.Message{ 24 | Header: r.header, 25 | Body: b, 26 | }, nil) 27 | } 28 | -------------------------------------------------------------------------------- /server/grpc/stream.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/server" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | // rpcStream implements a server side Stream. 11 | type rpcStream struct { 12 | s grpc.ServerStream 13 | request server.Request 14 | } 15 | 16 | func (r *rpcStream) Close() error { 17 | return nil 18 | } 19 | 20 | func (r *rpcStream) Error() error { 21 | return nil 22 | } 23 | 24 | func (r *rpcStream) Request() server.Request { 25 | return r.request 26 | } 27 | 28 | func (r *rpcStream) Context() context.Context { 29 | return r.s.Context() 30 | } 31 | 32 | func (r *rpcStream) Send(m interface{}) error { 33 | return r.s.SendMsg(m) 34 | } 35 | 36 | func (r *rpcStream) Recv(m interface{}) error { 37 | return r.s.RecvMsg(m) 38 | } 39 | -------------------------------------------------------------------------------- /server/grpc/util.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os" 7 | "sync" 8 | 9 | "google.golang.org/grpc/codes" 10 | ) 11 | 12 | // convertCode converts a standard Go error into its canonical code. Note that 13 | // this is only used to translate the error returned by the server applications. 14 | func convertCode(err error) codes.Code { 15 | switch err { 16 | case nil: 17 | return codes.OK 18 | case io.EOF: 19 | return codes.OutOfRange 20 | case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: 21 | return codes.FailedPrecondition 22 | case os.ErrInvalid: 23 | return codes.InvalidArgument 24 | case context.Canceled: 25 | return codes.Canceled 26 | case context.DeadlineExceeded: 27 | return codes.DeadlineExceeded 28 | } 29 | switch { 30 | case os.IsExist(err): 31 | return codes.AlreadyExists 32 | case os.IsNotExist(err): 33 | return codes.NotFound 34 | case os.IsPermission(err): 35 | return codes.PermissionDenied 36 | } 37 | return codes.Unknown 38 | } 39 | 40 | func wait(ctx context.Context) *sync.WaitGroup { 41 | if ctx == nil { 42 | return nil 43 | } 44 | wg, ok := ctx.Value("wait").(*sync.WaitGroup) 45 | if !ok { 46 | return nil 47 | } 48 | return wg 49 | } 50 | -------------------------------------------------------------------------------- /server/mock/mock_handler.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | "go-micro.dev/v5/server" 6 | ) 7 | 8 | type MockHandler struct { 9 | Opts server.HandlerOptions 10 | Hdlr interface{} 11 | Id string 12 | } 13 | 14 | func (m *MockHandler) Name() string { 15 | return m.Id 16 | } 17 | 18 | func (m *MockHandler) Handler() interface{} { 19 | return m.Hdlr 20 | } 21 | 22 | func (m *MockHandler) Endpoints() []*registry.Endpoint { 23 | return []*registry.Endpoint{} 24 | } 25 | 26 | func (m *MockHandler) Options() server.HandlerOptions { 27 | return m.Opts 28 | } 29 | -------------------------------------------------------------------------------- /server/mock/mock_subscriber.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | "go-micro.dev/v5/server" 6 | ) 7 | 8 | type MockSubscriber struct { 9 | Opts server.SubscriberOptions 10 | Sub interface{} 11 | Id string 12 | } 13 | 14 | func (m *MockSubscriber) Topic() string { 15 | return m.Id 16 | } 17 | 18 | func (m *MockSubscriber) Subscriber() interface{} { 19 | return m.Sub 20 | } 21 | 22 | func (m *MockSubscriber) Endpoints() []*registry.Endpoint { 23 | return []*registry.Endpoint{} 24 | } 25 | 26 | func (m *MockSubscriber) Options() server.SubscriberOptions { 27 | return m.Opts 28 | } 29 | -------------------------------------------------------------------------------- /server/mock/mock_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "testing" 5 | 6 | "go-micro.dev/v5/server" 7 | ) 8 | 9 | func TestMockServer(t *testing.T) { 10 | srv := NewServer( 11 | server.Name("mock"), 12 | server.Version("latest"), 13 | ) 14 | 15 | if srv.Options().Name != "mock" { 16 | t.Fatalf("Expected name mock, got %s", srv.Options().Name) 17 | } 18 | 19 | if srv.Options().Version != "latest" { 20 | t.Fatalf("Expected version latest, got %s", srv.Options().Version) 21 | } 22 | 23 | srv.Init(server.Version("test")) 24 | if srv.Options().Version != "test" { 25 | t.Fatalf("Expected version test, got %s", srv.Options().Version) 26 | } 27 | 28 | h := srv.NewHandler(func() string { return "foo" }) 29 | if err := srv.Handle(h); err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | sub := srv.NewSubscriber("test", func() string { return "foo" }) 34 | if err := srv.Subscribe(sub); err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | if sub.Topic() != "test" { 39 | t.Fatalf("Expected topic test got %s", sub.Topic()) 40 | } 41 | 42 | if err := srv.Start(); err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | if err := srv.Register(); err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | if err := srv.Deregister(); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | if err := srv.Stop(); err != nil { 55 | t.Fatal(err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /server/proto/server.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.server; 4 | 5 | service Server { 6 | rpc Handle(HandleRequest) returns (HandleResponse) {}; 7 | rpc Subscribe(SubscribeRequest) returns (SubscribeResponse) {}; 8 | } 9 | 10 | message HandleRequest { 11 | string service = 1; 12 | string endpoint = 2; 13 | string protocol = 3; 14 | } 15 | 16 | message HandleResponse {} 17 | 18 | message SubscribeRequest { 19 | string topic = 1; 20 | } 21 | 22 | message SubscribeResponse {} 23 | -------------------------------------------------------------------------------- /server/rpc_event.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "go-micro.dev/v5/broker" 5 | "go-micro.dev/v5/transport" 6 | "go-micro.dev/v5/transport/headers" 7 | ) 8 | 9 | // event is a broker event we handle on the server transport. 10 | type event struct { 11 | err error 12 | message *broker.Message 13 | } 14 | 15 | func (e *event) Ack() error { 16 | // there is no ack support 17 | return nil 18 | } 19 | 20 | func (e *event) Message() *broker.Message { 21 | return e.message 22 | } 23 | 24 | func (e *event) Error() error { 25 | return e.err 26 | } 27 | 28 | func (e *event) Topic() string { 29 | return e.message.Header[headers.Message] 30 | } 31 | 32 | func newEvent(msg transport.Message) *event { 33 | return &event{ 34 | message: &broker.Message{ 35 | Header: msg.Header, 36 | Body: msg.Body, 37 | }, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/rpc_handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "reflect" 5 | 6 | "go-micro.dev/v5/registry" 7 | ) 8 | 9 | type RpcHandler struct { 10 | handler interface{} 11 | opts HandlerOptions 12 | name string 13 | endpoints []*registry.Endpoint 14 | } 15 | 16 | func NewRpcHandler(handler interface{}, opts ...HandlerOption) Handler { 17 | options := HandlerOptions{ 18 | Metadata: make(map[string]map[string]string), 19 | } 20 | 21 | for _, o := range opts { 22 | o(&options) 23 | } 24 | 25 | typ := reflect.TypeOf(handler) 26 | hdlr := reflect.ValueOf(handler) 27 | name := reflect.Indirect(hdlr).Type().Name() 28 | 29 | var endpoints []*registry.Endpoint 30 | 31 | for m := 0; m < typ.NumMethod(); m++ { 32 | if e := extractEndpoint(typ.Method(m)); e != nil { 33 | e.Name = name + "." + e.Name 34 | 35 | for k, v := range options.Metadata[e.Name] { 36 | e.Metadata[k] = v 37 | } 38 | 39 | endpoints = append(endpoints, e) 40 | } 41 | } 42 | 43 | return &RpcHandler{ 44 | name: name, 45 | handler: handler, 46 | endpoints: endpoints, 47 | opts: options, 48 | } 49 | } 50 | 51 | func (r *RpcHandler) Name() string { 52 | return r.name 53 | } 54 | 55 | func (r *RpcHandler) Handler() interface{} { 56 | return r.handler 57 | } 58 | 59 | func (r *RpcHandler) Endpoints() []*registry.Endpoint { 60 | return r.endpoints 61 | } 62 | 63 | func (r *RpcHandler) Options() HandlerOptions { 64 | return r.opts 65 | } 66 | -------------------------------------------------------------------------------- /server/rpc_response.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-micro.dev/v5/codec" 7 | "go-micro.dev/v5/transport" 8 | ) 9 | 10 | type rpcResponse struct { 11 | header map[string]string 12 | socket transport.Socket 13 | codec codec.Codec 14 | } 15 | 16 | func (r *rpcResponse) Codec() codec.Writer { 17 | return r.codec 18 | } 19 | 20 | func (r *rpcResponse) WriteHeader(hdr map[string]string) { 21 | for k, v := range hdr { 22 | r.header[k] = v 23 | } 24 | } 25 | 26 | func (r *rpcResponse) Write(b []byte) error { 27 | if _, ok := r.header["Content-Type"]; !ok { 28 | r.header["Content-Type"] = http.DetectContentType(b) 29 | } 30 | 31 | return r.socket.Send(&transport.Message{ 32 | Header: r.header, 33 | Body: b, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /server/rpc_util.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // waitgroup for global management of connections. 8 | type waitGroup struct { 9 | // global waitgroup 10 | gg *sync.WaitGroup 11 | // local waitgroup 12 | lg sync.WaitGroup 13 | } 14 | 15 | // NewWaitGroup returns a new double waitgroup for global management of processes. 16 | func NewWaitGroup(gWg *sync.WaitGroup) *waitGroup { 17 | return &waitGroup{ 18 | gg: gWg, 19 | } 20 | } 21 | 22 | func (w *waitGroup) Add(i int) { 23 | w.lg.Add(i) 24 | if w.gg != nil { 25 | w.gg.Add(i) 26 | } 27 | } 28 | 29 | func (w *waitGroup) Done() { 30 | w.lg.Done() 31 | if w.gg != nil { 32 | w.gg.Done() 33 | } 34 | } 35 | 36 | func (w *waitGroup) Wait() { 37 | // only wait on local group 38 | w.lg.Wait() 39 | } 40 | -------------------------------------------------------------------------------- /server/wrapper.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // HandlerFunc represents a single method of a handler. It's used primarily 8 | // for the wrappers. What's handed to the actual method is the concrete 9 | // request and response types. 10 | type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error 11 | 12 | // SubscriberFunc represents a single method of a subscriber. It's used primarily 13 | // for the wrappers. What's handed to the actual method is the concrete 14 | // publication message. 15 | type SubscriberFunc func(ctx context.Context, msg Message) error 16 | 17 | // HandlerWrapper wraps the HandlerFunc and returns the equivalent. 18 | type HandlerWrapper func(HandlerFunc) HandlerFunc 19 | 20 | // SubscriberWrapper wraps the SubscriberFunc and returns the equivalent. 21 | type SubscriberWrapper func(SubscriberFunc) SubscriberFunc 22 | 23 | // StreamWrapper wraps a Stream interface and returns the equivalent. 24 | // Because streams exist for the lifetime of a method invocation this 25 | // is a convenient way to wrap a Stream as its in use for trace, monitoring, 26 | // metrics, etc. 27 | type StreamWrapper func(Stream) Stream 28 | -------------------------------------------------------------------------------- /store/mysql/mysql_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package mysql 5 | 6 | import ( 7 | "encoding/json" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | _ "github.com/go-sql-driver/mysql" 13 | "go-micro.dev/v5/store" 14 | ) 15 | 16 | var ( 17 | sqlStoreT store.Store 18 | ) 19 | 20 | func TestMain(m *testing.M) { 21 | if tr := os.Getenv("TRAVIS"); len(tr) > 0 { 22 | os.Exit(0) 23 | } 24 | 25 | sqlStoreT = NewMysqlStore( 26 | store.Database("testMicro"), 27 | store.Nodes("root:123@(127.0.0.1:3306)/test?charset=utf8&parseTime=true&loc=Asia%2FShanghai"), 28 | ) 29 | os.Exit(m.Run()) 30 | } 31 | 32 | func TestWrite(t *testing.T) { 33 | err := sqlStoreT.Write( 34 | &store.Record{ 35 | Key: "test", 36 | Value: []byte("foo2"), 37 | Expiry: time.Second * 200, 38 | }, 39 | ) 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | } 44 | 45 | func TestDelete(t *testing.T) { 46 | err := sqlStoreT.Delete("test") 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | } 51 | 52 | func TestRead(t *testing.T) { 53 | records, err := sqlStoreT.Read("test") 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | 58 | t.Log(string(records[0].Value)) 59 | } 60 | 61 | func TestList(t *testing.T) { 62 | records, err := sqlStoreT.List() 63 | if err != nil { 64 | t.Error(err) 65 | } else { 66 | beauty, _ := json.Marshal(records) 67 | t.Log(string(beauty)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /store/nats-js-kv/context.go: -------------------------------------------------------------------------------- 1 | package natsjskv 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/store" 7 | ) 8 | 9 | // setStoreOption returns a function to setup a context with given value. 10 | func setStoreOption(k, v interface{}) store.Option { 11 | return func(o *store.Options) { 12 | if o.Context == nil { 13 | o.Context = context.Background() 14 | } 15 | 16 | o.Context = context.WithValue(o.Context, k, v) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /store/noop.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | type noopStore struct{} 4 | 5 | func (n *noopStore) Init(opts ...Option) error { 6 | return nil 7 | } 8 | 9 | func (n *noopStore) Options() Options { 10 | return Options{} 11 | } 12 | 13 | func (n *noopStore) String() string { 14 | return "noop" 15 | } 16 | 17 | func (n *noopStore) Read(key string, opts ...ReadOption) ([]*Record, error) { 18 | return []*Record{}, nil 19 | } 20 | 21 | func (n *noopStore) Write(r *Record, opts ...WriteOption) error { 22 | return nil 23 | } 24 | 25 | func (n *noopStore) Delete(key string, opts ...DeleteOption) error { 26 | return nil 27 | } 28 | 29 | func (n *noopStore) List(opts ...ListOption) ([]string, error) { 30 | return []string{}, nil 31 | } 32 | 33 | func (n *noopStore) Close() error { 34 | return nil 35 | } 36 | 37 | func NewNoopStore(opts ...Option) Store { 38 | return new(noopStore) 39 | } 40 | -------------------------------------------------------------------------------- /store/postgres/README.md: -------------------------------------------------------------------------------- 1 | # Postgres plugin 2 | 3 | This module implements a Postgres implementation of the micro store interface. 4 | 5 | ## Implementation notes 6 | 7 | ### Concepts 8 | We maintain a single connection to the Postgres server. Due to the way connections are handled this means that all micro "databases" and "tables" are stored under a single Postgres database as specified in the connection string (https://www.postgresql.org/docs/8.1/ddl-schemas.html). The mapping of micro to Postgres concepts is: 9 | - micro database => Postgres schema 10 | - micro table => Postgres table 11 | 12 | ### Expiry 13 | Expiry is managed by an expiry column in the table. A record's expiry is specified in the column and when a record is read the expiry field is first checked, only returning the record if its still valid otherwise it's deleted. A maintenance loop also periodically runs to delete any rows that have expired. 14 | -------------------------------------------------------------------------------- /store/postgres/metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Asim Aslam 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // Original source: github.com/micro/go-plugins/v3/store/cockroach/metadata.go 16 | 17 | package postgres 18 | 19 | import ( 20 | "database/sql/driver" 21 | "encoding/json" 22 | "errors" 23 | ) 24 | 25 | // https://github.com/upper/db/blob/master/postgresql/custom_types.go#L43 26 | type Metadata map[string]interface{} 27 | 28 | // Scan satisfies the sql.Scanner interface. 29 | func (m *Metadata) Scan(src interface{}) error { 30 | source, ok := src.([]byte) 31 | if !ok { 32 | return errors.New("Type assertion .([]byte) failed.") 33 | } 34 | 35 | var i interface{} 36 | err := json.Unmarshal(source, &i) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | *m, ok = i.(map[string]interface{}) 42 | if !ok { 43 | return errors.New("Type assertion .(map[string]interface{}) failed.") 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // Value satisfies the driver.Valuer interface. 50 | func (m Metadata) Value() (driver.Value, error) { 51 | j, err := json.Marshal(m) 52 | return j, err 53 | } 54 | 55 | func toMetadata(m *Metadata) map[string]interface{} { 56 | md := make(map[string]interface{}) 57 | for k, v := range *m { 58 | md[k] = v 59 | } 60 | return md 61 | } 62 | -------------------------------------------------------------------------------- /store/postgres/pgx/README.md: -------------------------------------------------------------------------------- 1 | # Postgres pgx plugin 2 | 3 | This module implements a Postgres implementation of the micro store interface. 4 | It uses modern https://github.com/jackc/pgx driver to access Postgres. 5 | 6 | ## Implementation notes 7 | 8 | ### Concepts 9 | Every database has they own connection pool. Due to the way connections are handled this means that all micro "databases" and "tables" can be stored under a single or several Postgres database as specified in the connection string (https://www.postgresql.org/docs/8.1/ddl-schemas.html). The mapping of micro to Postgres concepts is: 10 | - micro database => Postgres schema 11 | - micro table => Postgres table 12 | 13 | ### Expiry 14 | Expiry is managed by an expiry column in the table. A record's expiry is specified in the column and when a record is read the expiry field is first checked, only returning the record if it's still valid otherwise it's deleted. A maintenance loop also periodically runs to delete any rows that have expired. 15 | -------------------------------------------------------------------------------- /store/postgres/pgx/db.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | import "github.com/jackc/pgx/v4/pgxpool" 4 | 5 | type DB struct { 6 | conn *pgxpool.Pool 7 | tables map[string]Queries 8 | } 9 | -------------------------------------------------------------------------------- /store/postgres/pgx/metadata.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | type Metadata map[string]interface{} 10 | 11 | // Scan satisfies the sql.Scanner interface. 12 | func (m *Metadata) Scan(src interface{}) error { 13 | source, ok := src.([]byte) 14 | if !ok { 15 | return errors.New("type assertion .([]byte) failed") 16 | } 17 | 18 | var i interface{} 19 | err := json.Unmarshal(source, &i) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | *m, ok = i.(map[string]interface{}) 25 | if !ok { 26 | return errors.New("type assertion .(map[string]interface{}) failed") 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // Value satisfies the driver.Valuer interface. 33 | func (m *Metadata) Value() (driver.Value, error) { 34 | j, err := json.Marshal(m) 35 | return j, err 36 | } 37 | 38 | func toMetadata(m *Metadata) map[string]interface{} { 39 | md := make(map[string]interface{}) 40 | for k, v := range *m { 41 | md[k] = v 42 | } 43 | return md 44 | } 45 | -------------------------------------------------------------------------------- /store/postgres/pgx/queries.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | import "fmt" 4 | 5 | type Queries struct { 6 | // read 7 | ListAsc string 8 | ListAscLimit string 9 | ListDesc string 10 | ListDescLimit string 11 | ReadOne string 12 | ReadManyAsc string 13 | ReadManyAscLimit string 14 | ReadManyDesc string 15 | ReadManyDescLimit string 16 | 17 | // change 18 | Write string 19 | Delete string 20 | DeleteExpired string 21 | } 22 | 23 | func NewQueries(database, table string) Queries { 24 | return Queries{ 25 | ListAsc: fmt.Sprintf(list, database, table) + asc, 26 | ListAscLimit: fmt.Sprintf(list, database, table) + asc + limit, 27 | ListDesc: fmt.Sprintf(list, database, table) + desc, 28 | ListDescLimit: fmt.Sprintf(list, database, table) + desc + limit, 29 | ReadOne: fmt.Sprintf(readOne, database, table), 30 | ReadManyAsc: fmt.Sprintf(readMany, database, table) + asc, 31 | ReadManyAscLimit: fmt.Sprintf(readMany, database, table) + asc + limit, 32 | ReadManyDesc: fmt.Sprintf(readMany, database, table) + desc, 33 | ReadManyDescLimit: fmt.Sprintf(readMany, database, table) + desc + limit, 34 | Write: fmt.Sprintf(write, database, table), 35 | Delete: fmt.Sprintf(deleteRecord, database, table), 36 | DeleteExpired: fmt.Sprintf(deleteExpired, database, table), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /store/postgres/pgx/templates.go: -------------------------------------------------------------------------------- 1 | package pgx 2 | 3 | // init 4 | 5 | const createSchema = "CREATE SCHEMA IF NOT EXISTS %s" 6 | const createTable = `CREATE TABLE IF NOT EXISTS %s.%s 7 | ( 8 | key text primary key, 9 | value bytea, 10 | metadata JSONB, 11 | expiry timestamp with time zone 12 | )` 13 | const createMDIndex = `create index if not exists idx_md_%s ON %s.%s USING GIN (metadata)` 14 | const createExpiryIndex = `create index if not exists idx_expiry_%s on %s.%s (expiry) where (expiry IS NOT NULL)` 15 | 16 | // base queries 17 | const ( 18 | list = "SELECT key FROM %s.%s WHERE key LIKE $1 and (expiry < now() or expiry isnull)" 19 | readOne = "SELECT key, value, metadata, expiry FROM %s.%s WHERE key = $1 and (expiry < now() or expiry isnull)" 20 | readMany = "SELECT key, value, metadata, expiry FROM %s.%s WHERE key LIKE $1 and (expiry < now() or expiry isnull)" 21 | write = `INSERT INTO %s.%s(key, value, metadata, expiry) 22 | VALUES ($1, $2::bytea, $3, $4) 23 | ON CONFLICT (key) 24 | DO UPDATE 25 | SET value = EXCLUDED.value, metadata = EXCLUDED.metadata, expiry = EXCLUDED.expiry` 26 | deleteRecord = "DELETE FROM %s.%s WHERE key = $1" 27 | deleteExpired = "DELETE FROM %s.%s WHERE expiry < now()" 28 | ) 29 | 30 | // suffixes 31 | const ( 32 | limit = " LIMIT $2 OFFSET $3" 33 | asc = " ORDER BY key ASC" 34 | desc = " ORDER BY key DESC" 35 | ) 36 | -------------------------------------------------------------------------------- /test/benchmark.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | "go-micro.dev/v5" 7 | "go-micro.dev/v5/broker" 8 | "go-micro.dev/v5/client" 9 | "go-micro.dev/v5/registry" 10 | "go-micro.dev/v5/server" 11 | "go-micro.dev/v5/transport" 12 | "go-micro.dev/v5/util/test" 13 | ) 14 | 15 | func BenchmarkService(b *testing.B) { 16 | cfg := ServiceTestConfig{ 17 | Name: "test-service", 18 | NewService: newService, 19 | Parallel: []int{1, 8, 16, 32, 64}, 20 | Sequential: []int{0}, 21 | Streams: []int{0}, 22 | // PubSub: []int{10}, 23 | } 24 | 25 | cfg.Run(b) 26 | } 27 | 28 | func newService(name string, opts ...micro.Option) (micro.Service, error) { 29 | r := registry.NewMemoryRegistry( 30 | registry.Services(test.Data), 31 | ) 32 | 33 | b := broker.NewMemoryBroker() 34 | 35 | t := transport.NewHTTPTransport() 36 | c := client.NewClient( 37 | client.Transport(t), 38 | client.Broker(b), 39 | ) 40 | 41 | s := server.NewRPCServer( 42 | server.Name(name), 43 | server.Registry(r), 44 | server.Transport(t), 45 | server.Broker(b), 46 | ) 47 | 48 | if err := s.Init(); err != nil { 49 | return nil, err 50 | } 51 | 52 | options := []micro.Option{ 53 | micro.Name(name), 54 | micro.Server(s), 55 | micro.Client(c), 56 | micro.Registry(r), 57 | micro.Broker(b), 58 | } 59 | options = append(options, opts...) 60 | 61 | srv := micro.NewService(options...) 62 | 63 | return srv, nil 64 | } 65 | -------------------------------------------------------------------------------- /transport/context.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type netListener struct{} 8 | 9 | // getNetListener Get net.Listener from ListenOptions. 10 | func getNetListener(o *ListenOptions) net.Listener { 11 | if o.Context == nil { 12 | return nil 13 | } 14 | 15 | if l, ok := o.Context.Value(netListener{}).(net.Listener); ok && l != nil { 16 | return l 17 | } 18 | 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /transport/grpc/handler.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "runtime/debug" 5 | 6 | "go-micro.dev/v5/errors" 7 | "go-micro.dev/v5/logger" 8 | "go-micro.dev/v5/transport" 9 | pb "go-micro.dev/v5/transport/grpc/proto" 10 | "google.golang.org/grpc/peer" 11 | ) 12 | 13 | // microTransport satisfies the pb.TransportServer inteface. 14 | type microTransport struct { 15 | addr string 16 | fn func(transport.Socket) 17 | } 18 | 19 | func (m *microTransport) Stream(ts pb.Transport_StreamServer) (err error) { 20 | sock := &grpcTransportSocket{ 21 | stream: ts, 22 | local: m.addr, 23 | } 24 | 25 | p, ok := peer.FromContext(ts.Context()) 26 | if ok { 27 | sock.remote = p.Addr.String() 28 | } 29 | 30 | defer func() { 31 | if r := recover(); r != nil { 32 | logger.Error(r, string(debug.Stack())) 33 | sock.Close() 34 | err = errors.InternalServerError("go.micro.transport", "panic recovered: %v", r) 35 | } 36 | }() 37 | 38 | // execute socket func 39 | m.fn(sock) 40 | 41 | return err 42 | } 43 | -------------------------------------------------------------------------------- /transport/grpc/proto/transport.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./proto;transport"; 4 | 5 | package go.micro.transport.grpc; 6 | 7 | service Transport { 8 | rpc Stream(stream Message) returns (stream Message) {} 9 | } 10 | 11 | message Message { 12 | map header = 1; 13 | bytes body = 2; 14 | } 15 | -------------------------------------------------------------------------------- /transport/headers/headers.go: -------------------------------------------------------------------------------- 1 | // headers is a package for internal micro global constants 2 | package headers 3 | 4 | const ( 5 | // Message header is a header for internal message communication. 6 | Message = "Micro-Topic" 7 | // Request header is a message header for internal request communication. 8 | Request = "Micro-Service" 9 | // Error header contains an error message. 10 | Error = "Micro-Error" 11 | // Endpoint header. 12 | Endpoint = "Micro-Endpoint" 13 | // Method header. 14 | Method = "Micro-Method" 15 | // ID header. 16 | ID = "Micro-ID" 17 | // Prefix used to prefix headers. 18 | Prefix = "Micro-" 19 | // Namespace header. 20 | Namespace = "Micro-Namespace" 21 | // Protocol header. 22 | Protocol = "Micro-Protocol" 23 | // Target header. 24 | Target = "Micro-Target" 25 | // ContentType header. 26 | ContentType = "Content-Type" 27 | // SpanID header. 28 | SpanID = "Micro-Span-ID" 29 | // TraceIDKey header. 30 | TraceIDKey = "Micro-Trace-ID" 31 | // Stream header. 32 | Stream = "Micro-Stream" 33 | ) 34 | -------------------------------------------------------------------------------- /transport/nats/options.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/nats-io/nats.go" 7 | "go-micro.dev/v5/transport" 8 | ) 9 | 10 | type optionsKey struct{} 11 | 12 | // Options allow to inject a nats.Options struct for configuring 13 | // the nats connection. 14 | func Options(nopts nats.Options) transport.Option { 15 | return func(o *transport.Options) { 16 | if o.Context == nil { 17 | o.Context = context.Background() 18 | } 19 | o.Context = context.WithValue(o.Context, optionsKey{}, nopts) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /transport/transport.go: -------------------------------------------------------------------------------- 1 | // Package transport is an interface for synchronous connection based communication 2 | package transport 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | // Transport is an interface which is used for communication between 9 | // services. It uses connection based socket send/recv semantics and 10 | // has various implementations; http, grpc, quic. 11 | type Transport interface { 12 | Init(...Option) error 13 | Options() Options 14 | Dial(addr string, opts ...DialOption) (Client, error) 15 | Listen(addr string, opts ...ListenOption) (Listener, error) 16 | String() string 17 | } 18 | 19 | // Message is a broker message. 20 | type Message struct { 21 | Header map[string]string 22 | Body []byte 23 | } 24 | 25 | type Socket interface { 26 | Recv(*Message) error 27 | Send(*Message) error 28 | Close() error 29 | Local() string 30 | Remote() string 31 | } 32 | 33 | type Client interface { 34 | Socket 35 | } 36 | 37 | type Listener interface { 38 | Addr() string 39 | Close() error 40 | Accept(func(Socket)) error 41 | } 42 | 43 | type Option func(*Options) 44 | 45 | type DialOption func(*DialOptions) 46 | 47 | type ListenOption func(*ListenOptions) 48 | 49 | var ( 50 | DefaultTransport Transport = NewHTTPTransport() 51 | 52 | DefaultDialTimeout = time.Second * 5 53 | ) 54 | -------------------------------------------------------------------------------- /util/backoff/backoff.go: -------------------------------------------------------------------------------- 1 | // Package backoff provides backoff functionality 2 | package backoff 3 | 4 | import ( 5 | "math" 6 | "time" 7 | ) 8 | 9 | // Do is a function x^e multiplied by a factor of 0.1 second. 10 | // Result is limited to 2 minute. 11 | func Do(attempts int) time.Duration { 12 | if attempts > 13 { 13 | return 2 * time.Minute 14 | } 15 | return time.Duration(math.Pow(float64(attempts), math.E)) * time.Millisecond * 100 16 | } 17 | -------------------------------------------------------------------------------- /util/buf/buf.go: -------------------------------------------------------------------------------- 1 | package buf 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | type buffer struct { 8 | *bytes.Buffer 9 | } 10 | 11 | func (b *buffer) Close() error { 12 | b.Buffer.Reset() 13 | return nil 14 | } 15 | 16 | func New(b *bytes.Buffer) *buffer { 17 | if b == nil { 18 | b = bytes.NewBuffer(nil) 19 | } 20 | return &buffer{b} 21 | } 22 | -------------------------------------------------------------------------------- /util/grpc/grpc.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // ServiceMethod converts a gRPC method to a Go method 9 | // Input: 10 | // Foo.Bar, /Foo/Bar, /package.Foo/Bar, /a.package.Foo/Bar 11 | // Output: 12 | // [Foo, Bar]. 13 | func ServiceMethod(m string) (string, string, error) { 14 | if len(m) == 0 { 15 | return "", "", fmt.Errorf("malformed method name: %q", m) 16 | } 17 | 18 | // grpc method 19 | if m[0] == '/' { 20 | // [ , Foo, Bar] 21 | // [ , package.Foo, Bar] 22 | // [ , a.package.Foo, Bar] 23 | parts := strings.Split(m, "/") 24 | if len(parts) != 3 || len(parts[1]) == 0 || len(parts[2]) == 0 { 25 | return "", "", fmt.Errorf("malformed method name: %q", m) 26 | } 27 | service := strings.Split(parts[1], ".") 28 | return service[len(service)-1], parts[2], nil 29 | } 30 | 31 | // non grpc method 32 | parts := strings.Split(m, ".") 33 | 34 | // expect [Foo, Bar] 35 | if len(parts) != 2 { 36 | return "", "", fmt.Errorf("malformed method name: %q", m) 37 | } 38 | 39 | return parts[0], parts[1], nil 40 | } 41 | 42 | // ServiceFromMethod returns the service 43 | // /service.Foo/Bar => service. 44 | func ServiceFromMethod(m string) string { 45 | if len(m) == 0 { 46 | return m 47 | } 48 | if m[0] != '/' { 49 | return m 50 | } 51 | parts := strings.Split(m, "/") 52 | if len(parts) < 3 { 53 | return m 54 | } 55 | parts = strings.Split(parts[1], ".") 56 | return strings.Join(parts[:len(parts)-1], ".") 57 | } 58 | -------------------------------------------------------------------------------- /util/grpc/grpc_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestServiceMethod(t *testing.T) { 8 | type testCase struct { 9 | input string 10 | service string 11 | method string 12 | err bool 13 | } 14 | 15 | methods := []testCase{ 16 | {"Foo.Bar", "Foo", "Bar", false}, 17 | {"/Foo/Bar", "Foo", "Bar", false}, 18 | {"/package.Foo/Bar", "Foo", "Bar", false}, 19 | {"/a.package.Foo/Bar", "Foo", "Bar", false}, 20 | {"a.package.Foo/Bar", "", "", true}, 21 | {"/Foo/Bar/Baz", "", "", true}, 22 | {"Foo.Bar.Baz", "", "", true}, 23 | } 24 | for _, test := range methods { 25 | service, method, err := ServiceMethod(test.input) 26 | if err != nil && test.err == true { 27 | continue 28 | } 29 | // unexpected error 30 | if err != nil && test.err == false { 31 | t.Fatalf("unexpected err %v for %+v", err, test) 32 | } 33 | // expecter error 34 | if test.err == true && err == nil { 35 | t.Fatalf("expected error for %+v: got service: %s method: %s", test, service, method) 36 | } 37 | 38 | if service != test.service { 39 | t.Fatalf("wrong service for %+v: got service: %s method: %s", test, service, method) 40 | } 41 | 42 | if method != test.method { 43 | t.Fatalf("wrong method for %+v: got service: %s method: %s", test, service, method) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /util/http/http_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/http" 7 | "testing" 8 | 9 | "go-micro.dev/v5/registry" 10 | ) 11 | 12 | func TestRoundTripper(t *testing.T) { 13 | m := registry.NewMemoryRegistry() 14 | 15 | rt := NewRoundTripper( 16 | WithRegistry(m), 17 | ) 18 | 19 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 20 | w.Write([]byte(`hello world`)) 21 | }) 22 | 23 | l, err := net.Listen("tcp", "127.0.0.1:0") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer l.Close() 28 | 29 | go http.Serve(l, nil) 30 | 31 | m.Register(®istry.Service{ 32 | Name: "example.com", 33 | Nodes: []*registry.Node{ 34 | { 35 | Id: "1", 36 | Address: l.Addr().String(), 37 | }, 38 | }, 39 | }) 40 | 41 | req, err := http.NewRequest("GET", "http://example.com", nil) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | w, err := rt.RoundTrip(req) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | b, err := io.ReadAll(w.Body) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | w.Body.Close() 56 | 57 | if string(b) != "hello world" { 58 | t.Fatal("response is", string(b)) 59 | } 60 | 61 | // test http request 62 | c := &http.Client{ 63 | Transport: rt, 64 | } 65 | 66 | rsp, err := c.Get("http://example.com") 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | b, err = io.ReadAll(rsp.Body) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | rsp.Body.Close() 76 | 77 | if string(b) != "hello world" { 78 | t.Fatal("response is", string(b)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /util/http/options.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | ) 6 | 7 | type Options struct { 8 | Registry registry.Registry 9 | } 10 | 11 | type Option func(*Options) 12 | 13 | func WithRegistry(r registry.Registry) Option { 14 | return func(o *Options) { 15 | o.Registry = r 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /util/http/roundtripper.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "go-micro.dev/v5/selector" 8 | ) 9 | 10 | type roundTripper struct { 11 | rt http.RoundTripper 12 | st selector.Strategy 13 | opts Options 14 | } 15 | 16 | func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 17 | s, err := r.opts.Registry.GetService(req.URL.Host) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | next := r.st(s) 23 | 24 | // rudimentary retry 3 times 25 | for i := 0; i < 3; i++ { 26 | n, err := next() 27 | if err != nil { 28 | continue 29 | } 30 | req.URL.Host = n.Address 31 | w, err := r.rt.RoundTrip(req) 32 | if err != nil { 33 | continue 34 | } 35 | return w, nil 36 | } 37 | 38 | return nil, errors.New("failed request") 39 | } 40 | -------------------------------------------------------------------------------- /util/jitter/jitter.go: -------------------------------------------------------------------------------- 1 | // Package jitter provides a random jitter 2 | package jitter 3 | 4 | import ( 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | var ( 10 | r = rand.New(rand.NewSource(time.Now().UnixNano())) 11 | ) 12 | 13 | // Do returns a random time to jitter with max cap specified. 14 | func Do(d time.Duration) time.Duration { 15 | v := r.Float64() * float64(d.Nanoseconds()) 16 | return time.Duration(v) 17 | } 18 | -------------------------------------------------------------------------------- /util/mdns/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /util/mdns/dns_sd_test.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | type mockMDNSService struct{} 11 | 12 | func (s *mockMDNSService) Records(q dns.Question) []dns.RR { 13 | return []dns.RR{ 14 | &dns.PTR{ 15 | Hdr: dns.RR_Header{ 16 | Name: "fakerecord", 17 | Rrtype: dns.TypePTR, 18 | Class: dns.ClassINET, 19 | Ttl: 42, 20 | }, 21 | Ptr: "fake.local.", 22 | }, 23 | } 24 | } 25 | 26 | func (s *mockMDNSService) Announcement() []dns.RR { 27 | return []dns.RR{ 28 | &dns.PTR{ 29 | Hdr: dns.RR_Header{ 30 | Name: "fakeannounce", 31 | Rrtype: dns.TypePTR, 32 | Class: dns.ClassINET, 33 | Ttl: 42, 34 | }, 35 | Ptr: "fake.local.", 36 | }, 37 | } 38 | } 39 | 40 | func TestDNSSDServiceRecords(t *testing.T) { 41 | s := &DNSSDService{ 42 | MDNSService: &MDNSService{ 43 | serviceAddr: "_foobar._tcp.local.", 44 | Domain: "local", 45 | }, 46 | } 47 | q := dns.Question{ 48 | Name: "_services._dns-sd._udp.local.", 49 | Qtype: dns.TypePTR, 50 | Qclass: dns.ClassINET, 51 | } 52 | recs := s.Records(q) 53 | if got, want := len(recs), 1; got != want { 54 | t.Fatalf("s.Records(%v) returned %v records, want %v", q, got, want) 55 | } 56 | 57 | want := dns.RR(&dns.PTR{ 58 | Hdr: dns.RR_Header{ 59 | Name: "_services._dns-sd._udp.local.", 60 | Rrtype: dns.TypePTR, 61 | Class: dns.ClassINET, 62 | Ttl: defaultTTL, 63 | }, 64 | Ptr: "_foobar._tcp.local.", 65 | }) 66 | if got := recs[0]; !reflect.DeepEqual(got, want) { 67 | t.Errorf("s.Records()[0] = %v, want %v", got, want) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /util/mdns/server_test.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestServer_StartStop(t *testing.T) { 9 | s := makeService(t) 10 | serv, err := NewServer(&Config{Zone: s, LocalhostChecking: true}) 11 | if err != nil { 12 | t.Fatalf("err: %v", err) 13 | } 14 | defer serv.Shutdown() 15 | } 16 | 17 | func TestServer_Lookup(t *testing.T) { 18 | serv, err := NewServer(&Config{Zone: makeServiceWithServiceName(t, "_foobar._tcp"), LocalhostChecking: true}) 19 | if err != nil { 20 | t.Fatalf("err: %v", err) 21 | } 22 | defer serv.Shutdown() 23 | 24 | entries := make(chan *ServiceEntry, 1) 25 | found := false 26 | doneCh := make(chan struct{}) 27 | go func() { 28 | select { 29 | case e := <-entries: 30 | if e.Name != "hostname._foobar._tcp.local." { 31 | t.Fatalf("bad: %v", e) 32 | } 33 | if e.Port != 80 { 34 | t.Fatalf("bad: %v", e) 35 | } 36 | if e.Info != "Local web server" { 37 | t.Fatalf("bad: %v", e) 38 | } 39 | found = true 40 | 41 | case <-time.After(80 * time.Millisecond): 42 | t.Fatalf("timeout") 43 | } 44 | close(doneCh) 45 | }() 46 | 47 | params := &QueryParam{ 48 | Service: "_foobar._tcp", 49 | Domain: "local", 50 | Timeout: 50 * time.Millisecond, 51 | Entries: entries, 52 | } 53 | err = Query(params) 54 | if err != nil { 55 | t.Fatalf("err: %v", err) 56 | } 57 | <-doneCh 58 | if !found { 59 | t.Fatalf("record not found") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /util/net/net_test.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestListen(t *testing.T) { 10 | fn := func(addr string) (net.Listener, error) { 11 | return net.Listen("tcp", addr) 12 | } 13 | 14 | // try to create a number of listeners 15 | for i := 0; i < 10; i++ { 16 | l, err := Listen("localhost:10000-11000", fn) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | defer l.Close() 21 | } 22 | 23 | // TODO nats case test 24 | // natsAddr := "_INBOX.bID2CMRvlNp0vt4tgNBHWf" 25 | // Expect addr DO NOT has extra ":" at the end! 26 | } 27 | 28 | // TestProxyEnv checks whether we have proxy/network settings in env. 29 | func TestProxyEnv(t *testing.T) { 30 | service := "foo" 31 | address := []string{"bar"} 32 | 33 | s, a, ok := Proxy(service, address) 34 | if ok { 35 | t.Fatal("Should not have proxy", s, a, ok) 36 | } 37 | 38 | test := func(key, val, expectSrv, expectAddr string) { 39 | // set env 40 | os.Setenv(key, val) 41 | 42 | s, a, ok := Proxy(service, address) 43 | if !ok { 44 | t.Fatal("Expected proxy") 45 | } 46 | if len(expectSrv) > 0 && s != expectSrv { 47 | t.Fatal("Expected proxy service", expectSrv, "got", s) 48 | } 49 | if len(expectAddr) > 0 { 50 | if len(a) == 0 || a[0] != expectAddr { 51 | t.Fatal("Expected proxy address", expectAddr, "got", a) 52 | } 53 | } 54 | 55 | os.Unsetenv(key) 56 | } 57 | 58 | test("MICRO_PROXY", "service", "go.micro.proxy", "") 59 | test("MICRO_NETWORK", "service", "go.micro.network", "") 60 | test("MICRO_NETWORK_ADDRESS", "10.0.0.1:8081", "", "10.0.0.1:8081") 61 | } 62 | -------------------------------------------------------------------------------- /util/pool/default_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "go-micro.dev/v5/transport" 8 | ) 9 | 10 | func testPool(t *testing.T, size int, ttl time.Duration) { 11 | // mock transport 12 | tr := transport.NewMemoryTransport() 13 | 14 | options := Options{ 15 | TTL: ttl, 16 | Size: size, 17 | Transport: tr, 18 | } 19 | // zero pool 20 | p := newPool(options) 21 | 22 | // listen 23 | l, err := tr.Listen(":0") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer l.Close() 28 | 29 | // accept loop 30 | go func() { 31 | for { 32 | if err := l.Accept(func(s transport.Socket) { 33 | for { 34 | var msg transport.Message 35 | if err := s.Recv(&msg); err != nil { 36 | return 37 | } 38 | if err := s.Send(&msg); err != nil { 39 | return 40 | } 41 | } 42 | }); err != nil { 43 | return 44 | } 45 | } 46 | }() 47 | 48 | for i := 0; i < 10; i++ { 49 | // get a conn 50 | c, err := p.Get(l.Addr()) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | msg := &transport.Message{ 56 | Body: []byte(`hello world`), 57 | } 58 | 59 | if err := c.Send(msg); err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | var rcv transport.Message 64 | 65 | if err := c.Recv(&rcv); err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | if string(rcv.Body) != string(msg.Body) { 70 | t.Fatalf("got %v, expected %v", rcv.Body, msg.Body) 71 | } 72 | 73 | // release the conn 74 | p.Release(c, nil) 75 | 76 | p.mu.Lock() 77 | if i := len(p.conns[l.Addr()]); i > size { 78 | p.mu.Unlock() 79 | t.Fatalf("pool size %d is greater than expected %d", i, size) 80 | } 81 | p.mu.Unlock() 82 | } 83 | } 84 | 85 | func TestClientPool(t *testing.T) { 86 | testPool(t, 0, time.Minute) 87 | testPool(t, 2, time.Minute) 88 | } 89 | -------------------------------------------------------------------------------- /util/pool/options.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "time" 5 | 6 | "go-micro.dev/v5/transport" 7 | ) 8 | 9 | type Options struct { 10 | Transport transport.Transport 11 | TTL time.Duration 12 | CloseTimeout time.Duration 13 | Size int 14 | } 15 | 16 | type Option func(*Options) 17 | 18 | func Size(i int) Option { 19 | return func(o *Options) { 20 | o.Size = i 21 | } 22 | } 23 | 24 | func Transport(t transport.Transport) Option { 25 | return func(o *Options) { 26 | o.Transport = t 27 | } 28 | } 29 | 30 | func TTL(t time.Duration) Option { 31 | return func(o *Options) { 32 | o.TTL = t 33 | } 34 | } 35 | 36 | func CloseTimeout(t time.Duration) Option { 37 | return func(o *Options) { 38 | o.CloseTimeout = t 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /util/pool/pool.go: -------------------------------------------------------------------------------- 1 | // Package pool is a connection pool 2 | package pool 3 | 4 | import ( 5 | "time" 6 | 7 | "go-micro.dev/v5/transport" 8 | ) 9 | 10 | // Pool is an interface for connection pooling. 11 | type Pool interface { 12 | // Close the pool 13 | Close() error 14 | // Get a connection 15 | Get(addr string, opts ...transport.DialOption) (Conn, error) 16 | // Release the connection 17 | Release(c Conn, status error) error 18 | } 19 | 20 | // Conn interface represents a pool connection. 21 | type Conn interface { 22 | // unique id of connection 23 | Id() string 24 | // time it was created 25 | Created() time.Time 26 | // embedded connection 27 | transport.Client 28 | } 29 | 30 | // NewPool will return a new pool object. 31 | func NewPool(opts ...Option) Pool { 32 | var options Options 33 | for _, o := range opts { 34 | o(&options) 35 | } 36 | 37 | return newPool(options) 38 | } 39 | -------------------------------------------------------------------------------- /util/registry/util_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "go-micro.dev/v5/registry" 8 | ) 9 | 10 | func TestRemove(t *testing.T) { 11 | services := []*registry.Service{ 12 | { 13 | Name: "foo", 14 | Version: "1.0.0", 15 | Nodes: []*registry.Node{ 16 | { 17 | Id: "foo-123", 18 | Address: "localhost:9999", 19 | }, 20 | }, 21 | }, 22 | { 23 | Name: "foo", 24 | Version: "1.0.0", 25 | Nodes: []*registry.Node{ 26 | { 27 | Id: "foo-123", 28 | Address: "localhost:6666", 29 | }, 30 | }, 31 | }, 32 | } 33 | 34 | servs := Remove([]*registry.Service{services[0]}, []*registry.Service{services[1]}) 35 | if i := len(servs); i > 0 { 36 | t.Errorf("Expected 0 nodes, got %d: %+v", i, servs) 37 | } 38 | if len(os.Getenv("IN_TRAVIS_CI")) == 0 { 39 | t.Logf("Services %+v", servs) 40 | } 41 | } 42 | 43 | func TestRemoveNodes(t *testing.T) { 44 | services := []*registry.Service{ 45 | { 46 | Name: "foo", 47 | Version: "1.0.0", 48 | Nodes: []*registry.Node{ 49 | { 50 | Id: "foo-123", 51 | Address: "localhost:9999", 52 | }, 53 | { 54 | Id: "foo-321", 55 | Address: "localhost:6666", 56 | }, 57 | }, 58 | }, 59 | { 60 | Name: "foo", 61 | Version: "1.0.0", 62 | Nodes: []*registry.Node{ 63 | { 64 | Id: "foo-123", 65 | Address: "localhost:6666", 66 | }, 67 | }, 68 | }, 69 | } 70 | 71 | nodes := delNodes(services[0].Nodes, services[1].Nodes) 72 | if i := len(nodes); i != 1 { 73 | t.Errorf("Expected only 1 node, got %d: %+v", i, nodes) 74 | } 75 | if len(os.Getenv("IN_TRAVIS_CI")) == 0 { 76 | t.Logf("Nodes %+v", nodes) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /util/ring/buffer_test.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestBuffer(t *testing.T) { 9 | b := New(10) 10 | 11 | // test one value 12 | b.Put("foo") 13 | v := b.Get(1) 14 | 15 | if val := v[0].Value.(string); val != "foo" { 16 | t.Fatalf("expected foo got %v", val) 17 | } 18 | 19 | b = New(10) 20 | 21 | // test 10 values 22 | for i := 0; i < 10; i++ { 23 | b.Put(i) 24 | } 25 | 26 | d := time.Now() 27 | v = b.Get(10) 28 | 29 | for i := 0; i < 10; i++ { 30 | val := v[i].Value.(int) 31 | 32 | if val != i { 33 | t.Fatalf("expected %d got %d", i, val) 34 | } 35 | } 36 | 37 | // test more values 38 | 39 | for i := 0; i < 10; i++ { 40 | v := i * 2 41 | b.Put(v) 42 | } 43 | 44 | v = b.Get(10) 45 | 46 | for i := 0; i < 10; i++ { 47 | val := v[i].Value.(int) 48 | expect := i * 2 49 | if val != expect { 50 | t.Fatalf("expected %d got %d", expect, val) 51 | } 52 | } 53 | 54 | // sleep 100 ms 55 | time.Sleep(time.Millisecond * 100) 56 | 57 | // assume we'll get everything 58 | v = b.Since(d) 59 | 60 | if len(v) != 10 { 61 | t.Fatalf("expected 10 entries but got %d", len(v)) 62 | } 63 | 64 | // write 1 more entry 65 | d = time.Now() 66 | b.Put(100) 67 | 68 | // sleep 100 ms 69 | time.Sleep(time.Millisecond * 100) 70 | 71 | v = b.Since(d) 72 | if len(v) != 1 { 73 | t.Fatalf("expected 1 entries but got %d", len(v)) 74 | } 75 | 76 | if v[0].Value.(int) != 100 { 77 | t.Fatalf("expected value 100 got %v", v[0]) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /util/signal/signal.go: -------------------------------------------------------------------------------- 1 | package signal 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | ) 7 | 8 | // ShutDownSingals returns all the signals that are being watched for to shut down services. 9 | func Shutdown() []os.Signal { 10 | return []os.Signal{ 11 | syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /util/socket/pool.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Pool struct { 8 | pool map[string]*Socket 9 | sync.RWMutex 10 | } 11 | 12 | func (p *Pool) Get(id string) (*Socket, bool) { 13 | // attempt to get existing socket 14 | p.RLock() 15 | socket, ok := p.pool[id] 16 | if ok { 17 | p.RUnlock() 18 | return socket, ok 19 | } 20 | p.RUnlock() 21 | 22 | // save socket 23 | p.Lock() 24 | defer p.Unlock() 25 | // double checked locking 26 | socket, ok = p.pool[id] 27 | if ok { 28 | return socket, ok 29 | } 30 | // create new socket 31 | socket = New(id) 32 | p.pool[id] = socket 33 | 34 | // return socket 35 | return socket, false 36 | } 37 | 38 | func (p *Pool) Release(s *Socket) { 39 | p.Lock() 40 | defer p.Unlock() 41 | 42 | // close the socket 43 | s.Close() 44 | delete(p.pool, s.id) 45 | } 46 | 47 | // Close the pool and delete all the sockets. 48 | func (p *Pool) Close() { 49 | p.Lock() 50 | defer p.Unlock() 51 | for id, sock := range p.pool { 52 | sock.Close() 53 | delete(p.pool, id) 54 | } 55 | } 56 | 57 | // NewPool returns a new socket pool. 58 | func NewPool() *Pool { 59 | return &Pool{ 60 | pool: make(map[string]*Socket), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /util/test/test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | ) 6 | 7 | var ( 8 | // Data is a set of mock registry data. 9 | Data = map[string][]*registry.Service{ 10 | "foo": { 11 | { 12 | Name: "foo", 13 | Version: "1.0.0", 14 | Nodes: []*registry.Node{ 15 | { 16 | Id: "foo-1.0.0-123", 17 | Address: "localhost:9999", 18 | }, 19 | { 20 | Id: "foo-1.0.0-321", 21 | Address: "localhost:9999", 22 | }, 23 | }, 24 | }, 25 | { 26 | Name: "foo", 27 | Version: "1.0.1", 28 | Nodes: []*registry.Node{ 29 | { 30 | Id: "foo-1.0.1-321", 31 | Address: "localhost:6666", 32 | }, 33 | }, 34 | }, 35 | { 36 | Name: "foo", 37 | Version: "1.0.3", 38 | Nodes: []*registry.Node{ 39 | { 40 | Id: "foo-1.0.3-345", 41 | Address: "localhost:8888", 42 | }, 43 | }, 44 | }, 45 | }, 46 | } 47 | ) 48 | 49 | // EmptyChannel will empty out a error channel by checking if an error is 50 | // present, and if so return the error. 51 | func EmptyChannel(c chan error) error { 52 | select { 53 | case err := <-c: 54 | return err 55 | default: 56 | return nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | // Package web provides web based micro services 2 | package web 3 | 4 | import ( 5 | "context" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/google/uuid" 10 | ) 11 | 12 | // Service is a web service with service discovery built in. 13 | type Service interface { 14 | Client() *http.Client 15 | Init(opts ...Option) error 16 | Options() Options 17 | Handle(pattern string, handler http.Handler) 18 | HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) 19 | Start() error 20 | Stop() error 21 | Run() error 22 | } 23 | 24 | // Option for web. 25 | type Option func(o *Options) 26 | 27 | // Web basic Defaults. 28 | var ( 29 | // For serving. 30 | DefaultName = "go-web" 31 | DefaultVersion = "latest" 32 | DefaultId = uuid.New().String() 33 | DefaultAddress = ":0" 34 | 35 | // for registration. 36 | DefaultRegisterTTL = time.Second * 90 37 | DefaultRegisterInterval = time.Second * 30 38 | 39 | // static directory. 40 | DefaultStaticDir = "html" 41 | DefaultRegisterCheck = func(context.Context) error { return nil } 42 | ) 43 | 44 | // NewService returns a new web.Service. 45 | func NewService(opts ...Option) Service { 46 | return newService(opts...) 47 | } 48 | -------------------------------------------------------------------------------- /web/web_test.go: -------------------------------------------------------------------------------- 1 | package web_test 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/urfave/cli/v2" 10 | "go-micro.dev/v5" 11 | "go-micro.dev/v5/logger" 12 | "go-micro.dev/v5/web" 13 | ) 14 | 15 | func TestWeb(t *testing.T) { 16 | for i := 0; i < 10; i++ { 17 | testFunc() 18 | } 19 | } 20 | 21 | func testFunc() { 22 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*250) 23 | defer cancel() 24 | 25 | service := micro.NewService( 26 | micro.Name("test"), 27 | micro.Context(ctx), 28 | micro.HandleSignal(false), 29 | micro.Flags( 30 | &cli.StringFlag{ 31 | Name: "test.timeout", 32 | }, 33 | &cli.BoolFlag{ 34 | Name: "test.v", 35 | }, 36 | &cli.StringFlag{ 37 | Name: "test.run", 38 | }, 39 | &cli.StringFlag{ 40 | Name: "test.testlogfile", 41 | }, 42 | ), 43 | ) 44 | w := web.NewService( 45 | web.MicroService(service), 46 | web.Context(ctx), 47 | web.HandleSignal(false), 48 | ) 49 | // s.Init() 50 | // w.Init() 51 | 52 | var wg sync.WaitGroup 53 | wg.Add(2) 54 | go func() { 55 | defer wg.Done() 56 | err := service.Run() 57 | if err != nil { 58 | logger.Logf(logger.ErrorLevel, "micro run error: %v", err) 59 | } 60 | }() 61 | go func() { 62 | defer wg.Done() 63 | err := w.Run() 64 | if err != nil { 65 | logger.Logf(logger.ErrorLevel, "web run error: %v", err) 66 | } 67 | }() 68 | 69 | wg.Wait() 70 | } 71 | -------------------------------------------------------------------------------- /wrapper/trace/opentelemetry/README.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry wrappers 2 | 3 | OpenTelemetry wrappers propagate traces (spans) accross services. 4 | 5 | ## Usage 6 | 7 | ```go 8 | service := micro.NewService( 9 | micro.Name("go.micro.srv.greeter"), 10 | micro.WrapClient(opentelemetry.NewClientWrapper()), 11 | micro.WrapHandler(opentelemetry.NewHandlerWrapper()), 12 | micro.WrapSubscriber(opentelemetry.NewSubscriberWrapper()), 13 | ) 14 | ``` -------------------------------------------------------------------------------- /wrapper/trace/opentelemetry/opentelemetry.go: -------------------------------------------------------------------------------- 1 | package opentelemetry 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "go-micro.dev/v5/metadata" 8 | "go.opentelemetry.io/otel" 9 | "go.opentelemetry.io/otel/baggage" 10 | "go.opentelemetry.io/otel/propagation" 11 | "go.opentelemetry.io/otel/trace" 12 | ) 13 | 14 | const ( 15 | instrumentationName = "github.com/micro/plugins/v5/wrapper/trace/opentelemetry" 16 | ) 17 | 18 | // StartSpanFromContext returns a new span with the given operation name and options. If a span 19 | // is found in the context, it will be used as the parent of the resulting span. 20 | func StartSpanFromContext(ctx context.Context, tp trace.TracerProvider, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { 21 | md, ok := metadata.FromContext(ctx) 22 | if !ok { 23 | md = make(metadata.Metadata) 24 | } 25 | propagator, carrier := otel.GetTextMapPropagator(), make(propagation.MapCarrier) 26 | for k, v := range md { 27 | for _, f := range propagator.Fields() { 28 | if strings.EqualFold(k, f) { 29 | carrier[f] = v 30 | } 31 | } 32 | } 33 | ctx = propagator.Extract(ctx, carrier) 34 | spanCtx := trace.SpanContextFromContext(ctx) 35 | ctx = baggage.ContextWithBaggage(ctx, baggage.FromContext(ctx)) 36 | 37 | var tracer trace.Tracer 38 | var span trace.Span 39 | if tp != nil { 40 | tracer = tp.Tracer(instrumentationName) 41 | } else { 42 | tracer = otel.Tracer(instrumentationName) 43 | } 44 | ctx, span = tracer.Start(trace.ContextWithRemoteSpanContext(ctx, spanCtx), name, opts...) 45 | 46 | carrier = make(propagation.MapCarrier) 47 | propagator.Inject(ctx, carrier) 48 | for k, v := range carrier { 49 | //lint:ignore SA1019 no unicode punctution handle needed 50 | md.Set(strings.Title(k), v) 51 | } 52 | ctx = metadata.NewContext(ctx, md) 53 | 54 | return ctx, span 55 | } 56 | --------------------------------------------------------------------------------