├── .github
├── FUNDING.yml
├── 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
├── micro
│ ├── README.md
│ ├── cli
│ │ ├── README.md
│ │ ├── cli.go
│ │ ├── new
│ │ │ ├── new.go
│ │ │ └── template
│ │ │ │ ├── handler.go
│ │ │ │ ├── ignore.go
│ │ │ │ ├── main.go
│ │ │ │ ├── makefile.go
│ │ │ │ ├── module.go
│ │ │ │ ├── proto.go
│ │ │ │ └── readme.go
│ │ └── util
│ │ │ ├── dynamic.go
│ │ │ ├── dynamic_test.go
│ │ │ └── util.go
│ ├── main.go
│ ├── run
│ │ └── run.go
│ ├── server
│ │ ├── server.go
│ │ └── util_jwt.go
│ └── web
│ │ ├── main.js
│ │ ├── styles.css
│ │ └── templates
│ │ ├── api.html
│ │ ├── auth_login.html
│ │ ├── auth_tokens.html
│ │ ├── auth_users.html
│ │ ├── base.html
│ │ ├── form.html
│ │ ├── home.html
│ │ ├── log.html
│ │ ├── logs.html
│ │ ├── service.html
│ │ └── status.html
├── options.go
└── protoc-gen-micro
│ ├── README.md
│ ├── examples
│ └── greeter
│ │ ├── greeter.pb.go
│ │ ├── greeter.pb.micro.go
│ │ └── greeter.proto
│ ├── generator
│ ├── Makefile
│ ├── generator.go
│ └── name_test.go
│ ├── main.go
│ └── plugin
│ └── micro
│ └── micro.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
├── events
├── events.go
├── memory.go
├── natsjs
│ ├── README.md
│ ├── helpers_test.go
│ ├── nats.go
│ ├── nats_test.go
│ └── options.go
├── options.go
├── store.go
├── store_test.go
└── stream_test.go
├── genai
├── default.go
├── gemini
│ └── gemini.go
├── genai.go
├── noop.go
├── openai
│ ├── openai.go
│ └── openai_test.go
└── options.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_events_test.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
├── http2_buf_pool.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/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: asim
2 |
--------------------------------------------------------------------------------
/.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/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/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_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 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/handler.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | HandlerSRV = `package handler
5 |
6 | import (
7 | "context"
8 |
9 | log "go-micro.dev/v5/logger"
10 |
11 | pb "{{.Dir}}/proto"
12 | )
13 |
14 | type {{title .Alias}} struct{}
15 |
16 | // Return a new handler
17 | func New() *{{title .Alias}} {
18 | return &{{title .Alias}}{}
19 | }
20 |
21 | // Call is a single request handler called via client.Call or the generated client code
22 | func (e *{{title .Alias}}) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
23 | log.Info("Received {{title .Alias}}.Call request")
24 | rsp.Msg = "Hello " + req.Name
25 | return nil
26 | }
27 |
28 | // Stream is a server side stream handler called via client.Stream or the generated client code
29 | func (e *{{title .Alias}}) Stream(ctx context.Context, req *pb.StreamingRequest, stream pb.{{title .Alias}}_StreamStream) error {
30 | log.Infof("Received {{title .Alias}}.Stream request with count: %d", req.Count)
31 |
32 | for i := 0; i < int(req.Count); i++ {
33 | log.Infof("Responding: %d", i)
34 | if err := stream.Send(&pb.StreamingResponse{
35 | Count: int64(i),
36 | }); err != nil {
37 | return err
38 | }
39 | }
40 |
41 | return nil
42 | }
43 | `
44 |
45 | SubscriberSRV = `package subscriber
46 |
47 | import (
48 | "context"
49 | log "go-micro.dev/v5/logger"
50 |
51 | pb "{{.Dir}}/proto"
52 | )
53 |
54 | type {{title .Alias}} struct{}
55 |
56 | func (e *{{title .Alias}}) Handle(ctx context.Context, msg *pb.Message) error {
57 | log.Info("Handler Received message: ", msg.Say)
58 | return nil
59 | }
60 |
61 | func Handler(ctx context.Context, msg *pb.Message) error {
62 | log.Info("Function Received message: ", msg.Say)
63 | return nil
64 | }
65 | `
66 | )
67 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/ignore.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | GitIgnore = `
5 | {{.Alias}}
6 | `
7 | )
8 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/main.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | MainSRV = `package main
5 |
6 | import (
7 | "{{.Dir}}/handler"
8 | pb "{{.Dir}}/proto"
9 |
10 | "go-micro.dev/v5"
11 | )
12 |
13 | func main() {
14 | // Create service
15 | service := micro.New("{{lower .Alias}}")
16 |
17 | // Initialize service
18 | service.Init()
19 |
20 | // Register handler
21 | pb.Register{{title .Alias}}Handler(service.Server(), handler.New())
22 |
23 | // Run service
24 | service.Run()
25 | }
26 | `
27 | )
28 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/makefile.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | Makefile = `
5 | GOPATH:=$(shell go env GOPATH)
6 | .PHONY: init
7 | init:
8 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
9 | go install go-micro.dev/v5/cmd/protoc-gen-micro@latest
10 | go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
11 |
12 | .PHONY: api
13 | api:
14 | protoc --openapi_out=. --proto_path=. proto/{{.Alias}}.proto
15 |
16 | .PHONY: proto
17 | proto:
18 | protoc --proto_path=. --micro_out=. --go_out=:. proto/{{.Alias}}.proto
19 |
20 | .PHONY: build
21 | build:
22 | go build -o {{.Alias}} *.go
23 |
24 | .PHONY: test
25 | test:
26 | go test -v ./... -cover
27 |
28 | .PHONY: docker
29 | docker:
30 | docker build . -t {{.Alias}}:latest
31 | `
32 | )
33 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/module.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | Module = `module {{.Dir}}
5 |
6 | go 1.18
7 |
8 | require (
9 | go-micro.dev/v5 latest
10 | github.com/golang/protobuf latest
11 | google.golang.org/protobuf latest
12 | )
13 | `
14 | )
15 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/proto.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | ProtoSRV = `syntax = "proto3";
5 |
6 | package {{dehyphen .Alias}};
7 |
8 | option go_package = "./proto;{{dehyphen .Alias}}";
9 |
10 | service {{title .Alias}} {
11 | rpc Call(Request) returns (Response) {}
12 | rpc Stream(StreamingRequest) returns (stream StreamingResponse) {}
13 | }
14 |
15 | message Message {
16 | string say = 1;
17 | }
18 |
19 | message Request {
20 | string name = 1;
21 | }
22 |
23 | message Response {
24 | string msg = 1;
25 | }
26 |
27 | message StreamingRequest {
28 | int64 count = 1;
29 | }
30 |
31 | message StreamingResponse {
32 | int64 count = 1;
33 | }
34 | `
35 | )
36 |
--------------------------------------------------------------------------------
/cmd/micro/cli/new/template/readme.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var (
4 | Readme = `# {{title .Alias}} Service
5 |
6 | This is the {{title .Alias}} service
7 |
8 | Generated with
9 |
10 | ` + "```" +
11 | `
12 | micro new {{.Alias}}
13 | ` + "```" + `
14 |
15 | ## Usage
16 |
17 | Generate the proto code
18 |
19 | ` + "```" +
20 | `
21 | make proto
22 | ` + "```" + `
23 |
24 | Run the service
25 |
26 | ` + "```" +
27 | `
28 | micro run .
29 | ` + "```"
30 | )
31 |
--------------------------------------------------------------------------------
/cmd/micro/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "embed"
5 | "go-micro.dev/v5/cmd"
6 |
7 | _ "go-micro.dev/v5/cmd/micro/cli"
8 | _ "go-micro.dev/v5/cmd/micro/run"
9 | "go-micro.dev/v5/cmd/micro/server"
10 | )
11 |
12 | //go:embed web/styles.css web/main.js web/templates/*
13 | var webFS embed.FS
14 |
15 | var version = "5.0.0-dev"
16 |
17 | func init() {
18 | server.HTML = webFS
19 | }
20 |
21 | func main() {
22 | cmd.Init(
23 | cmd.Name("micro"),
24 | cmd.Version(version),
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/cmd/micro/web/main.js:
--------------------------------------------------------------------------------
1 | // Minimal JS for reactive form submissions
2 |
3 | document.addEventListener('DOMContentLoaded', function() {
4 | document.querySelectorAll('form[data-reactive]')?.forEach(function(form) {
5 | form.addEventListener('submit', async function(e) {
6 | e.preventDefault();
7 | const formData = new FormData(form);
8 | const params = {};
9 | for (const [key, value] of formData.entries()) {
10 | params[key] = value;
11 | }
12 | const action = form.getAttribute('action');
13 | const method = form.getAttribute('method') || 'POST';
14 | try {
15 | const resp = await fetch(action, {
16 | method,
17 | headers: { 'Content-Type': 'application/json' },
18 | body: JSON.stringify(params)
19 | });
20 | const data = await resp.json();
21 | // Find or create a response container
22 | let respDiv = form.querySelector('.js-response');
23 | if (!respDiv) {
24 | respDiv = document.createElement('div');
25 | respDiv.className = 'js-response';
26 | form.appendChild(respDiv);
27 | }
28 | respDiv.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
29 | } catch (err) {
30 | alert('Error: ' + err);
31 | }
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/auth_login.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2 class="text-2xl font-bold mb-4">Login</h2>
3 | <form method="POST" action="/auth/login" style="max-width:340px; margin:2em 0;">
4 | <div style="margin-bottom:1.2em;">
5 | <input name="id" placeholder="Username" required style="width:100%; padding:0.7em;">
6 | </div>
7 | <div style="margin-bottom:1.2em;">
8 | <input name="password" type="password" placeholder="Password" required style="width:100%; padding:0.7em;">
9 | </div>
10 | <button type="submit" style="width:100%; padding:0.7em;">Login</button>
11 | </form>
12 | {{if .Error}}
13 | <div style="color:#c00; margin-top:1em;">{{.Error}}</div>
14 | {{end}}
15 | {{end}}
16 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/auth_users.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2 class="text-2xl font-bold mb-4">User Accounts</h2>
3 | <table style="margin-bottom:2em;">
4 | <thead>
5 | <tr><th>ID</th><th>Type</th><th>Scopes</th><th>Metadata</th><th>Delete</th></tr>
6 | </thead>
7 | <tbody>
8 | {{range .Users}}
9 | <tr>
10 | <td>{{.ID}}</td>
11 | <td>{{.Type}}</td>
12 | <td>{{range .Scopes}}<code>{{.}}</code> {{end}}</td>
13 | <td>
14 | {{range $k, $v := .Metadata}}
15 | {{if ne $k "password_hash"}}
16 | <b>{{$k}}</b>: {{$v}}
17 | {{end}}
18 | {{end}}
19 | </td>
20 | <td>
21 | <form method="POST" action="/auth/users" style="display:inline; padding: 0; border: 0">
22 | <input type="hidden" name="delete" value="{{.ID}}">
23 | <button type="submit" onclick="return confirm('Delete user {{.ID}}?')">Delete</button>
24 | </form>
25 | </td>
26 | </tr>
27 | {{end}}
28 | </tbody>
29 | </table>
30 | <h3 style="margin-bottom:1em;">Create New User</h3>
31 | <form method="POST" action="/auth/users">
32 | <input name="id" placeholder="Username" required style="margin-right:1em;">
33 | <input name="password" type="password" placeholder="Password" required style="margin-right:1em;">
34 | <select name="type" style="margin-right:1em;">
35 | <option value="user">User</option>
36 | <option value="admin">Admin</option>
37 | </select>
38 | <button type="submit">Create</button>
39 | </form>
40 | {{end}}
41 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/form.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2>{{.ServiceName}}</h2>
3 | <form action="/{{.Action}}" method="POST" data-reactive>
4 | <h3 class="text-lg font-bold mb-2">{{.EndpointName}}</h3>
5 | {{range .Inputs}}
6 | <label class="block font-semibold">{{.Label}}</label>
7 | <input name="{{.Name}}" placeholder="{{.Placeholder}}" class="border rounded px-2 py-1 mb-2 w-full" value="{{.Value}}">
8 | {{end}}
9 | <button class="micro-link mt-2" type="submit">Submit</button>
10 | <div class="js-response"></div>
11 | </form>
12 | {{if .Error}}
13 | <div class="mt-4 text-red-600 font-bold">Error: {{.Error}}</div>
14 | {{end}}
15 | {{if .Response}}
16 | <div class="mt-4">
17 | <h4 class="font-bold mb-2">Response</h4>
18 | {{.ResponseTable}}
19 | <pre class="bg-gray-100 rounded p-2 mt-2">{{.ResponseJSON}}</pre>
20 | </div>
21 | {{end}}
22 | <script src="/main.js"></script>
23 | {{end}}
24 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/home.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2 class="text-2xl font-bold mb-4">Dashboard</h2>
3 | <div style="display:flex; align-items:center; gap:2em; margin-bottom:2em;">
4 | <div style="display:flex; align-items:center; gap:0.5em;">
5 | <span style="font-size:2.2em; vertical-align:middle;">
6 | {{if eq .StatusDot "green"}}
7 | <span style="display:inline-block; width:1em; height:1em; background:#2ecc40; border-radius:50%;"></span>
8 | {{else if eq .StatusDot "yellow"}}
9 | <span style="display:inline-block; width:1em; height:1em; background:#ffcc00; border-radius:50%;"></span>
10 | {{else}}
11 | <span style="display:inline-block; width:1em; height:1em; background:#ff4136; border-radius:50%;"></span>
12 | {{end}}
13 | </span>
14 | <span style="font-size:1.2em; font-weight:bold;">Status</span>
15 | </div>
16 | <div style="font-size:1.1em;">Services: <b>{{.ServiceCount}}</b></div>
17 | <div style="font-size:1.1em; color:#2ecc40;">Running: <b>{{.RunningCount}}</b></div>
18 | <div style="font-size:1.1em; color:#ff4136;">Stopped: <b>{{.StoppedCount}}</b></div>
19 | </div>
20 | <p>Welcome to the Micro dashboard. Use the sidebar to navigate services, logs, status, and API.</p>
21 | {{end}}
22 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/log.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2 class="text-2xl font-bold mb-4">Logs for {{.Service}}</h2>
3 | <pre class="bg-gray-100 rounded p-2 mt-2" style="max-height: 60vh; overflow-y: auto;">{{.Log}}</pre>
4 | <a href="/logs" class="micro-link">Back to logs</a>
5 | {{end}}
6 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/logs.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2 class="text-2xl font-bold mb-4">Logs</h2>
3 | <ul class="no-bullets">
4 | {{range .Services}}
5 | <li><a href="/logs/{{.}}" class="micro-link">{{.}}</a></li>
6 | {{end}}
7 | </ul>
8 | {{end}}
9 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/service.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | {{if .ServiceName}}
3 | <h2 class="text-xl font-bold mb-2">{{.ServiceName}}</h2>
4 | <h4 class="font-semibold mb-2">Endpoints</h4>
5 | {{if .Endpoints}}
6 | {{range .Endpoints}}
7 | <div><a href="{{.Path}}" class="micro-link">{{.Name}}</a></div>
8 | {{end}}
9 | {{else}}
10 | <p>No endpoints registered</p>
11 | {{end}}
12 | <h4 class="font-semibold mt-4 mb-2">Description</h4>
13 | <pre class="bg-gray-100 rounded p-2">{{.Description}}</pre>
14 | {{else}}
15 | <h2 class="text-2xl font-bold mb-4">Services</h2>
16 | {{if .Services}}
17 | <ul class="no-bullets">
18 | {{range .Services}}
19 | <li><a href="/{{.}}" class="micro-link">{{.}}</a></li>
20 | {{end}}
21 | </ul>
22 | {{else}}
23 | <p>No services registered</p>
24 | {{end}}
25 | {{end}}
26 | {{end}}
27 |
--------------------------------------------------------------------------------
/cmd/micro/web/templates/status.html:
--------------------------------------------------------------------------------
1 | {{define "content"}}
2 | <h2 class="text-2xl font-bold mb-4">Service Status</h2>
3 | <table>
4 | <thead>
5 | <tr>
6 | <th>Service</th>
7 | <th>Directory</th>
8 | <th>Status</th>
9 | <th>PID</th>
10 | <th>Uptime</th>
11 | <th>ID</th>
12 | <th>Logs</th>
13 | </tr>
14 | </thead>
15 | <tbody>
16 | {{range .Statuses}}
17 | <tr>
18 | <td>{{.Service}}</td>
19 | <td><code>{{.Dir}}</code></td>
20 | <td>{{.Status}}</td>
21 | <td>{{.PID}}</td>
22 | <td>{{.Uptime}}</td>
23 | <td style="font-size:0.9em; color:#888;">{{.ID}}</td>
24 | <td><a href="/logs/{{.ID}}" class="log-link">View logs</a></td>
25 | </tr>
26 | {{end}}
27 | </tbody>
28 | </table>
29 | {{end}}
30 |
--------------------------------------------------------------------------------
/cmd/protoc-gen-micro/examples/greeter/greeter.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option go_package = "../greeter";
4 |
5 | service Greeter {
6 | rpc Hello(Request) returns (Response) {}
7 | rpc Stream(stream Request) returns (stream Response) {}
8 | }
9 |
10 | message Request {
11 | string name = 1;
12 | optional string msg = 2;
13 | }
14 |
15 | message Response {
16 | string msg = 1;
17 | }
18 |
--------------------------------------------------------------------------------
/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 [](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_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/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/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/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 |
--------------------------------------------------------------------------------
/events/natsjs/README.md:
--------------------------------------------------------------------------------
1 | # NATS JetStream
2 |
3 | This plugin uses NATS with JetStream to send and receive events.
4 |
5 | ## Create a stream
6 |
7 | ```go
8 | ev, err := natsjs.NewStream(
9 | natsjs.Address("nats://10.0.1.46:4222"),
10 | natsjs.MaxAge(24*160*time.Minute),
11 | )
12 | ```
13 |
14 | ## Consume a stream
15 |
16 | ```go
17 | ee, err := events.Consume("test",
18 | events.WithAutoAck(false, time.Second*30),
19 | events.WithGroup("testgroup"),
20 | )
21 | if err != nil {
22 | panic(err)
23 | }
24 | go func() {
25 | for {
26 | msg := <-ee
27 | // Process the message
28 | logger.Info("Received message:", string(msg.Payload))
29 | err := msg.Ack()
30 | if err != nil {
31 | logger.Error("Error acknowledging message:", err)
32 | } else {
33 | logger.Info("Message acknowledged")
34 | }
35 | }
36 | }()
37 |
38 | ```
39 |
40 | ## Publish an Event to the stream
41 |
42 | ```go
43 | err = ev.Publish("test", []byte("hello world"))
44 | if err != nil {
45 | panic(err)
46 | }
47 | ```
48 |
49 |
--------------------------------------------------------------------------------
/events/store_test.go:
--------------------------------------------------------------------------------
1 | package events
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/google/uuid"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestStore(t *testing.T) {
11 | store := NewStore()
12 |
13 | testData := []Event{
14 | {ID: uuid.New().String(), Topic: "foo"},
15 | {ID: uuid.New().String(), Topic: "foo"},
16 | {ID: uuid.New().String(), Topic: "bar"},
17 | }
18 |
19 | // write the records to the store
20 | t.Run("Write", func(t *testing.T) {
21 | for _, event := range testData {
22 | err := store.Write(&event)
23 | assert.Nilf(t, err, "Writing an event should not return an error")
24 | }
25 | })
26 |
27 | // should not be able to read events from a blank topic
28 | t.Run("ReadMissingTopic", func(t *testing.T) {
29 | evs, err := store.Read("")
30 | assert.Equal(t, err, ErrMissingTopic, "Reading a blank topic should return an error")
31 | assert.Nil(t, evs, "No events should be returned")
32 | })
33 |
34 | // should only get the events from the topic requested
35 | t.Run("ReadTopic", func(t *testing.T) {
36 | evs, err := store.Read("foo")
37 | assert.Nilf(t, err, "No error should be returned")
38 | assert.Len(t, evs, 2, "Only the events for this topic should be returned")
39 | })
40 |
41 | // limits should be honoured
42 | t.Run("ReadTopicLimit", func(t *testing.T) {
43 | evs, err := store.Read("foo", ReadLimit(1))
44 | assert.Nilf(t, err, "No error should be returned")
45 | assert.Len(t, evs, 1, "The result should include no more than the read limit")
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/genai/default.go:
--------------------------------------------------------------------------------
1 | package genai
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | var (
8 | DefaultGenAI GenAI = &noopGenAI{}
9 | defaultOnce sync.Once
10 | )
11 |
12 | func SetDefault(g GenAI) {
13 | defaultOnce.Do(func() {
14 | DefaultGenAI = g
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/genai/genai.go:
--------------------------------------------------------------------------------
1 | // Package genai provides a generic interface for generative AI providers.
2 | package genai
3 |
4 | // Result is the unified response from GenAI providers.
5 | type Result struct {
6 | Prompt string
7 | Type string
8 | Data []byte // for audio/image binary data
9 | Text string // for text or image URL
10 | }
11 |
12 | // Stream represents a streaming response from a GenAI provider.
13 | type Stream struct {
14 | Results <-chan *Result
15 | Err error
16 | // You can add fields for cancellation, errors, etc. if needed
17 | }
18 |
19 | // GenAI is the generic interface for generative AI providers.
20 | type GenAI interface {
21 | Generate(prompt string, opts ...Option) (*Result, error)
22 | Stream(prompt string, opts ...Option) (*Stream, error)
23 | String() string
24 | }
25 |
26 | // Option is a functional option for configuring providers.
27 | type Option func(*Options)
28 |
29 | // Options holds configuration for providers.
30 | type Options struct {
31 | APIKey string
32 | Endpoint string
33 | Type string // "text", "image", "audio", etc.
34 | Model string // model name, e.g. "gemini-2.5-pro"
35 | // Add more fields as needed
36 | }
37 |
38 | // Option functions for generation type
39 | func Text(o *Options) { o.Type = "text" }
40 | func Image(o *Options) { o.Type = "image" }
41 | func Audio(o *Options) { o.Type = "audio" }
42 |
43 | // Provider registry
44 | var providers = make(map[string]GenAI)
45 |
46 | // Register a GenAI provider by name.
47 | func Register(name string, provider GenAI) {
48 | providers[name] = provider
49 | }
50 |
51 | // Get a GenAI provider by name.
52 | func Get(name string) GenAI {
53 | return providers[name]
54 | }
55 |
--------------------------------------------------------------------------------
/genai/noop.go:
--------------------------------------------------------------------------------
1 | package genai
2 |
3 | type noopGenAI struct{}
4 |
5 | func (n *noopGenAI) Generate(prompt string, opts ...Option) (*Result, error) {
6 | return &Result{Prompt: prompt, Type: "noop", Text: "noop response"}, nil
7 | }
8 |
9 | func (n *noopGenAI) Stream(prompt string, opts ...Option) (*Stream, error) {
10 | results := make(chan *Result, 1)
11 | results <- &Result{Prompt: prompt, Type: "noop", Text: "noop response"}
12 | close(results)
13 | return &Stream{Results: results}, nil
14 | }
15 |
16 | func (n *noopGenAI) String() string {
17 | return "noop"
18 | }
19 |
20 | var Default = &noopGenAI{}
21 |
--------------------------------------------------------------------------------
/genai/openai/openai_test.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "go-micro.dev/v5/genai"
5 | "os"
6 | "testing"
7 | )
8 |
9 | func TestOpenAI_GenerateText(t *testing.T) {
10 | apiKey := os.Getenv("OPENAI_API_KEY")
11 | if apiKey == "" {
12 | t.Skip("OPENAI_API_KEY not set")
13 | }
14 | client := New(genai.WithAPIKey(apiKey))
15 | res, err := client.Generate("Say hello world", genai.Text)
16 | if err != nil {
17 | t.Fatalf("Generate error: %v", err)
18 | }
19 | if res == nil || res.Text == "" {
20 | t.Error("Expected non-empty text response")
21 | }
22 | }
23 |
24 | func TestOpenAI_GenerateImage(t *testing.T) {
25 | apiKey := os.Getenv("OPENAI_API_KEY")
26 | if apiKey == "" {
27 | t.Skip("OPENAI_API_KEY not set")
28 | }
29 | client := New(genai.WithAPIKey(apiKey))
30 | res, err := client.Generate("A cat wearing sunglasses", genai.Image)
31 | if err != nil {
32 | t.Fatalf("Generate error: %v", err)
33 | }
34 | if res == nil || res.Text == "" {
35 | t.Error("Expected non-empty image URL")
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/genai/options.go:
--------------------------------------------------------------------------------
1 | package genai
2 |
3 | // Option sets options for a GenAI provider.
4 | func WithAPIKey(key string) Option {
5 | return func(o *Options) {
6 | o.APIKey = key
7 | }
8 | }
9 |
10 | func WithEndpoint(endpoint string) Option {
11 | return func(o *Options) {
12 | o.Endpoint = endpoint
13 | }
14 | }
15 |
16 | func WithModel(model string) Option {
17 | return func(o *Options) {
18 | o.Model = model
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/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 | <!DOCTYPE html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8">
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 | <title>{% if page.title %}{{ page.title }} | {% endif %}Go Micro Documentation</title>
7 | <style>
8 | body { font-family: sans-serif; margin: 0; padding: 0; background: #f9f9f9; }
9 | header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 2rem; background: #fff; border-bottom: 1px solid #eee; }
10 | .logo-link { display: flex; align-items: center; text-decoration: none; }
11 | .logo-link img { height: 40px; margin-right: 10px; }
12 | nav a { margin-left: 24px; color: #333; text-decoration: none; font-weight: 500; }
13 | nav a:hover { color: #007d9c; }
14 | main { max-width: 800px; margin: 2rem auto; background: #fff; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.03); }
15 | pre { background: whitesmoke; padding: 10px; border-radius: 5px; }
16 | </style>
17 | </head>
18 | <body>
19 | <header>
20 | <a class="logo-link" href="/">
21 | <img src="/images/logo.png" alt="Go Micro Logo">
22 | <span style="font-size: 1.3rem; font-weight: bold; color: #222;">Go Micro</span>
23 | </a>
24 | <nav>
25 | <a href="/docs/">Docs</a>
26 | <a href="https://github.com/micro/go-micro" target="_blank" rel="noopener">GitHub</a>
27 | <a href="/">Home</a>
28 | </nav>
29 | </header>
30 | <main>
31 | {{ content }}
32 | </main>
33 | </body>
34 | </html>
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/01ed999b2ac8505c2bf470eef067a7f77fef30b9/internal/website/images/logo.png
--------------------------------------------------------------------------------
/internal/website/index.html:
--------------------------------------------------------------------------------
1 |
2 | <!DOCTYPE html>
3 | <html>
4 | <head>
5 | <meta charset="UTF-8" />
6 | <meta name="viewport" content="width=device-width" />
7 | <meta name="go-import" content="go-micro.dev/v5 git https://github.com/micro/go-micro">
8 | <meta name="go-source" content="go-micro.dev/5 https://github.com/micro/go-micro https://github.com/micro/go-micro/tree/master{/dir} https://github.com/micro/go-micro/blob/master{/dir}/{file}#L{line}">
9 | <title>Go Micro</title>
10 | <style>
11 | body {
12 | font-family: Arial;
13 | font-size: 16px;
14 | margin: 25px;
15 | }
16 | .container {
17 | max-width: 250px;
18 | margin: 0 auto;
19 | padding-top: 100px;
20 | text-align: center;
21 | }
22 | a { color: black; text-decoration: none; font-weight: bold; padding: 10px;}
23 | pre { background: #f5f5f5; border-radius: 5px; padding: 10px;}
24 | </style>
25 | </head>
26 |
27 | <body>
28 | <div class="container">
29 | <!-- <h1>Go Micro</h1> -->
30 | <a href="https://github.com/micro/go-micro">
31 | <img src="https://raw.githubusercontent.com/micro/go-micro/master/logo.png" style="height: auto; width: 100%;" />
32 | </a>
33 | <p>A Go microservices framework</p>
34 | <pre>go get go-micro.dev/v5</pre>
35 | </div>
36 | </body>
37 | </html>
38 |
39 |
--------------------------------------------------------------------------------
/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/01ed999b2ac8505c2bf470eef067a7f77fef30b9/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/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/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/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<string, string> 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/http2_buf_pool.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import "sync"
4 |
5 | var http2BufPool = sync.Pool{
6 | New: func() interface{} {
7 | return make([]byte, DefaultBufSizeH2)
8 | },
9 | }
10 |
11 | func getHTTP2BufPool() *sync.Pool {
12 | return &http2BufPool
13 | }
14 |
--------------------------------------------------------------------------------
/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/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 | ```
--------------------------------------------------------------------------------